力扣算法题(三)二分查找

本文详细介绍了如何使用二分查找解决多种编程挑战,包括寻找数组中满足特定条件的最小子数组长度、查找排序数组中的目标值及其索引、找到旋转排序数组中的元素等。通过二分查找的原理,分析了不同问题的解决方案,并提供了相应的代码模板,帮助读者深入理解二分查找的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

209-Minimum Size Subarray Sum

给定含有n个正整数的数组,和一个正整数target,找出该数组中满足其和>=target的长度最小的连续子数组,并返回其长度,如果不存在,返回0

//方法有误,暂未查出
public int minSubArrayLen03(int target,int[] nums) {

   int result=0;
   for(int i=0;i<nums.length;i++) {
	   result+=nums[i];
   }
   if(result<target) {
	   return 0;
   }
   int i=0,j=nums.length-1;
   while(i<j) {
	   if(nums[i]<nums[j]) {
		   if(result-nums[i]>=target) {
			   result=result-nums[i];
			   i++;
		   }else {
			   return j-i+1;
		   }
	   }else if(nums[i]>nums[j]){
		   if(result-nums[j]>=target) {
			   result=result-nums[j];
			   j--;
		   }else {
			   return j-i+1;
		   }
	   }else if(nums[i]==nums[j]) {
		   if(result-nums[i]>=target) {
			   if((i+1)<(j-1)) {
				   if((nums[i+1]<nums[j-1])) {
					   result-=nums[i];
					   i++;
				   } else {
					   result-=nums[j];
					   j++;
				   }
			   }else {
				   result-=nums[i];
				   i++;
			   }
		   }else {
			   return j-i+1;
		   }
		   
	   }
   }
   return j-i+1;

}

1,暴力法
初始化子数组长度为数组长度,枚举数组中的每个下标作为子数组的开始下标,//对于每个开始下标i,需要找到大于等于i的最小下标j,使得nums[i]到nums[j]的元素和大于等于s
更新数组的最小长度

public int minSubArrayLen04(int s,int[] nums) {
	   int n=nums.length;
	   //特殊情况
	   if(n==0) {
		   return 0;
	   }
	   int ans=Integer.MAX_VALUE;
	   for(int i=0;i<n;i++) {
		   int sum=0;
		   for(int j=i;j<n;j++) {
			   sum+=nums[j];
			   if(sum>=s) {
				   ans=Math.min(ans, j-i+1);
				   break;
			   }
		   }
	   }
	   return ans==Integer.MAX_VALUE?0:ans;//o(n2),o(1)
   }

2,前缀和+二分查找
为了使用二分查找,需要额外创建一个数组sums用于存储数组nums的前缀和,
其中sums[i]表示从nums[0]到nums[i-1]的元素和。
对每个下标i,通过二分查找得到大于或等于i的最小下标bound,使得sums[bound]-sums[i-1]>=s
更新子数组的最小长度,子数组的长度是bound-(i-1)

public int minSubArrayLen05(int s,int[] nums) {
	   int n=nums.length;
	   if(n==0) {
		   return 0;
	   }
	   int ans=Integer.MAX_VALUE;
	   int[] sums=new int[n+1];
	   //sums[0]=0表示前0个元素的前缀和为0
	   //数组递增,因为为正
	   for(int i=1;i<=n;i++) {
		   sums[i]=sums[i-1]+nums[i-1];
	   }
	   for(int i=1;i<=n;i++) {
		   int target=s+sums[i-1];//也就是target-sums[i-1]=s
		   int bound =Arrays.binarySearch(sums, target);
		   //在sums中搜索值为target的元素,返回索引。
		   // 搜索值不是数组元素,且大于数组内元素,索引值为 – (length + 1);
		   //搜索值不是数组元素,且小于数组内元素,索引值为 – 1。
		   if (bound < 0) {
               bound = -bound - 1;
               //length:找不到最后结果会小于0
               //0     :找不到的话最后结果会小于0
           }
           if (bound <= n) {
               ans = Math.min(ans, bound - (i - 1));
           }
       }
       return ans == Integer.MAX_VALUE ? 0 : ans;
	   }

//二分查找的时间复杂度是logn,遍历的时间复杂度是n,所以总时间复杂度是O(nlog(n))

3,滑动窗口
定义两个指针start和end分别表示子数组的开始位置和结束位置,
维护sum存储子数组中的元素和,即从nums[start]到nums[end]的元素和
初始,start和end都指向0,sum值为0,
每一轮迭代将nums[end]加到sum,如果sum>=s,则更新子数组最小长度end-start+1
然后将nums[start]从sums中减去并将start右移,直到sum<s,
在此过程中同样更新子数组的最小长度。每一轮迭代最后end右移。

public int minSubArrayLen06(int s,int[] nums) {
	   int n=nums.length;
	   if(n==0) {
		   return 0;
	   }
	   int ans=Integer.MAX_VALUE;
	   int start=0,end=0;
	   int sum=0;
	   while(end<n) {//不够大就一直加
		   sum+=nums[end];
		   while(sum>=s){
			   ans=Math.min(ans, end-start+1);
			   sum-=nums[start];
			   start++;
		   }
		   end++;
	   }
	   return ans==Integer.MAX_VALUE?0:ans;
   }

704-Binary Search (未找到返回-1)

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

//二分查找,不用仔细看。

public int search(int[] nums,int target) {
	   int n=nums.length;
	   if((nums[0]>target)||(nums[n-1])<target) {
		   return -1;
	   }
	   int i=0;
	   for(i=0;i<n;i++) {
		   if(nums[i]==target) {
			   return i;
		   }
	   }
	   return -1;//循环出来也没找到,说明没有
   }

//二分查找
//初始化指针left=0,right=n-1 当left<=right
//循环,当left<right
//比较中间元素nums[privot=(left+right)/2]和目标值target
//如果target=nums[pivot],返回pivot
//如果target<nums[pivot],则在右侧继续搜索right=pivot-1
//如果target>nums[pivot],则在右侧继续搜索left=pivot+1

  public int search01(int[] nums,int target) {
	   int n=nums.length;
	   int left=0,right=n-1,pivot=0;
	   while(left<=right) {
//<=因为可能出现只有一个元素的情况
		   pivot=left+(right-left)/2;
//返回中间 位置的下标,判断并进行下一次二分,可以防止right+left可能会溢出
		   if(target==nums[pivot]) {
			   return pivot;
		   }else if(target>nums[pivot]) {//向右侧搜索
			   left=pivot+1;
		   }else {
			   right=pivot-1;
		   }
	   }
	   //出了循环还没找到,说明没有
	   return -1;
   }

35-Search Insert Position (未找到元素时返回待插入元素的位置,若存在多个目标元素,则返回任意一个) (二分查找避免死循环注意项)

