1 二分法学习笔记总结
参考
https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/
1.1 取中位数索引的方法
- 传统的方法
int mid = (left + right) /2
,在 left 和 right 比较大的时候, 两者相加很可能超过 int 类的最大值,即发现整型溢出; - 改进
int mid = left + (right - left) /2
- 最好的写法
int mid = (left + right) >>> 1
>>
, 右移时,丢弃右边指定位数,左边补上符号位;>>>, 无符号右移运算符
,丢弃右边指定的位数,左边补上 0 。所以对负数右移,可以变成正数;
1.2 循环条件
while (left <= right)
,退出循环时,要考虑返回 left 和 right;
2 二分查找模板思想
2.1 循环条件
while (left < right)
, 在退出循环的时候,一定有left == right
,这时返回 left 或者 right 都可以;- 退出循环的时候还有一个数没有考虑,即退出循环之前索引 left 或者 索引 right 上的值?这点可以等到退出循环以后考虑,甚至有时不用考虑,就确定它是目标数;
2.2 左、右边界值
- 如果左、右边界值不包括目标数,会出现错误;
- 如果 left 和 right 表示的是数组的索引,要考虑“索引有效性的问题”,即 索引是否越界;
2.3 中位数索引
- 中位数先写
int mid = (left + right) >>> 1
,然后根据分支的情况,再做调整;
当数组个数是偶数的时候:
int mid = left + (right -left) / 2
, 得到左中位数的索引int mid = left + (right -left + 1) / 2
, 得到右中位数的索引
当数组个数是奇数的时候,二者都能得到最中间的元素
int mid = left + (right -left) / 2
等价于int mid = (left + right) >>> 1
int mid = left + (right -left + 1) / 2
等价于int mid = (left + right + 1) >>> 1
2.4 逻辑分支
- 先写逻辑上最容易想到的分支,这个分支逻辑通常是排除中位数的逻辑;
- 例如,求 x 的平方根时,如果一个数的平方小于或者等于 x, 那么这个数可能是也可能不是 x 的平方根;但是可以肯定的是,如果一个数的平方大于 x ,这个数肯定不是 x 的平方根;
- 所以先写 “容易想到”的分支,排除中位数之后,通常另一个分支就不排除中位数,不必具体考虑另一个分支的逻辑的具体含义;
- 在循环内只写 2 个分支,一个分支排除中位数,另一个分支不排除中位数,循环中不单独对中位数做判断;
- “夹逼法”,没有必要在每一轮循环开始前单独判断当前中位数是否是目标元素,因此分支数少了一支,代码执行效率高
- 可能出现以下 2 种模板
2.5 左、右中位数的选取
- 根据分支逻辑选择中位数的类型,选择的标准是避免出现死循环
当候选区间只剩下两个元素的时候,进入中位数是左边界的逻辑,由于左边界不收缩,下一次循环还选左中位数,左边界还不收缩,如此下去,进入了死循环。解决方案:使用右中位数;
当候选区间只剩下 2 个元素的时候,一旦进入中位数是右边界的逻辑,由于右边界不收缩,下一次循环还选右中位数,右边界还不收缩,如此下去,进入了死循环;解决方法:换成左中位数
2.6 退出循环的处理
- 退出循环的时候,可能需要对“夹逼法”剩下的那个数单独做一次判断;
- 如果能确定候选区间里一定有目标元素,则不需要这一步。例如:x 的平方根一定在 [0,x] 内,所以退出
while ( left < right)
后,不必单独判断 left 或者 right 是否符合; - 如果不确定候选区间里是否存在目标元素,则需要单独做一次判断。因为目标数可能不在数组中,当候选区间变成一个数的时候,需要单独判断这个数是否为目标数。
3 死循环代码测试
public static int testSqrt(int x) throws InterruptedException {
if (x == 1 || x == 0) {
return x;
}
int left = 1;
int right = x / 2;
while (left < right) {
Thread.sleep(1000);
System.out.println("left = " + left + ",right= " + right);
// 在分支左区间不发生收缩时,中位数要选右中位数
int mid = (left + right) >>> 1;
System.out.println("mid = " + mid);
int square = mid * mid;
if (square > x) {
System.out.println("进入 right = mid -1 分支");
right = mid - 1;
} else {
System.out.println("进入 left=mid 分支");
left = mid;
}
System.out.println();
}
return left;
}
public static void main(String[] args) throws InterruptedException {
int res = testSqrt(9);
System.out.println(res);
}
来源:CSDN
作者:一角残叶
链接:https://blog.csdn.net/u012292754/article/details/100060293