结合leetcode题目整理了学习二分查找的过程和理解。
二分本质上是求第一个满足某条件的数。如果题目具有二分性质,也就是所谓的题目具有单调性质。
每次写之前要考虑:
- 二分的边界
- 二分条件怎么写
- left和right怎么收缩
- 多push一个inf会不会更好。(本质上来讲,就是考虑二分的边界内是不是一定存在答案,如按插入一个数时插入数大于数组中所有数字)
关于nums.push_back(inf):
如果数列后加一个inf,变成{1,2,3,3,4,inf},大于等于target的第一个就是4号位置,大于等于target+1第一个就是5号位置。一些题目中更简单
inf
即题目中数据范围的最大值
cpp封装了一些现成的函数,在algorithm里,lower_bound和upper_bound分别是找大于等于第一个和大于第一个,小于最后一个和小于等于最后一个分别是lower_bound-1和upper_bound-1
要注意的问题
-
首先确定区间的边界条件。[left, right],右侧使用闭区间更不易出错
while(left <= right)
的终止条件是left == right + 1
,写成区间的形式就是[right + 1, right]
,这时候区间为空。
移动方式:left = mid + 1; right = mid - 1;
-
确定要找大于等于某个值的最小值/小于等于某个值的最大值/…
-
注意有重复数据时的情况
二分框架
int left, right, ans;
while (left <= right) {
int mid = (left + right) / 2;
if () {
left = mid + 1;
} else {
right = mid - 1;
ans = mid;
}
}
return ans;
704. 二分查找
寻找大于等于target的最小值/小于等于target的最大值。然后做判断,如果该值等于target,返回位置,否则返回-1
这里用的方法是小于等于target的最大值,把等号和ans放在left一边
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + ((right - left) / 2);
if (target < nums[mid]) {
right = mid - 1;
} else if (target > nums[mid]) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
}
};
35. 搜索插入位置
二分查找时间复杂度为O(log n)
本题实际上是判断大于等于target的最小值的位置,所以等号放在right一边,ans也放在right一边
需要多判断是否超出原数组长度(也可以在最后插入inf)
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int mid, ans;
while (left <= right) {
mid = left + (right - left) / 2;
if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
ans = mid;
}
}
return nums[nums.size() - 1] >= target? ans: nums.size();
}
};
以本题为例,说明为什么ans会放在right一侧:
落在右边分区的mid值要么满足nums[mid]
大于target
,要么满足nums[mid]
等于target
,所以ans
一定在这个分支内;而左边部分只会有nums[mid] < left
,答案一定不在其中。
如果求大于等于target的最小值,就把ans
放在大于等于的条件下。
假如a到b这个区间内存在那个等于的答案x,所有的x到b都满足大于等于x,所有的a到x都满足小于等于x,而二分就是找到x这个位置,或者x的左边一个,或者x的右边一个。
⭐34. 在排序数组中查找元素的第一个和最后一个位置
开始位置:大于等于target的最小值
结束位置:小于等于target的最大值/大于target的最小值-1
class Solution {
public:
int binarySearch(vector<int>& nums, int target, bool lower) {
int left = 0;
int right = nums.size() - 1;
int ans = nums.size();
nums.push_back(1e9);
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] > target || (lower && nums[mid] >= target)) {
right = mid - 1; //右边界左移
ans = mid;
} else {
left = mid + 1; //左边界右移
}
}
return ans;
}
vector<int> searchRange(vector<int>& nums, int target) {
int leftIndex = binarySearch(nums, target, true);
int rightIndex = binarySearch(nums, target, false) - 1; //求右边界时实际上求的是第一个大于target的值,故减一
if (leftIndex <= rightIndex && rightIndex < nums.size() && nums[leftIndex] == target &&nums[rightIndex] == target) {
return vector<int> {leftIndex, rightIndex};
}
return vector<int> {-1, -1};
}
};
69. x 的平方根
求小于等于
sqrt(x)
的最大值。
注意mid * mid
可能超出int
范围,强制转换为long long
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int mid, ans;
while (left <= right) {
mid = left + (right - left) / 2;
if ((long long)mid * mid <= x) {
left = mid + 1;
ans = mid;
} else {
right = mid - 1;
}
}
return ans;
}
};
367. 有效的完全平方数
可以找大于等于的最小值,最后判断是否相等
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 1;
int right = num;
int mid;
int ans;
while (left <= right) {
mid = left + (right - left) / 2;
if ((long long)mid * mid < num) {
left = mid + 1;
} else {
right = mid - 1;
ans = mid;
}
}
return (long long)ans * ans == num? true: false;
}
};
也可以:
class Solution {
public:
bool isPerfectSquare(int num) {
int l=1,r=num;
while(l<=r)
{
int mid=l+(r-l)/2;
if(1ll*mid*mid<num)l=mid+1;
else
{
r=mid-1;
if(1ll*mid*mid==num)return true;
}
}
return false;
}
};
1ll就是一个longlong类型的1,一个longlong类型乘int类型,会把int类型强转成longlong类型,然后再乘,所以不会溢出。
其他
-
给定一个按照升序排列的整数数组 nums,和一个目标值 target,找到大于target的最小值
-
给定一个按照升序排列的整数数组 nums,和一个目标值 target,找到小于等于target的最大值
-
给定一个按照降序排列的整数数组 nums,和一个目标值 target,找到大于等于target的最大值
-
给定一个按照降序排列的整数数组 nums,和一个目标值 target,找到小于等于target的最小值
12问:
34问不需要二分
补充知识