//给定排序数组和目标值,在数组中找到目标值,并返回其索引,如果目标值不存于数组中,返回它将会按顺序插入的位置

   public int searchInsert(int[] nums, int target) {
       int n=nums.length;
	   //排除,target不在数组范围内
	   if(target<=nums[0])
		   return 0;
       //比最后一个数大放在n位置
	   if(target>nums[n-1])
		   return n;
       //和最后一个数相等放在n-1位置
      if(target==nums[n-1])
           return n-1;
	   int i=0;
	   while(i<n) {
		   if(nums[i]>=target) {
			   return i;
		   }else{
              i++;
          }
	   }
	   return i;
   }

//暴力解决需要O(n)的时间复杂度,二分的话则可以降低到O(logn)
//思路和二分查找没有区别,设定左侧下标left和右侧下标right中间下标mid
//每次根据nums[mid]和target之间的大小进行判断,相等则直接返回下标
//查找结束没有相等值返回left

模板

    public int searchInsert01(int[] nums,int target) {
    	int left=0,right=nums.length-1;
    	while(left<=right) {
    		//这里=因为可能数组只有一个元素
    		int mid=left+(right-left)/2;//5/2=2
    		if(nums[mid]==target) {
    			
    		}else if(nums[mid]<target) {
    			left=mid+1;//去右边找,从mid+1到right,剔除mid点
    		}else if(nums[mid]>target){
    			right=mid-1;//左边找,从left到mid-1,剔除了mid点
    		}
    	}
    	return 0;
    }

34-Find First and Last Position of Element in Sorted Array (返回目标元素的左右边界,不存在时则返回{-1,-1}) (34升级)

//34排序数组
//在排序数组中查找该元素在数组中第一个和最后一个的位置
//如果没有返回[-1,1]

//由于数组已经排序,整个数组是单调递增的,使用二分法来加速查找过程
//考虑target的开始位置和结束位置,我们要找的就是数组中,
//第一个等于target的位置和第一个大于target的位置

public int[] searchRange(int[] nums,int target) {
	int leftIdx=binarySearch(nums,target,true);
	int rightIdx=binarySearch(nums,target,false)-1;
	if(leftIdx<=rightIdx&&rightIdx<nums.length&&nums[leftIdx]==target&&nums[rightIdx]==target) {
		return new int[] {leftIdx,rightIdx};
	}
	return new int[] {-1,-1};
}



public int binarySearch(int[] nums, int target, boolean lower) {
	int left=0,right=nums.length-1,ans=nums.length;
	while(left<right) {
		int mid=(left+right)/2;
		if(nums[mid]>target||(lower&&nums[mid]>=target)) {
			//找第一个等于或者最大的小于它的,找第一个大于它
			right=mid-1;
			ans=mid;
		}else {
			left=mid+1;
		}
	}
	return 0;
}

//while(left<=right)在退出循环的时候left=right+1,即right在左,left在右
//while(left< right)在退出循环的时候,left==right

//1,找第一次出现的位置,二分查找,找到了继续向左找 2,查找出现的最后一个位置,向右

 public int[] searchRange01(int[] nums,int target) {
    	if(nums.length==0) {
    		return new int[] {-1,-1};
    	}
    	int firstPosition=findFirstPosition(nums,target);
    	int lastPosition=findLastPosition(nums,target);
    	return new int[] {firstPosition,lastPosition};
    }
    private int findFirstPosition(int[] nums,int target) {
    	int left=0;
    	int right=nums.length-1;
    	while(left<=right) {
    		int mid=left+(right-left)/2;
    		
    		if(nums[mid]==target) {
    			//不可以直接返回,应该继续向左边找,即[left,mid-1]区间里找
    			right=mid-1;
    		}else if(nums[mid]<target) {
    			//应该继续向右边找,即[mid+1,right]区间里找
    			left=mid+1;
    		}else {//此时 nums[mid]>target,应该继续向左找,即[left,mid-1]区间找
    			right=mid-1;
    		}
    	}
    	//此时left和right位置left=right+1,此时left才是第一次元素出现的位置
    	//因此还需要特别做一次判断
    	if(left!=nums.length&&nums[left]==target) {
    		return left;
    	}
    	return -1;
    }
    
    private int findLastPosition(int[] nums,int target) {
    	 int left = 0;
         int right = nums.length - 1;

         while (left <= right) {
             int mid = left + (right - left) / 2;
             if (nums[mid] == target) {
                 // 只有这里不一样:不可以直接返回,应该继续向右边找,即 [mid + 1, right] 区间里找
                 left = mid + 1;
             } else if (nums[mid] < target) {
                 // 应该继续向右边找,即 [mid + 1, right] 区间里找
                 left = mid + 1;
             } else {
                 // 此时 nums[mid] > target,应该继续向左边找,即 [left, mid - 1] 区间里找
                 right = mid - 1;
             }
         }

         if (right != -1 && nums[right] == target) {
             return right;
         }
         return -1;

    }

33-Search in Rotated Sorted Array (有序数组旋转,二分查找的线性思维)

//整数数组nums按升序排列,数组中的值互不相同
//在传递给函数之前,nums在预知未知的某个下标k上进行了旋转,(0=<k<nums.length)
//使数组变为nums[k],nums[k+1]...nums[n-1],nums[n],nums[0],nums[1]...nums[k-1]
//[0,1,2,4,5,6,7]在下标3处经过旋转后可能变为[4,5,6,7,0,1,2]
//给旋转后的数组nums和一个整数target,
//如果nums中存在这个目标值target,则返回它的索引,否则返回-1

//无序数组排序

//1,将旋转数组转换为有序数组进行查询,
//找到最值将旋转数组分成 两段有序数组,接下来在有序数组中找到目标值
//找到最小的索引,将数组分成升序的两端,
//根据nums[0]和target的关系判断在左段还是右段

//对于旋转数组nums=[4,5,6,7,0,1,2]
//先根据nums[0]与target的关系判断target在左段还是右段
//例如target=5,比nums[0]大,目标值在左半段,在[4,5,6,7,inf,inf,inf]里找
//例如target=1,比nums[0]小,目标值在右半段,在[-inf,-inf,-inf,-inf,0,1,2]里找
 public int search(int[] nums,int target) {
    	int left=0,right=nums.length-1;
    	while(left<=right) {
    		int mid=left+right-left)/2;
    		if(nums[mid]==target) {
    			return mid;
    		}
    		//先根据nums[0]与target的关系判断目标值是在左半段还是右半段
    		if(target>=nums[0]) {
    			//目标值在左半段时,若mid在右半段,则将mid的值改成inf
    			if(nums[mid]<nums[0]) {
    				nums[mid]=Integer.MAX_VALUE;
    			}
    		}else {
    			//target<=nums[0]目标值在右半段时,若mid在左半段,则将mid索引的值改成-inf
    			if(nums[mid]>=nums[0]) {
    				nums[mid]=Integer.MIN_VALUE;
    			}
    		}
    		if(nums[mid]<target) {
    			left=mid+1;
    		}else {
    			right=mid-1;
    		}
    	}
    	return -1;
    }
