高级农民
- 积分
- 1633
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-3-1
- 最后登录
- 1970-1-1
|
我用同样的方式写了比较简单的例子,给一个数组arr,给了一个目标值x
- public int get(int[] arr, int x) {
- int lo = 0, hi = arr.length-1;
- while (lo < hi) {
- int mid = (lo + hi) / 2;
- if (arr[mid] >= x) // 需要满足的条件
- hi = mid;
- else
- lo = mid + 1;
- }
- return arr[lo];
- }
复制代码
这个代码实现的效果是,找到数组中大于等于x的最小下标,抽象的说就是满足条件的最小下标(条件就是if中的判断条件),对不同的题目if里面的条件肯定不一样
这是为什么呢?
lo,hi在这段代码里表示的是[lo, hi]闭区间,如果下标mid满足条件让hi=mid,这就说明满足条件的会保留在区间里,不满足的逐步被清出去。
1.当数组只有一个元素满足条件时,必然最后lo会和hi碰头,剩下的就是所求元素。
2.当有多个满足条件时,会出现[lo, hi]里面全都满足条件,这时候mid取(lo+hi)/2,因为此时mid也满足条件,会使得hi=mid,右边界逐渐逼近左边界,最后出现hi==lo+1时,求得mid=(lo+hi)/2=lo,使得hi=mid=lo,跳出循环。所以最后hi会指向最小下标
3.当没有元素满足条件,lo不断变大,直到和hi碰头,但是所指元素并不满足条件
这个写法最后lo是保证了和hi相等才会退出,另外为了最后lo指向的位置记得多加个判断来排除3的情况
现在回到LC658这个题目,首先我觉得这个题目能想到用二分做是这道题比较刁钻的地方,题目让我们求离x最近的k个数,当出现tie的时候取小下标。而正好上面已经解释过了这个写法能实现满足条件的最小下标,所以剩下就只用整明白解答里的if条件。我们可以用一个下标窗口[l, l+k)来在数组上滑动确定答案,并且保证l越小越好。
下面的“距离”是指差值的绝对值。
因为要确定最接近的K个,也就是说窗口内的数和x的“距离”要小于等于窗口外的数到x的“距离”,然后因为要保证l越小,则arr[l]肯定就是窗口内"距离"最大的数了,所以要拿arr[l]和窗口外的数比,用二分求边界l。二分里面mid就是要求的边界,用mid和窗口外的mid+k做比较,只要arr[mid]的"距离"小于等于arr[mid+k]的“距离”就是满足答案要求的窗口,所以条件部分我们可以写
- while (l < r) {
- int mid = (l + r) / 2;
- if (x - arr[mid] <= arr[mid+k] - x) r = mid;
- else l = mid+1;
- }
复制代码
即高票答案的if (x - arr.get(mid) > arr.get(mid+k) - x) lo = mid + 1;
最后对于二分写法开始的时候自己见过蛮多的,之前也各种懵逼,最后还是找了一个自己理解的背了下来(就是高票写的这个当模版用了)。对于不同的写法没好坏之分,但是一定要确保知道下标最后指的位置是什么含义,要不然很容易写跪掉。。。另外和前几层的同学说的一样,可以用l=x, r=x+1来检查自己二分的写法会不会出现死循环之类的(这个是在另一个地方学到的,里面还有更深入的二分讨论,链接见此)
|
|