(一)异或解题思路:
异或的基础知识:相同为0,相异为1
(1)异或也称为无进位加法
(2)0^N=N N^N=0
相同数字如果有奇数个异或结果仍为这个数,如果这个相同数字有偶数个,异或完是0
(3) 异或的交换律和结合律:a^b=b^a; a^b^c=a^(b^c)
(4)一团数字异或之间没有顺序,一群相同的数字中有一个数字不同,异或最后的结果是那个不同的数字。
//1.俩个数字之间交换
int a=甲;
int b=乙;
a=a^b;//a=甲^乙;
b=a^b;//乙=甲^乙^乙=甲
a=a^b;//甲=甲^乙^甲=乙
//a和b必须要求在俩块不同的空间内
//2.在一个数组中已知只有一种数字出现了奇数次,剩下的数字都出现了偶数次。怎么找出这个数字
void num(int arr[],int size)
{
int eor=0;
for(eor=0;eor<size-1;eor++)
{
eor^=arr[eor];//偶数次的数字都异或完,奇数次的数字最后保留
}
return eor;
}
//3俩种数出现了奇数次 剩下的数字出现了偶数次
void game(int arr[],int size)
{
int err=0,i=0;
for(i=0;i<size;i++)
err=err^arr[i];//err的最后结果是那俩个数a^b;
int A=err^(~err+1);//得到异或结果为1的最右边一位
int err1=0;
for(i=0;i<size;i++)
{
if(arr[i]&A==0)
err1^=arr[i];//把结果为1的数字中末尾是0的分到一组 得到a或者b
}
return err1,err1^err;
}
//一个整型数组nums里除了俩个数字之外,其他数字都出现了俩次。请写出一个程序找出这俩个只出现一次的数字。要求时间复杂度O(n),空间复杂度O(1)
void game(int arr[],int size)
{
int i=0,err=0;
for(i=0;i<size;i++)
{
err^=arr[i];
}
题目:一个整型数组nums里除了俩个数字之外,其他数字都出现了俩次。请写出一个程序找出这俩个只出现一次的数字。要求时间复杂度O(n),空间复杂度O(1)
思路:所有的数字异或,结果==俩个出现1次的数异或的值(出现俩次的元素都异或没了)
假设出现1次的俩个数字为8和3
00000011
00001000
^
00001011
找任意一个异或的结果为1的位。说明异或的俩个数字的那一位一个为0,一个为1。
假设找到这个异或结果的第N位为1,把原数组中第N位为1的分在一组,第N位为0的分在一组。
那么出现俩次的要么进入第一组,要么进入第二组。出现1次的一个进入第一组,1个进入第二组。
int *singlenumber(int *num,int numsize,int*returnSize)
{
int ret=0;
for(int i=0;i<numsize;i++)
{
ret^=num[i];
}//数组中的所有数字异或,出现俩次的数字都没了。ret==x1^x2
//分离出x1和x2 找出ret中任意的第M位为1的,一个为1一个为0分为俩组
int m=0;
while(m<32)
{
if(ret&(1<<m))
break;
else ++m;
}
//去原数组中分离出x1和x2 x1和x2各在一组,其他的数字成对在另一组
int x1=0,x2=0;
for(int i=0;i<numsize;i++)
{
if(num[i]&(i<<m))
{
x1^=a[i];
}
else{
x2^=a[i];
}
}
int* retArr=(int *)malloc(sizeof(int)*2);
retArr[0]=1;
retArr[1]=2;
*returnsize=2;
return 0;
}
题目:数组num中包含了从0到n的所有整数,但其中缺少了一个。找出这个缺失的整数。时间复杂度O(n).
1 | 6 | 10 |
1 | 10 |
方法:俩个数组(数组中的数和[0,N]) 之间的数字相互异或。相同的就抵消了。结果就是要找的数字。
(1)先用0(x)和原数组的数字异或。
(2)再用x和[0,N]的数字异或
(3)最后的x就是要找的数字
00000000
^00000001//1
00000001
^00000010//2
00000011//3
^00000001//1
00000010//2
int number(int*num,int numsize)
{
int x=0;
for(size_t i=0;i<numsize;i++)
{
x^=nums[i];
}
for(size_t i=0;i<=numsize;i++)
{
x^=i;
}
return x;
}
俩个数字交换
俩个数字之间交换
int a=甲;
int b=乙;
a=a^b;//a=甲^乙;
b=a^b;//乙=甲^乙^乙=甲
a=a^b;//甲=甲^乙^甲=乙
(二) 二分查找的拓展:
int GinarySearch(int *a,int n,int x)
{
assert(a);
int begin=0;
int end=n;
while(begin<end)
{
int mid=begin+((end-begin)>>1);
if(a[mid]<x)
begin=mid+1;
else if(a[mid]>x)
end=mid;
else return a[mid];
}
return 0;
}
折半搜索法:
由于这个数组中有n个元素,每查找一次变为原来的一半。反向来看,2*x=n解的x=log2n(x为查找的次数)
最坏情况O(log2n)//难在排序 最好情况O(1)
1)在一个有序数组中,找到大于等于某个数的最左侧的位置
void find(int arr[],int size,int x)
{
int left=0,right=size-1,mid=0;
if(arr==NULL||size==0)
return -1;
while(left<=right)
{
int mid=(right+left)/2;
if(arr[mid]>=x)
{
right=mid-1;
}
else if(arr[mid]<=x)
{
left=mid+1;
}
}
return mid;
}
2)数组所有成员无序,任意俩个相邻的数字不相等,求局部最小。
局部最小:arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]<arr[1],那么arr[0]为局部最小。如果arr[N-1]<arr[N-2],那么arr[N-1]是局部最小。如果0<i<N-1,arr[i]<arr[i+1]&&arr[i]<arr[i-1],那么arr[i]是局部最小。
void min(int arr[],int length){
if (arr == null || arr.length == 0) {
return -1; // no exist
}
if (arr.length == 1 || arr[0] < arr[1]) {
return 0;
}
if (arr[arr.length - 1] < arr[arr.length - 2]) {
return arr.length - 1;
}
int left = 1;
int right = arr.length - 2;
int mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (arr[mid] > arr[mid - 1]) {
right = mid - 1;
} else if (arr[mid] > arr[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}
return left;
}
例(三)小和问题
在一个数组中,每一个数字左边比当前数字小的数字累加起来的和,叫做这个数组的小和。求一个数组的小和。一定有排序。
//arr[L,R]既要排好序,也能走小和
int process(int a[],int i,int r)
{
if(i==r)
return 0;
int mid=i+((r-l)>>1);
return process(arr,l,mid)+process(arr,mid+1,r)+merge(arr,i,mid,r);
//左侧求小和的数量+右侧小和数+merge小和数目
}
int merge(int arr[],int L,int m,int r)
{
int help[]=new int[r-L+1];
int i=0;
int p1=L;
int p2=m+1;
int res=0;
for(p1<=m&&p2<=r)
{
res+=arr[p1]<arr[p2]?(r-p2+1)*arr[p1++]:0;
help[i++]=arr[p1]<arr[p2]?arr[p1]:arr[p2];
}
while(p1<=m){
help[i++]=arr[p1++];
}
while(p2<=r){
help[i++]=arr[p2++];
}
for(i=0;i<sizeof(help)/sizeof(arr[0]);i++)
{
arr[L+i]=help[i];
}
}
逆序对问题
在一个数组中,左边的数字如果比右边大,则这俩个数字构成一个逆序对,请打印所有的逆序对
//
int process(int a[],int i,int r)
{
if(i==r)
return 0;
int mid=i+((r-l)>>1);
return process(arr,l,mid)+process(arr,mid+1,r)+merge(arr,i,mid,r);
}
int merge(int arr[],int L,int m,int r)
{
int help[]=new int[r-L+1];
int i=0;
int p1=L;
int p2=m+1;
int res=0;
for(p1<=m&&p2<=r)
{
res+=arr[p1]>arr[p2]?(r-p2+1)*arr[p1++]:0;
help[i++]=arr[p1]<arr[p2]?arr[p1]:arr[p2];
}
while(p1<=m){
help[i++]=arr[p1++];
}
while(p2<=r){
help[i++]=arr[p2++];
}
for(i=0;i<sizeof(help)/sizeof(arr[0]);i++)
{
arr[L+i]=help[i];
}
}
(四)荷兰国旗问题
给定一个数组arr和一个数字num,请把小于等于num的数字放在数组的左边,大于num的数字放在数组的右边
void flag(int arr[],int length,int nums)
{
int x=-1;//标记小于区域,让后面小于的和小于区域的下一个位置交换,小于区域++
//如果第一个位置是小于,其实就是和自己交换,i一定永远大于等于x
for(int i=0;i<size;i++) {
if(a[i]<=nums) {
int temp=a[i];
a[i]=a[x+1];
a[x+1]=temp;
x++;
}
}
}
//给定一个数组arr和一个数字num,把小于num的数字放在数组左边,等于的放在中间,大于的放在右边
void flag2(int arr[], int l, int r, int num) {
int less = l - 1;
int more = r + 1;
int cur=l;
while (cur< more) {
if (arr[cur] < num) {
swap(arr, ++less, index++);
} else if (arr[cur] > num) {
swap(arr, --more, cur);
} else {
cur++;
}
}
return new int[] { less + 1, more - 1 };
}