//先根据nums[mid]与nums[lo]的关系判断mid是在左段还是右段,
//接下来再判断target是在mid的左边还是右边,从而来调整左右边界lo和hi
  public int search(int[] nums,int target) {
    	int left=0,right=nums.length-1,mid=0;
    	while(left<right) {
    		mid=left+(right-left)/2;
    		if(nums[mid]==target) {
    			return mid;
    		}
    		//先根据nums[mid]与nums[left]的关系判断mid是在左段还是右段
    		if(nums[mid]>=nums[left]) {
    			//再判断target是在mid的左边还是右边,从而调整左右边界left和right
    			if(target>=nums[left]&&target<nums[mid]) {//left<target<mid在左半段
    				right=mid-1;
    			}else {//left<mid,但是target不在他们中间,所以target在右半段
    				left=mid+1;
    			}
    		}else {//left>mid
    			if(target>nums[mid]&&target<=nums[right]) {
    				//mid<target<right,在右半段
    				left=mid+1;
    			}else {//left>mid,但是target不在mid和right中间
    				right=mid-1;
    			}
    		}
    	}
    	return -1;
    }

81-Search in Rotated Sorted Array II (33题目的升级)

//假设按照升序排序的数组在预知未知的某个点上进行了旋转
//编写一个函数来判断给定的目标值是否存在于数组中,存在返回true,否则返回false

//超出时间限制
   public boolean search04(int[] nums,int target) {
    	if(nums==null||nums.length==0) {
    		return false;
    	}
    	int left=0,right=nums.length-1,mid=0;
    	while(left<=right) {
    		mid=left+(right-left)/2;
    		if(target==nums[mid]) {
    			return true;
    		}
    		if(nums[left]==nums[mid]) {
    			left++;
    			continue;
    		}
    		//判断nums[0]和nums[mid]的大小
    	if(nums[left]<nums[mid]) {
    		if(nums[left]<=target&&target<nums[mid]) {
    			right=mid-1;
   //如果有,在左端,改的是right,left没有改,所以应该包含左边界
    		}else {
    			left=mid+1;
    		}
    	}else{
    		if((target>nums[mid])&&(target<=nums[right])) {
    			//在右半段,改left,所以包含右端相等情况
    			left=mid+1;
  //这里mid位置与target不等,只包含nums[right]相等的情况,所以可以直接去掉
    		}else {
    			right=mid-1;
    		}
    	}
    }
    	//循环到最后还是没有
		return false;	
  }

153-Find Minimum in Rotated Sorted Array (二分查找线性思维的理解)

//寻找旋转排序数组中的最小值

//这种最大值最小值一般是相邻的
//左中右三个位置的值相比较,有以下几种情况:
//1,左值<中值<右值,最小值在最左边
//2,左值>中值<右值,最小值在左半边
//3,左值<中值>右值,最小值在右半边

//4,左值>中值>右值,两个单调递减,不可能
    public int findMin(int[] nums) {
    	int left=0;
    	int right=nums.length-1;
    	while(left<right) {
    		//要保证左闭右开区间里始终套住最小值,所以不包含=
    		int mid=left+(right-left)/2;
    		if(nums[mid]>nums[right]) {//中>右,在右半边
    			left=mid+1;
    		}else if(nums[mid]<nums[right]) {
    			right=mid;
    		}
    		//整数除法是向下取整,mid更靠近left
    		//结合while条件,可以知道left<=mid,mid<right,mid始终小于right
    		//因此在while循环内,nums[mid]要么大于要么小于nums[right],不会等于
    		//这样else {right=mid;}可以改为else if(nums[mid]<nums[right]){right=mid;}
    		
//while循环退出的条件:如果输入数组只有一个数,左右边界位置重合,left==right,不会进入while循环,直接输出。
//如果输入数组多于一个数,循环到最后只会剩下两个数,nums[left]==nums[mid],以及nums[right],这里的位置left==mid==right-1
//如果num[left]==nums[mid]>nums[right],则左边大,右边小,需要执行left=mid+1,
//使得left==right,左右边界位置重合,循环结束,nums[left]与nums[right]都保存了最小值
    		
//如果nums[left]==nums[mid]<nums[right],则左边小右边大,会执行right=mid,
//使得left=right,左右边界位置重合,循环结束,left,mid,right都保存了最小值
    
//
    		
    	}
		return nums[left];//最小值一定存在且无法比较,所以只能圈住它
    }
//1,左值<中值<右值,最大值在最右边
//2,左值>中值<右值,最大值在右半边
//3,左值<中值>右值,最大值在中值这里
//最大值的下一位就是最小值,while(left<right)把最大值圈出来
    public int findMin02(int[] nums) {
    	int left=0,right=nums.length-1,mid=0;
    	while(left<right) {
    		mid=left+(right-left+1)/2;//先加一再除,mid更靠近右边的right
    		if(nums[mid]<nums[right]) {
    			left=mid;//向右移动左边界
    		}else {
    			right=mid-1;//向左移动右边界
    		}
//这里right和left的变化依然考虑只有两个数和一个数的情况
//两个数的话,left+1=mid=right,这时left=mid和right=mid-1都可以回到最终right在最大值位置
    	}
    	return nums[(right+1)%nums.length];
    	//最大值向右移动一位就是最小值了,考虑最大值在最右边的情况
    }

154-Find Minimum in Rotated Sorted Array II (153题的升级)

//寻找旋转排序数组中的最小值2
//寻找旋转数组中的最小元素,数组中可能出现重复的元素

//旋转数组包含 重复元素,可以采用right=right-1来解决这个问题,此操作不会丢失最小值
//若nums[right]是唯一最小值,不可能满足判别条件nums[mid]==nums[right]
//若nums[right]不是唯一最小值,由于mid<right而nums[mid]==nums[right]
//最小值依然在left和right-1中间

//旋转过的数组是两个小升序数组
/*
 * 1,mid>right时,最小值在mid和right中间left=mid+1,mid不可能是最小值可以直接跳过
 * 2,mid<right时,最小值在左边,mid可能时最小值,所以不跳过right=mid
 * 3,mid=right时,right=right-1
 * while(left<right),用循环包围住最小值,然后mid=left+(right-left)/2靠近left
 * 或者mid=left+(right-left+1)/2来靠近right
 * */

//结果错误(已改正)
  public int findMin03(int[] nums) {
    	
    	int left=0,right=nums.length-1,mid=0;
    	while(left<right) {
    		mid=left+(right-left)/2;
    		if(nums[mid]>nums[right]) {
    			left=mid+1;
    		}else if(nums[mid]<nums[right]) {
    			right=mid;
    		}else {
    			right=right-1;
    		}
    	}
		return nums[left];
    	//关于这个right和left怎么变化 ,在数多的时候就是排除,所以不影响
		//所以要选取特殊例子,如3,1和1,3.
		//3,1 l=0,r=1,m=0,最后要得出1,left=mid+1
		//1,3 l=0,r=1,m=0,最后要得出0,right=mid
    }
    public int finMin04(int[] nums) {
    	
    	int left=0,right=nums.length-1;
    	while(left<right) {
    		int mid=(left+right)/2;
    		if(nums[mid]>nums[right]) left=mid+1;
    		else if(nums[mid]<nums[right]) right=mid;
    		else right=right-1;
    	}
    	
		return nums[left];
    }

