二分搜索算法总结
算法框架
二分查找并不简单,Knuth 大佬(发明 KMP 算法的那位)都说二分查找:思路很简单,细节是魔鬼。很多人喜欢拿整型溢出的 bug 说事儿,但是二分查找真正的坑根本就不是那个细节问题,而是在于到底要给 mid
加一还是减一,while 里到底用 <=
还是 <
。
二分查找根据搜索区间分类有两种情况:
- 「搜索区间」是 [left, right] :
right = nums.length - 1
while (left <= right)
left = mid+1 和 right = mid-1
- 「搜索区间」是 [left, right)
right = nums.length
while (left < right)
left = mid+1 和 right = mid
二分查找根据搜索目的分类有三种情况:
- 找到一个 target 的索引
当 nums[mid] == target 时可以立即返回
- 找到 target 的最左侧索引
当 nums[mid] == target 时不要立即返回 而要收紧右侧边界以锁定左侧边界
- 找到 target 的最右侧索引
当 nums[mid] == target 时不要立即返回 而要收紧左侧边界以锁定右侧边界
🤣当搜索区间是闭区间时,在寻找边界的时候,由于返回条件是left = mid+1 和 right = mid-1 ,可能会在特殊的输入值下出现数组越界的情况,需要排除。
例如下图:
算法模板:
注意左右边界收缩时的判断条件;
算法一直使用左闭右闭区间,所以左右区间收缩时要+1或者-1;
寻找左右区间的时候由于终止循环不再是left>right或者==target
而是只有left>right
所以需要在返回时进行判断。
int binary_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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}
int left_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) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定左侧边界
right = mid - 1;
}
}
// 最后要检查 left 越界的情况
if (left >= nums.length || nums[left] != target)
return -1;
return left;
}
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) {
right = mid - 1;
} else if (nums[mid] == target) {
// 别返回,锁定右侧边界
left = mid + 1;
}
}
// 最后要检查