二分查找

对于寻找左右边界的二分搜索,常见的手法是使用左闭右开的搜索区间,但是为了方便记忆,全部统一为两端都闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
}
}
// 最后要检查 right 越界的情况
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

思路:

二分查找的时间复杂度为O(logN),我们只需要调用两次,分别查找左边界和右边界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
return {left_bound(nums, target), right_bound(nums, target)};
}
int left_bound(vector<int>& nums, int target){
int left = 0, right = nums.size() - 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{
//收缩右边界
right = mid - 1;
}
}
//检查出界情况
if(left >= nums.size() || nums[left] != target){
return -1;
}
return left;
}
int right_bound(vector<int>& nums, int target){
int left = 0, right = nums.size() - 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{
//收缩左边界
left = mid + 1;
}
}
//检查出界情况
if(right < 0 || nums[right] != target){
return -1;
}
return right;
}
};

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例:

输入: nums = [1,3,5,6], target = 5
输出: 2

输入: nums = [1,3,5,6], target = 2
输出: 1

思路:

前面我们讲过了数组中寻在目标元素重复的情况,现在是目标元素不存在的情况。

当目标元素target不存在数组nums中时,搜索左侧边界的二分搜索的返回值可以做以下几种解读:

  • 返回的这个值是 nums 中⼤于等于 target 的最⼩元素索引。
  • 返回的这个值是 target 应该插⼊在 nums 中的索引位置。
  • 返回的这个值是 nums 中⼩于 target 的元素个数。

⽐如在有序数组 nums = [2,3,5,7] 中搜索 target = 4,搜索左边界的⼆分算法会返回 2,你带⼊上⾯ 的说法,都是对的。

我们现在需要的正是第二种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
return left_bound(nums, target);
}
int left_bound(vector<int>& nums, int target){
int left = 0, right = nums.size();//需要注意
//需要注意不能等于
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] < target){
left = mid + 1;
}else if(nums[mid] > target){
//这里也需要注意
right = mid;
}else{
right = mid;
}
}
return left;
}
};

以上几点需要注意,因为我们需要的是要插入的位置。