162-Find Peak Element

//寻找峰值,峰值元素是指大于左右相邻值的元素,给一个数组,找到峰值返回索引
//数组可能包含多个峰值,返回任意一个即可,只用大于左右两个数即可

   //从i=1开始寻找,这个想法是错误的
    public int findPeakElement(int[] nums) {
    	//注意特殊情况,端点只需要比一边大就可以
    	//只有一个元素,本身就是峰值
    	int n=nums.length-1;
        if(nums.length<2) return 0;
        if(nums[0]>nums[1]) return 0;
        if(nums[n]>nums[n-1]) return n;
        for(int i=1;i<nums.length-1;i++) {
    		if(nums[i]>nums[i-1]&&nums[i]>nums[i+1]) {
    			return i;
    		}
    	}	
		return -1;
    }

852-Peak Index in a Mountain Array

//符合下列属性的数组称为山脉数组
//arr.length>=3,0<i<arr.length-1
//存在i使得,arr[0]<arr[1]<…arr[i]>…>arr[length-1]

//最大值在最左边和最右边的不是山脉数组
//最大值周围离他越远数值越小
//遍历数组,如果后面的值大于前面的i++,当开始变小就返回i  o(n)

//二分法,求最大值,数组是两个数组合在一起,一个单调递增在左/,一个单调递减在右\
//左<中,最大值在中到右中间,left=mid,这里有问题,左<中很可能是/\这种
//左>=中,最大值在左到中中间,right=mid-1

//左<中<右,中右之间是最大值
//左<=中>右,left--,right--
//左>中>=右,左中之间

//左>中<右,不可能出现

//特殊情况,只有三个元素,132,left=1,mid=3,right=2
//2131,left=2,mid=1,right=1,
//13100,left=1,mid=1,right=0
public int peakIndexInMountainArray(int[] arr) {
	int left=0,right=arr.length-1,mid=0;
	while(left<right) {
		mid=left+(right-left)/2;
		if(arr[left]<arr[mid]&&arr[mid]<arr[right])
			left=mid;
		else if(arr[left]>arr[mid]&&arr[mid]>=arr[right])
			right=mid-1;
		else if(arr[mid]>=arr[left]&&arr[mid]>arr[right])
			{left--;right--;}
	}
	return left;
	
}
//官方方法
public int peakIndexInMoutainArray(int[] A) {
	int left=0,right=A.length-1;
	while(left<right) {
		int mid=left+(right-left)/2;
		if(A[mid]<A[mid+1])//单调递增的话这里就是处于后半段
			left=mid+1;
		else//不单调递增这里就是前半段
			right=mid;
	}
	
	return left;
}

69-Sqrt(x)

//x的平方根,实现int sqrt(int x)函数
// 输入: 4
// 输出: 2
// 示例 2:
//
// 输入: 8
// 输出: 2
// 说明: 8 的平方根是 2.82842…,
// 由于返回类型是整数,小数部分将被舍去。

//二分法搜索平方根的思想,高了降低,低了就涨。
//一个数的平方根肯定不会超过它的一半
//一个数的一半的平方大于它自己,那么这个数>=4,或者<=0

//判断数在哪两个平方中间,选左边那个
//1,4,9,16,25,36,49,64,81,100,121,144,169,
//0,2,4,8,12,
//0,1,2,4,6,
//从它的1/4遍历到1/2
    public int mySqrt(int x) {
    	if(x==0) {
    		return 0;
    	}
    	if(x<4) {
    		return 1;
    	}
    	int i=0;
    	for(i=x/4;i<x/2+1;i++) {
    		if(i*i>x) {
    			return i;
    		}
    	}
    	return i;
    }
    
    //官方解答
    public int mySqrt01(int x) {
    	
//注意:针对特殊测试用例,2145839,所以把搜索的范围设置成长整型
//为了照顾到0,把左边界设置成0
    	long left=0;
//为了照顾到1把有边界设置为x/2+1
    	long right=x/2+1;
    	while(left<right) {
    		//这里一定去右中位数,如果取左中位数,代码会进入死循环
    		//long mid=left+(right-left+1)/2;
    		long mid=(left+right+1)>>>1;//除2
//10 << 1 = 20,10 << 3 = 80,6 >> 1 = 3,6 >> 2 = 1
    		long square=mid*mid;
    		//右中位数,当只有两位数时left=0,mid=1,right=1  left=1或者right=0
    		if(square>x) {
    			right=mid-1;
    		}else {
    			left=mid;
//如果取左中位数,就会一直取mid,在只有两个数的时候,如果取左中位数,就不会变
    		}
    	}
    	return (int)left;
    }
    
    //牛顿法
    public int mySqrt03(int a) {
    	long x=a;
    	while(x*x>a) {
    		x=(x+a/x)/2;
    	}
    	return (int)x;
    }

74-Search a 2D Matrix

//编写一个高效的算法来判断m*n矩阵中,是否存在一个目标值。该矩阵具有如下特性
//每行中的整数从左到右按升序排列,每行的第一个整数大于前一行的最后一个整数
//可以用二分法,把矩阵看成数组
//m*n的矩阵可以视为长度为m*n的有序数组
 public boolean searchMatrix(int[][] matrix,int target) {
    	
    	int left=0,right=matrix.length*matrix[0].length-1;
    	while(left<=right) {
    		int mid=(left+right)/2;
    		int midVal=matrix[mid/matrix[0].length][mid%matrix[0].length];
    		if(midVal==target)return true;
    		else if(midVal>target) right=mid-1;
    		else left=mid+1;
    	}
		return false;
    }

240-Search a 2D Matrix II (74题的升级)

//编写一个高效的算法来搜索m*n矩阵中的一个目标值
//每行元素从左到右升序排列,每列元素从上到下升序排列
//矩阵已经排过序,需要使用二分法搜索以加快我们的算法

//这个算法的时间复杂度是O(log(n!)),主循环中执行的工作量最大,运行min(m,n)次迭代
//每次迭代中,我们对长度为m-i和n-i的数组片执行两次二分搜索,因此循环的每一次迭代中
   private boolean binarySearch0(int[][] matrix,int target,int start,boolean vertical) {
    	int lo=start;
    	int hi=vertical?matrix[0].length-1:matrix.length-1;
    	
    	while(hi>=lo) {
    		int mid=(lo+hi)/2;
    		if(vertical) {//搜索一列column
    			if(matrix[start][mid]<target) {
    				lo=mid+1;
    			}else if(matrix[start][mid]>target) {
    				hi=mid-1;
    			}else {
    				return true;
    			}
    		}else {//搜索一行row
    			if(matrix[mid][start]<target) {
    				lo=mid+1;
    			}else if(matrix[mid][start]>target) {
    				hi=mid-1;
    			}else {
    				return true;
    			}
    		}
    	}
    	return false;
    }
    
    public boolean searchMatrix01(int[][] matrix,int target) {
    	//首先确保矩阵不为空,迭代矩阵对角线,从当前元素对列和行搜索,
    	//保持从当前对开始的行和列为己排序。
    	//我们总是可以二分搜索这些行和列切片
    	if(matrix==null||matrix.length==0)
    		return false;
    	
    	int shorterDim=Math.min(matrix.length,matrix[0].length);
    	for(int i=0;i<shorterDim;i++) {
    		boolean verticalFound  =binarySearch0(matrix,target,i,true);
    		boolean horizontalFound=binarySearch0(matrix,target,i,false);
    		if(verticalFound||horizontalFound) {
    			return true;
    		}
    	}
    	
		return false;
    }

