前言
刷LeetCode的时候,发现能实现二分查找的写法有很多,有很多帖子讨论不同的边界问题会出现不同的问题:
- 死循环问题
- 仅一部分的测试用例可以通过
为了避免以上问题,收敛下题目,记住解题思路。
2. 例题
2.1 最简单的二分查找算法题
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
// [left,left] 和 [right,right] 都代表区间只有一个元素,一旦出现了[right,left] 代表程序尽最大努力找target,依旧还是找不到
while (left <= right) {
int mid = (right - left) / 2 + left;
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
// 目标在 mid 的左边 移动右指针, 舍弃右边元素
right = mid - 1;
} else if (target > nums[mid]) {
// 同理,移动左指针
left = mid + 1;
}
}
return -1;
}
2.2 第一个错误的版本
public int firstBadVersion(int n) {
int left = 1;
int right = n;
int mid = 0;
while (right > left) {
// 向下取整
// [A] left = 1, right = 1, (1-1) + 1 = 1
// [A, B, C] left = 1, right = 3 , (3-1) / 2 + 1 = 2
// [A, B, C, D] left = 1, right = 4 , (4-1) / 2 + 1 = 2
mid = (right - left) / 2 + left;
if (isBadVersion(mid)) {
// 答案落在 [left, mid] 中
right = mid;
} else {
// 答案落在 [mid+1, right] 中
left = mid + 1;
}
}
// right == left 指针相遇, 则待考察元素只剩下一个。由题意得, 序列中必然出现错误版本, 则该元素即为所求。
return left;
}
2.2.1 理解为什么是 left = mid + 1, 而 right = mid
当序列中有三个元素, (下标从0开始)
left == 0, right == 2
[true, false, false]
mid == 0 + (0 + 2) / 2 = 1
往 mid 的左边找可以找到答案
left == 0, right == 1 (为什么不是 mid - 1 而是 mid) 因为当前Mid可能是答案,不要漏掉了。
[true, true, false]
往 mid 的右边找可以找到答案
left == 1, right == 2 (为什么可以是 mid + 1) 因为当前Mid不可能是答案 (答案是要找false节点,而不是true节点)
2.3 有序数组的平方
public int[] sortedSquares(int[] nums) {
// 越小的值,平方后会变得越大
int left = 0;
int right = nums.length - 1;
// 双指针, 加入结果集的指针, 继续移动
int[] ans = new int[nums.length];
// ans 期望是从小到大排列, 从后往前一次填充候选者
for (int i = ans.length - 1; i >= 0; i--) {
// 对比两个指针元素
int leftValue = nums[left] * nums[left];
int rightValue = nums[right] * nums[right];
if (leftValue > rightValue ) {
ans[i] = leftValue;
left++;
} else {
ans[i] = rightValue;
right--;
}
}
return ans;
}
2.3.1 理解边界条件
笔者写的答案是用 ans 的数组大小作为边界条件。LeetCode 官方题解写的是 i <= j 为边界,现在理解下这种方式的定义。
[1, 2, 3] 序列中 left = 0, right = 2, 选举后3加入ans,序列变成 [1,2]
[1, 2] 序列中 left = 0, right = 1, 选举后2加入ans, 序列变成[1]
[1] 序列中, left = 0, right = 0, 选举后1加入ans,序列变成[]
程序结束
结论是,两指针相遇后,还需要把最后一个元素加入序列,所以边界取等号
2.4 搜索插入位置
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid;
while (left <= right ) {
mid = left + (right - left ) / 2;
// nums中的元素不重复,如果 target 跟 nums 中的X一样,则X所在的下标即为待插入位置
// 如序列 1 3 4 5; 插入 3
if (target == nums[mid]) {
return mid;
} else if (target < nums[mid]) {
// 待插入的元素应该在 mid 的左边 移动右指针, 舍弃右边元素
right = mid - 1;
} else if (target > nums[mid]) {
// 同理,移动左指针
left = mid + 1;
}
}
// 二分没有找到插入的位置, 那么target 一定小于 left, 从left 位置插入既能满足题解。
return left;
}
- 摘一段解析 为什么最后可以返回 left

3.1 待考察元素的区间要覆盖全
2.1 和 2.2 中二分查找法,区间都是是 [left......mid-1] [mid,mid] [mid+1......right],其中mid为待考察元素。2.2 中left可以作为结果返回,是根据题目的语义决定的。

356

被折叠的 条评论
为什么被折叠?



