2022年5月15日
目录
使用场景
寻找一个数
寻找左侧边界
寻找右侧边界
二分查找框架
代码
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
技巧:将所有情况用else if写清楚,展现所有细节。
寻找一个数(基本二分搜索)
题目:搜索一个数,如果存在,返回其索引,否则返回-1。
代码框架:
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
问题:
-
为什么是while(left <= right)?
因为在本段代码中,区间是两端都闭区间
[left,right](right = nums.length - 1;)
,而不是左闭右开区间[left,right)
。 -
为什么判断后是
left=mid+1,right=mid-1
?因为本算法的搜索区间是闭区间,即
[left,right]
,当发现索引mid不是要找的target时,要去搜[left,mid-1]或[mid+1,right]
,因为mid已经搜索过了,要从搜索区间中去除。
寻找左侧边界
代码框架:
int left_bound(int[] nums, int target) {
if (nums.length == 0) return -1;
int left = 0;
int right = nums.length; // 注意
while (left < right) { // 注意
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid; //收紧右边界,向左收缩
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid; // 注意
}
}
return left;
}
问题:
-
为什么是while(left < right)?
答:因为代码中,
right = nums.length;
,所以每次搜索区间都是[left,right)
。 -
为什么
left=mid+1,right=mid
?答:因为搜索区间是
[left,right)
左闭右开,所以当nums[mid]
被检测之后,下一步的搜索区间应该去掉mid,然后分割成两个区间,即[left,mid)和[mid+1,right)
。 -
是不是可以使用两边都闭的搜索区间?
代码如下:
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) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.length || nums[left] != target) return -1; return left; }
寻找右侧边界
代码模板:
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 - left) / 2;
if (nums[mid] == target) {
left = mid + 1; // 注意
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1; // 注意
}
问题:
-
为什么最后返回left - 1 而不是返回left?为什么不返回right?
答:while循环的终止条件是
left==right
,所以left和right一样,也可以返回right-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) { right = mid - 1; } else if (nums[mid] == target) { // 这⾥改成收缩左侧边界即可 left = mid + 1; } } // 这⾥改为检查 right 越界的情况,⻅下图 if (right < 0 || nums[right] != target) return -1; return right; }
逻辑梳理
-
当我们初始化
right = nums.length - 1
时,就决定了搜索区间是[left, right]
,也决定了循环while (left <= right)
和left = mid+1、right = mid-1
。 -
当我们初始化
right = nums.length
时,就决定了搜索区间是[left, right)
,也决定了循环while (left < right)
和left = mid+1、right = mid
。 -
找target的最左侧索引时,
nums[mid] == target
时要收紧右侧边界。