//对于已排序的数组,有两种方法可以确定一个任意元素目标是否可以用常数时间判断,第一
//如果数组的区域为0,则它不包含元素,如果target小于数组最小值,大于数组最大值,那么矩阵不包含目标值
//如果目标值包含在数组内,因此我们沿着索引行的矩阵中间列,如果我们找到target,立即返回true
//matrix[row-1][mid]<target<matrix[row][mid]
//矩阵可以围绕这个索引分为四个子矩阵,左上和右下矩阵不能包含目标,所以我们可以从搜索空间中删除它们

private int[][] matrix;
private int target;
private boolean searchRec(int left,int up,int right,int down) {
	//矩阵没有高度和宽度
	if(left>right||up>down) {
		return false;
	//target比最大值大比最小值小
	}else if(target<matrix[up][left]||target>matrix[down][right]) {
		return false;
	}
	int mid=left+(right-left)/2;
	//定位row,通过matrix[row-1][mid]<target<matrix[row][mid],把矩阵划分成4个
	int row=up;
	while(row<=down&&matrix[row][mid]<=target) {
		if(matrix[row][mid]==target) {
			return true;
		}
		row++;
	}
	//继续搜索两个小矩阵
		return searchRec(left,row,mid-1,down)||searchRec(mid+1,up,right,row-1);
}
	public boolean searchMatrix02(int[][] mat,int targ) {
		matrix=mat;
		target=targ;
		if(matrix==null||matrix.length==0) {
			return false;
		}
		return searchRec(0,0,matrix[0].length-1,matrix.length-1);
	}

//方法4,初始化指向矩阵左下角的指针,指向值>target,向上;指向值<target,向右;
//矩阵的特性,左上是最小值,右下是最大值

public boolean searchMatrix03(int[][] matrix,int target) {
	int row=matrix.length-1;//行
	int col=0;//列
	while(row>=0&&col<matrix[0].length) {
		if(matrix[row][col]>target) {
			row--;
		}else if(matrix[row][col]<target) {
			col++;
		}else {
			return true;
		}
	}
	return false;
}

875-Koko Eating Bananas

//这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉,H小时内最多拿多少
//每个小时选1堆,多于K则拿K,少于K全部拿
//返回他H小时内吃掉所有的最小速度
//H-nums.length=超出次数
//算法错误

public int minEatingSpeed(int[] piles,int h) {
		Arrays.sort(piles);
		if(piles==null)
			return 0;
		if(h==piles.length) {//长度为h
			return piles[h-1];
		}
		if(h==piles.length+1) {//长度为h-1
			return piles[h-3];//第二大
		}
		long sum=0;//数很可能很大
		for(int i=0;i<piles.length;i++)
			sum+=piles[i];
		return (int) (sum/h+1);
	}
	//如果珂珂能够以K的进食速度最终吃完所有的香蕉,H小时内,possible(K)=true
	//那么存在X,当K>=X时,possible(K)=true
	//当初始条件为3,6,7,11时,存在X=4使得possible(1,2,3)=false,possible(4,5,)=true
	//我们二分查找possible(K)的值,来找到第一个使得possible(x)为true的X,
	//我们将每一堆的完成时间加在一起和H比较
	public int minEatingSpeed01(int[] piles,int H) {
		int lo=1;
		int hi=1_000_000_000;
		//结果值范围可以定义一下,最慢速度,除去第一堆外,其他每堆都是0根,
		//最大速度除去第一堆,其他每堆都是一根
		while(lo<hi) {
			int mi=(lo+hi)/2;
			if(!possible(piles,H,mi))
				lo=mi+1;
			else
				hi=mi;
		}
		return lo;
	}
	public boolean possible(int[] piles,int H,int K) {
		int time=0;
		for(int p:piles)
			time+=(p-1)/K+1;//!!!!!!!
		return time<=H;
	}

1011-Capacity To Ship Packages Within D Days

//1011在D天内送达包裹的能力
//传送带上的i个包裹重量为weight[i],按给出的重量顺序往传送带装载包裹
//装载重量不会超过最大载重G
//返回D天内能将传送带上的所有包裹送达的船的最低运载能力
//最小是,刚好每天都运了最大载重,和/D.
//最大是,((和+1)/D-1)*2, G/2,G/2+1,G/2+1 1,G//shipwithdays方法有误

public static int shipWithinDays(int[] weights,int D) {
    		int len=weights.length;
    		int sum=0,max=0;
    		if(len==0) 
    			{return 0;}
    		for(int num:weights) {
    			max=Math.max(max, num);
    			sum+=num;
    		}
    		/*要在D天内运完所有包裹,那么每天至少的承重量为sum/D
    		 *但是,因为一次至少运一个包裹!!,而包裹可大可小,那么可能weight[i]>sum/D
    		 *所以最低承重max(sum/D,maxWeight)*/
    		//最低承载量
    		int left=Math.max(sum/D, max);//向下取整
    		//最高承载量
    		int right=((sum+1)/D-1)	*2+1;//向上取整,防止失去边界i
    		while(left<right) {
    			//int mid=left+(right-left)>>>1;//向左贴近
    			int mid=(right+left)/2;
    			if(isok(weights,mid,D))
    				right=mid;
    			else
    				left=mid+1;
    		}
			return left;
    	}
    	
    	//判断G最大载重,能否在D天内装完
    	private static boolean isok(int[] weights, int G,int D) {
    		int temp=0;
    		for(int val:weights) {
    			if(temp+val>G) {
    				temp=0;
    				D--;
    			}
    			temp+=val;
    		}
    		return D>0;
    	}
    	
    	public static boolean shipwithdays(int [] weight,int G,int D) {
    		int time=0;
    		int i=0;int sum=0;
    		while(i<weight.length) {
    			if(sum<=G) {
    				sum+=weight[i];
    				if(i<weight.length) {
    				   i++;//装上了,指向下一个
    				}
    				
    			}else {
    				i--;
    				sum-=weight[i];
    				System.out.println("sum="+sum);
    				time++;System.out.println(time+"车");
    				sum=0;//再开始新的一车
    				//装不下拿出来,送走
    				System.out.println("i="+i);
    			}
    		}
    		System.out.println("跳出循环");
    		System.out.println(time);
    		if(time>D)
    			return false;
    		return true;
    	}

