算法题解(一)

目录

(一)异或解题思路:

  (二) 二分查找的拓展:

(三)小和问题

(四)荷兰国旗问题


(一)异或解题思路:

  异或的基础知识:相同为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).

1610
110

方法:俩个数组(数组中的数和[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;
	}

(3)0000000111111111查找第一个1

int search(int *arr,int n){

//通过mid查找<=,通过head tail共同定位<
int head=0,tail=n-1,mid;
while(head<tail)
 {
  mid=(head+tail)/2;
  if(arr[mid]==0) head=mid+1;
  else tail=mid;
}

 if (arr[head]==0) return -1;
 else return head;
}

(4)111111111100000000查找第一个0

int search(int *arr,int n){

//通过mid查找<=,通过head tail共同定位<
int head=0,tail=n-1,mid;
while(head<tail)
 {
  mid=(head+tail)/2;
  if(arr[mid]==1) head=mid+1;
  else tail=mid;
}

 if (arr[head]==1) return head;
 head-=1;
 if(head<0) return -1;
 return head;
}

 函数<——>数组(本质上都是映射),,

函数中有参数值,代表计算资源。数组有下标和值,代表存储资源。俩者相互转化就是时间换取空间。二分算法中有序数组对应单调函数。

例(三)小和问题

在一个数组中,每一个数字左边比当前数字小的数字累加起来的和,叫做这个数组的小和。求一个数组的小和。一定有排序。

//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 };
	}

  

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太一TT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值