二分法查找元素位置

题目描述(leetcode35):
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。
二分法查找元素位置_第1张图片
我的代码:

    public int searchInsert(int[] nums, int target) {
        if(nums==null) return -1; 
        if(nums.length==0) return 0;
        int start=0;
        int end=nums.length; //要点4
        while(start<end){ //要点1
            int mid=(start+end)>>>1;  //要点2
            if(nums[mid]>=target){
                end=mid;
            }else{
                start=mid+1;  //要点3
            }
        }
        return start;//要点1
    }

二分法的模型。
参考题解:
https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
几个问题?

1.要点一:while()循环条件怎么写?

while循环是写while(start<=end)还是while(start
他们的区别无非就是退出循环后,返回值的问题,第一种<=,最后退出之后left>right,拿不准的话还需要判断一次。但是while(left

2.要点二:中间怎么取?
2.1首先讨论三种写法(都用移位代替了/2)
  1. mid=(start+end)>>1;
  2. mid=start+(end-start)>>1;
  3. mid=(start+end)>>>1;

第一种写法存在溢出问题,第二种写法由于start和end都是索引,所以这里不会有溢出情况(如果只看这个式子,如果start是个非常小的负数,那还是可能溢出的)。
第三种情况是最推荐的写法,start+end虽然可能溢出,但溢出无非就是符号位由0变成了1(变成负数),但通过无符号右移,这个负数位就变成了正常的数位,最高位补零,又成了正数,所以结果是正确的。
举个例子(我们假设int只有4bit,第5位是符号位)
二分法查找元素位置_第2张图片
在java中,Arrays提供的binarySearch方法也是通过方式三实现的。
二分法查找元素位置_第3张图片
_____________________________

2.2中位数怎么选

此外,这里还有个问题,就是当它是偶数的时候,中位数有两个,左中位数(小),右中位数(大)。
这个问题处理不好,最后容易陷入死循环。(我之前经常遇到这个问题)
可以通过下面的方式来指定

  • mid=(start+end)>>>1; 左中位数(小)
  • mid=(start+end+1)>>>1; 右中位数(d大)
  • mid=start+(end-start)>>1; 左中位数(小)
  • mid=start+(end-start+1)>>1; 右中位数(d大)
    需要哪个,和要点三配合一下自己判断(最后只剩两个数的时候为了避免死循环带进去试一下就知道了)
    说的明白点,我觉得就是如果在要点三中mid要保留到左边界(start=mid),那中位数就选右中位数,如果mid保留到右边界(end=mid),那中位数就选左中位数。总之不能共边,否则就会出现start=mid,mid=start…的循环。
    参考leetcode69,需要考虑这个问题。
3.要点三:怎么更新start和end?

我们需要先明确最后找到的start和end一定是一小一大,待找的数一定在【start,end】闭区间中。
我们需要明确你要的是左分界还是右分界,就是假设最后区间【2,4】,待查找的数是3,那你的结果是要2(左分界)还是4(右分界)?
明确这个问题后,我们可以用排除法来考虑。二分本身就是每次排除一半的数。
假设我们要右分界,也就是比target略大的那个数,那当midtarget时我们无法排除mid到底是不是第一个比target大的,因此mid需要保留,我们只能更新end=mid;
同理,如果我们要左分界,也就是比target略小的那个数,

 while(start<end){ 
            int mid=(start+end)>>>1; 
            if(nums[mid]<=target){
                start=mid; //mid无法排除,因此mid保留
            }else{
                end=mid-1;  //mid>target,那mid一定不是结果,可以排除,end从mid-1开始就好。
            }
        }
4.要点四:起始的end怎么选取?
  1. end=nums.length-1; 常规做法
  2. end=nums.length;
    其实这个问题当更新一次end之后就没区别,那区别就在第一次。
    当【start,end】很大的时候基本也没多大区别。
    主要是当很小的时候,比如nums只有一个数,那第一种写法start=end=0;
    那进不去while循环就直接输出了。这样肯定不对啊。但是如果end=nums.length;那么start=0;end=1;就可以进去判断了。
最后还有问题:当start=end后就不进行最后一次判断了,是否漏掉了一个数?确定没有问题吗?

这个问题在我一开始给出的参考文章里讲的很好,需不需要再判断一次这个数,取决于你之前写的排除逻辑,如果你的候选区间里一定包含你要的,那最后就不用判断。
二分法查找元素位置_第4张图片
总之,这些要点有很多种组合方式,也可以像下面这样写,推荐文章一开头的写法。

	int start=0;
	int end=nums.length-1;
	while(start<=end) {
		...
	}
	//后处理:判断一下是返回start还是end——
	return start或者end;

推荐写法记住以下几点:

  1. int end=nums.length;
  2. while(start
  3. int mid=(end+start)>>>1;的写法
  4. 根据排除逻辑更新start,和end。并选择mid是int mid=(end-start)>>>1;还是int mid=(end-start+1)>>>1;
  5. 直接return start或者end;

你可能感兴趣的:(算法)