373-Find K Pairs with Smallest Sums

//给定两个升序排列的整形数组nums1和nums2,以及一个整数k
//定义一对值(u,v),其中一个元素来自nums1,第二个元素来自nums2
//找到和最小的k对数字(u1,v1)(u2,v2)…(uk,vk),元素可以重复使用

	//利用一个数组来保存nums1中每个元素对应的最小组合的nums2下标,
	//每次只需要最多遍历n次就能获取剩下可用的最小组合,获取之后,对应的下标就加一,
	//防止重复获取,双指针,指的就是f数组的下标对应的nums1的下标元素,
	//f数组中的元素值对应的就是nums2下标元素,
public List<List<Integer>> kSmallestPairs01(int[] nums1,int[] nums2,int k){
			
    		List<List<Integer>> res=new ArrayList<>();
    		int n=nums1.length,m=nums2.length;
    		if(n==0||m==0)return res;
    		//利用一个数组来保存nums1中每个元素对应的最小组合的nums2下标,初始值0,因为刚开始对应的是最小元素
    		int[] f=new int[n];
    		
    		//外层最多遍历k次,获取前k个最小值
    		while(res.size()<k) {
    			int cur=0;
    			//遍历每个nums1元素对应nums2最小可用组合,并获取最小组合
    			for(int i=1;i<n;i++) {
    				//当前i位置可用组合已经用完了
    				if(f[i]==m)continue;
    				//比较获取最小的组合
    				if(f[cur]==m||nums1[cur]+ nums2[f[cur]]>nums1[i]+nums2[f[i]]) {
    					cur=i;
    				}
    			}
    			//所有的组合都用完了,就跳出循环
    			if(f[cur]==m)break;
    			//答案中添加当前组合
    			res.add(Arrays.asList(nums1[cur],nums2[f[cur]]));
    			//当前组合中nums1元素对应的nums2元素下标加一,这样就不会重复用到之前的组合
    			f[cur]++;
    		}
    		return null;
    		
    	}

378-Kth Smallest Element in a Sorted Matrix (二分查找解空间的理解)

//有序矩阵中第k小的元素
//给定n*n矩阵,其中每行每列元素按升序排序,找到矩阵中第k小的元素
//它是排序后的第k小元素,而不是第k个不同的元素

//由题目给出的性质可知,这个矩阵的每一行均为一个有序数组,
//问题转化为n个有序数组中找第k大的数,可以想到归并排序的做法,归并到第k个数即可停止
//本题是n个数组归并,需要用小根堆维护

public int kthSmallest02(int[][] matrix,int k) {
    		PriorityQueue<int[]> pq=new PriorityQueue<int[]>(new Comparator<int[]>() {
    			public int compare(int[]a,int[]b) {
    				return a[0]-b[0];//小根堆比较器
    			}
    		});
    		int n=matrix.length;//n行
    		for(int i=0;i<n;i++) {
    			pq.offer(new int[] {matrix[i][0],i,0});//把左端最小值加入最小堆
    		}
    		for(int i=0;i<k-1;i++) {
    			int[] now=pq.poll();
//取出栈顶元素,也就是最小值,以及最小值所在的位置,取出k-1次,下次取出的就是第k小的值
    			if(now[2]!=n-1) {
//如果不是这一行最后一位的端点值,就把这一行的下一位放入最小堆中
//每次弹出候选人中的最小值,然后把上次弹出的候选人的右边一个补进来,就能保证全局最小值在候选人列表中产生
    				pq.offer(new int[] {matrix[now[1]][now[2]+1],now[1],now[2]+1});		
    			}
    		}
    		return pq.poll()[0];
    	}

//方法三,二分查找
//由题目给出的性质,这个矩阵的元素是从左下到右下递增的(假设矩阵左上角为matrix[0][0])
//整个数组中matrix[0][0]为最小值,matrix[n-1][n-1]为最大值,
//
//初始位置在matrix[n-1][0]左下角,设当前位置为matrix[i][j],若matrix[i][j]<=mid,则将当前所在列的不大于mid的数的数量
//即i+1累加到答案中,并向右移动,否则向上移动
//不断移动直到走出格子为止

public int kthSmallest03(int[][] matrix,int k) {
	int n=matrix.length;
	int left=matrix[0][0];//最小值
	int right=matrix[n-1][n-1];//最大值
	while(left<right) {
		int mid=left+((right-left)>>1);
		if(check(matrix,mid,k,n)) {
			right=mid;
		}else {
			left=mid+1;
		}
	}
	return left;
}
public boolean check(int[][] matrix,int mid,int k,int n) {
	int i=n-1;
	int j=0;
	int num=0;
	while(i>=0&&j<n) {
		if(matrix[i][j]<=mid) {
			num+=i+1;//比mid小的多加i+1个,看下一列
			j++;
		}else {
			i--;//向下移动变小,看这一列有多少比mid小的
		}
	}
	return num>=k;//比mid小的数多于k个,mid减小
}

668-Kth Smallest Number in Multiplication Table

//668乘法表中第k小的数
//给定高度m,宽度n的一张m*n乘法表,需要返回表中第k小的数字
/输入: m = 3, n = 3, k = 5
输出: 3
解释:
乘法表:
1 2 3
2 4 6
3 6 9
/

//二分搜索,当且仅当乘法表中存在小于或等于k,enough(x)才为真。
//通俗的说,enough(x)描述了x是否足够大可以成为乘法表中第k小的值。
//然后,对于A,当x>=A,enough(x)为true,x<A,enough(x)为false
//二分搜索中,循环不变量enough(hi)=true,开始时,enough(mn)=true,并且每当设置hi时
//都将其设置为enough(hi)=true,开始时,enough(m
n)=true,并且每当设置hi时,都将其设置为enough

