这是跟着代码随想录的顺序学习算法的第一天,学习一下才发现自己多菜=-=。
以下是学习题解时自己的一些理解与笔记,有错误欢迎指正与讨论。
二分法
参考相关链接:
笔记
二分法查找需要满足有序,且无重复元素。
1、注意防止溢出
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
2、位操作符
int middle = left + ((right - left) >> 1); // >> 位操作符 右移1位
3、关于 while
的终止条件和 right
的初始化之间的关系
while
是 <=
时,意味着终止条件应为 left == right + 1
,即为 左闭右闭,故初始化时 right = nums.size() - 1
。
while
是 <
时,意味着终止条件应为 left == right
,即为 左闭右开,故初始化时 right = nums.size()
。
因为 left
与 right
是逐渐靠近的,会首先达到left == right
,再进一步达到 left == right + 1
。
下面先考虑达到 left == right
时的情况,左闭右开的区间已不符合要求,即不会存在 [A, A)
的情况,需要终止循环;但此时左闭右闭区间仍然符合要求,故不需要终止循环。
下面再考虑达到 left == right + 1
时的情况,左闭右闭区间已经不符合要求,即不会存在 [A+1, A]
的情况,故此时才需要终止循环。
4、left = mid + 1
,right = mid - 1
,right = mid
因为 left
是闭的,而此时的 mid
是不可能取到的值,所以区间不能包含 mid
的存在,故left = mid + 1
,right
同理可得何时需要取 mid - 1
。
5、如何搜索左边界?
①左闭右开
//...
if (nums[mid] == target) {
right = mid;
}
//...
while (left < right) {
//...
}
// target 比所有数都大
if (left == nums.length) {
return -1;
}
// target 在搜索区间的左边(不一定取到)
return nums[left] == target ? left : -1;
第一段代码这样做能搜索左边界,因为此时选的区间形式为左闭右开,while终止条件为 left == right
,所以如果此时的 right
取到了左边界,则将会一直在左半部分进行循环搜索 target
,直到 left
收敛到 right
,或者 left
也取到了 target
。
②左闭右闭
//...
if (nums[mid] == target) {
right = mid - 1; //收缩右侧边界
}
//...
while (left < right) {
//...
}
// 检查出界情况
if (left >= nums.length || nums[left] != target){
return -1;
}
return left;
此时选的区间形式为左闭右闭,while终止条件为 left == right + 1
,故需要 right == mid - 1
。
两种方法完整代码如下:
// 左闭右开区间,搜索左侧边界
int left_bound(int[] nums, int target) {
int left = 0,right = nums.length;
// 搜索区间为 [left, right)
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 固定右侧边界
right = mid;
} else if (nums[mid] < target) {
// 此时搜索区间为[mid + 1, right)
left = mid + 1;
} else if (nums[mid] > target) {
// 此时搜索区间为 [left ,mid)
right = mid;
}
}
// 检查出界情况
if (left == nums.length || nums[left] != target) {
return -1;
}
return left;
}
// 左闭右闭区间,搜索左侧边界
int left_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
// 搜索区间为 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 收缩右侧边界
right = mid - 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
}
}
// 检查出界情况
if (left >= nums.length || nums[left] != target){
return -1;
}
return left;
}
6、搜索右边界
对 left
的更新必须是 left = mid + 1
,就是说 while
循环结束时,nums[left]
一定不等于 target
了,而 nums[left-1]
可能是 target
。
// 左闭右开区间,搜索右侧边界
int right_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] == target) {
// 收缩左侧边界
left = mid + 1;
} else if (nums[mid] < target) {
// 此时搜索区间为[mid + 1, right)
left = mid + 1;
} else if (nums[mid] > target) {
// 此时搜索区间为 [left ,mid)
right = mid;
}
}
// 检查 left 越界的情况
if (left == 0 || nums[left - 1] != target) {
return -1;
}
return left - 1;
}
// 左闭右闭区间,搜索右侧边界
int right_bound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 收缩左侧边界
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
}
}
// 检查 right 越界的情况
if (right < 0 || nums[right] != target){
return -1;
}
return right; // 终止条件为 left == right + 1
}