昨天看了代码随想录Carl哥的数组二分,记录一下自己的思考。
在Carl哥的pdf里,提了两种划分区间。
一种是左闭右闭,即[left, right],对应以下两点:
- while(left <= right),之所以是<=,而不是<,是因为左闭右闭的情况下,left==right是有意义的;
- if(arr[mid] > target),r=mid-1,因为接下来要找的区间是[left, mid-1]。
第二种是左闭右开,即[left, right),对应以下两点:
- while(left < right),<是因为左闭右开的情况下,left不能等于right;
- if(arr[mid] > target),r=mid,因为接下来要找的区间是[left, mid)。
但是,但是,Carl哥的pdf里的例题比较简单,如果因此忽略这两种方式的代码区别的话,在做题的时候又会一脸迷茫了。
通过比较两种方式的代码,发现:
- 左闭右闭,r=mid-1,l=mid+1;
- 左闭右开,r=mid, l = mid + 1;
然后,就是做pdf中推荐的leetcode题。
这里,我推荐做 34. 在排序数组中查找元素的第一个和最后一个位置。
记录一下我根据这一思想做这道题的思路。
- 首先,查找元素的第一和最后一个位置,就是找元素的左右边界,都可以通过二分查找来找到;
- 先找左边界:
int start = -1, end = -1; // 记录左右边界
int l = 0;
int r = nums.length - 1; // 这里采用左闭右闭方式,[left, right]
int mid = l + (r - l) / 2;
while (l <= r) { // 左闭右闭,l <= r
if (nums[mid] > target) {
r = mid - 1; // 左闭右闭,r = mid - 1
} else if (nums[mid] < target) {
l = mid + 1; // 左闭右闭,l = mid + 1
} else {
r = mid - 1; // r = mid - 1,因为要在左边区间[left, mid-1]里寻找
start = mid; // 记录左边界
}
}
- 再找右边界:
l = 0;
r = nums.length - 1; // 仍然采用左闭右闭的方式
mid = l + (r - l) / 2;
while (l <= r) { // 左闭右闭
if (nums[mid] > target) {
r = mid - 1; // 左闭右闭
} else if (nums[mid] < target) {
l = mid + 1; // 左闭右闭
} else {
l = mid + 1; // l = mid + 1,因为要在右区间[mid + 1, right]里找
end = mid; // 记录右边界
}
}
- 结合左右边界,判断返回值:
if (start == -1 || end == -1) // start、end还是初始值,说明数组中没有等于target的数
return new int[]{-1, -1};
return new int[]{start, end};
关于左闭右开、左闭右闭两种方式,在做题时都可以采用,在我看来结果上没有区别。
切记注意的是:一经选用某一方式,一定要始终保证区间定义不变!
1. 左闭右闭,r = nums.length - 1,while(l <= r),r = mid - 1,l = mid + 1。
2. 左闭右开,r = nums.length,while(l < r),r = mid,l = mid + 1。