//正确尝试
//需要在第i行寻找一个大于num的个数,只要min(num/i,n)
//其中(i是这一行的行号,n是矩阵的行列数)
//num/i代表如果num也在第i行它存在的列数,
//所以只要取最小值就是第i行不大于num的个数
/*1 2 3
2 4 6
3 6 9
/
//第2行不大于4的个数:min(不大于4/第二行2,矩阵的列数3)=2个(就是2,4)
//确定这个乘法表中不大于num的个数,就将每一行不大于num的个数累加即可
//初始化left=1,right=n
m+1,mid=(left+right)/2,在m,n中寻找不超过mid的个数

public boolean enough(int x,int m,int n,int k) {
	int count=0;
	for(int i=1;i<=m;i++) {//m行
		count+=Math.min(x/i, n);//第i行寻找不大于x的数
	}
	return count>=k;
}
public int findKthNumber01(int m,int n,int k) {
	int lo=1,hi=m*n;
	while(lo<hi) {
		int mi=lo+(hi-lo)/2;
		if(!enough(mi,m,m,k))lo=mi+1;
		else hi=mi;//最后肯定是lo=mi=hi-1
	}
	return lo;
}

//先构造乘法表,然后再去搜索,这样不行,m,n的取值可能非常大,非常耗内存
//m,n的乘法表中取值范围为[1,mn],可以使用二分搜索
//失败的尝试
//向右向下增长的矩阵找第k小的数字,自己生成m
n的矩阵

public int findKthNumber(int m,int n,int k) {
	//最小堆
	PriorityQueue<int[]> pq=new PriorityQueue<int[]>(new Comparator<int[]>() {
		public int compare(int[] a, int[] b) {
			// TODO 自动生成的方法存根
			return a[0]-b[0];//小顶堆中存放数组,第一位放值,第二三位放位置
		}
	});
	for(int i=0;i<m-1;i++) {
		//pq.offer(new int[] {})
	}
	return k;
}

719-Find K-th Smallest Pair Distance (复杂二分查找问题的精髓)

//给定一个整数数组,返回所有数对之间的第k个最小距离。
//一对(A,B)的距离被定义为A和B之间的绝对差值

//由于第k小的距离一定在[0,W=max(nums)-min(nums)]中,我们在这个区间上进行二分。
//对于当前位置guess,统计距离小于等于guess的距离对数量,并根据与k的关系整理上下界
//定义函数possible(guess)为真,当且仅当距离小于等于guess的距离对数量比k大和k相等
//if possible true,right=mid,因为可能等于,所以边界不能去掉
//if possible false,left=mid+1,返回left,因为最后left=mid=right-1

//二分查找+双指针
//使用双指针来计算除所有小于等于guess的距离对数目.right通过循环逐渐递增,
//left在每次循环中被维护,使得它满足nums[right]-nums[left]<=guess且最小
//对于nums[right],以它为右端的满足距离小于等于guess的距离对数目即为right-left
//我们在循环中对这些right-left进行累加,就得到了所有小于等于guess的距离对数目

public int smallestDistancePair(int[] nums,int k) {
	Arrays.sort(nums);
	int lo=0;
	int hi=nums[nums.length-1]-nums[0];
	while(lo<hi) {
		int mi=(lo+hi)/2;
		int count=0,left=0;
		for(int right=0;right<nums.length;++right) {
			while(nums[right]-nums[left]>mi)left++;
			count+=right-left;
		}
		//count=number of pairs with distance<=mi
		if(count>=k)hi=mi;//有可能是=k所以要保留
		else lo=mi+1;
	}
	return lo;
}

/*题目中条件里写的len(nums)<=10000,也就是说数据量为10的4次方,
 *那么时间复杂度上O(n2)基本是超时的,O(n)基本不可能,所以想到O(nlogn),
 *顺势也就能基本猜出用的是二分法,下面是二分法的具体实现
*/
//对数组进行排序,找出最大距离对,nums[len-1]-nums[0],第k小的距离对一定在最大和最小距离对之间
//有了范围我们可以使用二分查找,通过while循环将所以<=mid的距离对的个数算出来,加到count中
//如果count>=k,那么right=mid,如果count<k,那么left=mid+1;
public int smallestDistancePair01(int[] nums,int k) {
	Arrays.sort(nums);
	int len=nums.length;
	int low=0;
	int high=nums[len-1]-nums[0];
	while(low<high) {
		int mid=low+(high-low)/2;
		int count=0;
		int left=0;
		for(int right=0;right<len;right++) {
			while(nums[right]-nums[left]>mid)left++;
			count+=right-left;
		}
		if(count>=k) {
			high=mid;
		}else {
			low=mid+1;
		}
	}
	return low;	
}

786-K-th Smallest Prime Fraction (373,378,668,719类似n*n的矩阵每行和每列都排序的k-th问题的解题思路)

//786

/*给你一个按递增顺序排列的数组arr和一个整数k,数组arr和一个整数k,数组arr由1和若干素数组成,且其中所有整数互不相同

  • 对于没对满足0<i<j<arr.length的i和j,可以得到分数arr[i]/arr[j].那么第k个最小的分数是多少,以长度为2的整数数组返回答案
  • answer[0]==arr[i]且answer[1]==arr[j]*/

//分数的比较不能用int,应该用float,或者比分子分母
//二分法,范围是从arr[0]/arr[n-1]到1

//under(x)求解小于x的分数数量,这是一个关于x的单调增函数,因此可以通过二分查找求解
//使用二分查找找出一个x,使得小于x的分数恰好有K个,并且记录其中最大的一个分数
//under(x)函数有两个目的:返回小于x的分数数量以及小于x的最大分数,这是为了找到第k小的。
//在under(x)函数中使用滑动窗口的方法:对于每个primes[j],找出最大的i使得primes[i]/prime[j]<x
//随着j和primes[j]的增加,i也会随之增加。

public int[] kthSmallestPrimeFraction(int[] primes,int K) {
	
	double lo=0,hi=1;
	int[] ans=new int[] {0,1};
	
	while(hi-lo>1e-9) {//1*10的-9次方
		double mi=lo+(hi-lo)/2.0;
		int[] res=under(mi,primes);
		if(res[0]<K) {//res中第一位放小于mi的分数个数
			lo=mi;
		}else {
			ans[0]=res[1];
			ans[1]=res[2];
			hi=mi;
		}
	}
	return ans;
}

public int[] under(double x,int[] primes) {
	int numer=0,denom=1,count=0,i=-1;
	for(int j=1;j<primes.length;++j) {
		while(primes[i+1]<primes[j]*x)++i;// i+1/j < x, j固定,i+1前面的也会小于x,j处共有i+1个
		count+=i+1;
		//放最大的那个分数i/j > numer/denom
		if(i>=0&&numer*primes[j]<denom*primes[i]) {
			numer=primes[i];
			denom=primes[j];
		}
	}
	return new int[] {count,numer,denom};
}

4-Median of Two Sorted Arrays (难题,理解即可)
//给定两个大小分别为m和n的正序(从小到大)数组nums1和nums2.找出并返回这两个正序数组的中位数

//两个数组的中位数,需要先合并这两个数组,然后根据奇数还是偶数,返回中位数

public double finMedianSortedArrays(int[] nums1,int[] nums2) {
	
	int[] nums;
	int m=nums1.length;
	int n=nums2.length;
	nums=new int[m+n];
	if(m==0) {
		if(n%2==0) {
			return(nums2[n/2-1]+nums[n/2])/2.0;
		}else {
			return nums2[n/2];
		}
	}
	if(n==0) {
		if(m%2==0) {
			return(nums2[m/2-1]+nums[m/2])/2.0;
		}else {
			return nums2[m/2];
		}
	}
	int count=0;
	int i=0,j=0;//i指向nums1,j指向nums2
	//把没放完的继续放进去
	while(count!=(m+n)) {
		if(i==m) {
			while(j!=n) {
				nums[count++]=nums2[j++];
			}
			break;
		}
		if(j==n) {
			while(i!=m) {
				nums[count++]=nums1[i++];
			}
			break;
		}
		if(nums1[i]<nums2[j]) {
			nums[count++]=nums[i++];
		}else {
			nums[count++]=nums[j++];
		}
	}
	if(count%2==0) {
		return (nums[count/2-1]+nums[count/2])/2.0;
	}else {
		return nums[count/2];//时间复杂度,空间复杂度嗾使m+n
	}
}

//我们不需要将两个数组真的合并,只需要找到中位数在哪里就可以了
//思路是写一个循环,然后里边判断是否到了中位数的位置,如果到了就返回结果
//把奇数偶数的情况合并一下,用len表示合并后数组的长度,如果是奇数,我们需要知道第(len+1)/2
//如果是偶数,需要知道第len/2和len/2+1个数,也需要遍历len/2+1次。遍历的话,奇数偶数都是len/2+1次

//返回中位数,奇数返回最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历结果
//用两个变量left和right,right保存当前循环的结果,每次循环前将right的值赋给left,这样最后一次循环的时候,
//left将得到right的值,right更新为最后一次的结果

public double findMedianSortedArrays(int[] A,int[] B) {
	
	int m=A.length;
	int n=B.length;
	int len=m+n;
	int left=-1,right=-1;
	int aStart=0,bStart=0;
	for(int i=0;i<=len/2;i++) {//只用取前len/2+1位数就可以了,也就是从0,1,2...len/2
		left=right;
		if(aStart<m&&(bStart>=n||A[aStart]<B[bStart])) {
			right=A[aStart++];
		}else {
			right=B[bStart++];
		}
	}
	if((len&1)==0)//判断奇偶,偶数输出0,奇数输出1.
		return (left+right)/2.0;
	else
		return right;
	
}//时间复杂度O(m+n),空间复杂度o(1)

//上面的方法都无法达到O(log(m+n)),很明显需要用二分法
//求中位数其实就是求第k小数的一种特殊情况,
//解法2中,我们一次遍历就相当于去掉一个不可能是中位数的一个值,也就是一个一个排除。
//由于数列是有序的,我们可以一半半的排除。假设要找第k小数,我们可以每次循环排除掉k/2个数。
//比较两个数组的第k/2个数字,如果k是奇数,向下取整。
//也就是比较第3个数字,上边数组中的4和下边数组中的3,如果哪个小,
//就表明该数组的前k/2个数字都不是第k小数字,把这些数去掉,新的数组继续进行比较
//去掉的数就一定是前面的,所以新的数组中找到第k-k/2小的数字就好了
//每次都是取k/2的数字比较,有时候回遇到数组长度小于k/2的时候,这时把箭头指向它的末尾
//由于上面的数组已经空了,只需要返回别的数组的数字就可以了
//从上边可以看到,无论第奇数个还是偶数个数字,对算法无影响,在算法进行中,k的值都有可能从奇数变偶数
//最终都会变为1,或者一个数组空了
//所以我们采用递归的思路,防止数组长度小于k/2,所以每次比较min(k/2,数组长度较小的那个),把小的那个
//对应的数组的数字排除,将两个新数组进入递归,并且k要减去排除的数字的个数。递归的出口是当k=1,或一个数组空了

//为什么奇数偶数个对这个算法没有影响呢,如果是最后求两个中间数的和除以2怎么办?!!!!!!!
//求两次

public double findMedianSortedArrays01(int[] nums1,int[] nums2) {
	int n=nums1.length;
	int m=nums2.length;
	int left=(n+m+1)/2;
	int right=(n+m+2)/2;
	//将奇数和偶数合并,如果是奇数,会求两次同样的k
	return (getKth(nums1,0,n-1,nums2,0,m-1,left)+getKth(nums1,0,n-1,nums2,0,m-1,right))*0.5;
}
private int getKth(int[] nums1,int start1,int end1,int[] nums2,int start2,int end2,int k) {
	int len1=end1-start1+1;
	int len2=end2-start2+1;
	//让len1的长度小于len2,这样就能保证如果有数组空了,一定是len1
	if(len1>len2)return getKth(nums2,start2,end2,nums1,start1,end1,k);
	//如果len1<len2,把他们对调
	if(len1==0)return nums2[start2+k-1];
	if(k==1)return Math.min(nums1[start1],nums2[start2]);
	
	int i=start1+Math.min(len1,k/2)-1;//第6位的索引是5
	int j=start2+Math.min(len2,k/2)-1;
	if(nums1[i]>nums2[j]) {
		return getKth(nums1,start1,end1,nums2,j+1,end2,k-(j-start2+1));
	}else {
		return getKth(nums1,i+1,end1,nums2,start2,end2,k-(i-start1+1));
	}
}//时间复杂度O(log(m+n)),用到了递归是尾递归,空间复杂度O(1)

//中位数,切割法
//一个长度为m的数组,有0-m个位置可以切
//把AB数组分别在i,j位置进行切割,将i的左边和j的左边组合成左半部分,
//j的右边和i的右边组合成右半部分

//i+j = m-i + n-j , 也就是 j = (m+n)/2 - i
//左半部分最大的值小于等于右半部分最小的值,max(A[i-1],b[j-1])<=min(A[i],B[j])
//那么中位数就是(左半部分最大值+右半部分最小值)/2
//A数组和B数组的总长度是奇数时,如果能够保证左半部分的长度比右半部分大1,
//左半部分的最大值小于等于右半部分的最小值的,那么中位数就是左半部分最大值
//上边的第一个条件可以合并我j=(m+n+1)/2-i,因为如果m+n是偶数,由于我们取的是int值,
//所以加1也不会影响结果,当然,由于0<=i<=m,为了保证0<=j<=n,必须保证m<=n


这个代码没看懂,,,,,,,,,,,,,,,,,,,,,!!!!!!



public double findMedianSortedArrays02(int[] A,int[] B) {
	int m=A.length;
	int n=B.length;
	if(m>n) return findMedianSortedArrays(B,A);//保证m<=n
	int iMin=0,iMax=m;
	while(iMin<=iMax) {
		int i=(iMin+iMax)/2;
		int j=(m+n+1)/2-i;
		if(j!=0&&i!=m&&B[j-1]>A[i]) {//i需要增大
			iMin=i+1;
		}else if(i!=0&&j!=n&&A[i-1]>B[j]) {//i需要减小
			iMax=i-1;
		}else{//达到要求,并且将边界条件列出来单独考虑
			int maxLeft=0;
			if(i==0) {maxLeft=B[j-1];}
			else if(j==0) {maxLeft=A[i-1];}
			else { maxLeft = Math.max(A[i-1], B[j-1]); }
            if ( (m + n) % 2 == 1 ) { return maxLeft; } // 奇数的话不需要考虑右半部分

            int minRight = 0;
            if (i == m) { minRight = B[j]; }
            else if (j == n) { minRight = A[i]; }
            else { minRight = Math.min(B[j], A[i]); }

            return (maxLeft + minRight) / 2.0; //如果是偶数的话返回结果
		}
	}
	return 0.0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值