二分最简单模版
二分的本质:找到丢弃一半的规则
其实二分并不一定局限在有序区间内,只要区间具有二段性
就可以二分,即区间具有某个性质可以将区间分为两段,使得每次搜索时都能将区间缩小而且保证答案仍在区间内
例如:
左边一段的点都满足一个性质:上面的点比nums[n - 1]大
右边一段的点都满足一个性质:上面的点不比nums[n - 1]大
这就是所谓的二段性
二分只有下面两种情况
- 找大于等于给定数的第一个位置 (满足某个条件的第一个数)
- 找小于等于给定数的最后一个数 (满足某个条件的最后一个数)
二分模板说明
- 循环必须是l < r
- if判断条件看是不是不满足条件, 然后修改上下界
- 若是r = mid - 1, 则前面mid 语句要加1(记住与r平衡就行)
- 出循环一定是l == r,所以l和r用哪个都可以
- 有的时候如果是单调的,l = mid + 1,一直加到最后一个,此时不用担心越界,因为l == r了,退出循环
代码模版
// 判断条件很复杂时用check函数,否则if后直接写条件即可
bool check(int mid) {
...
return ...;
}
// 能二分的题一定是满足某种性质,分成左右两部分
// if的判断条件是让mid落在满足你想要结果的区间内
// 找满足某个条件的第一个数 即右半段
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
// 找满足某个条件的最后一个数 即左半段
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
例题
69.x的平方根
class Solution {
public:
int mySqrt(int x) {
int l = 0, r = x;
while (l < r) {
// 两个int相加减会溢出 中间加个长整型常量
// 少用乘法,用除法可以防止溢出
int mid = l + 1ll + r >> 1;
if (mid <= x / mid) l = mid;
else r = mid - 1;
}
return l;
}
};
162. 寻找峰值(不有序,也能二分)
二分法,我们一般用在有序数组上,那么这个题为什么可以用二分呢?
不管什么情况,之所以能用二分,是因为我们可以根据某个条件,直接抛弃一半的元素,从而使得时间复杂度降到 log 级别。
只要数组中存在一个元素比它相邻元素大,则沿着它一定可以找到一个峰值,这样就可以抛弃另一半了
找到了丢弃一半的规则
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r) {
int mid = l + r >> 1;
// if (mid == n - 1) return nums[n - 1]; 不用加这句,因为此时l == r
if (nums[mid] <= nums[mid + 1]) l = mid + 1;
else r = mid;
}
return l;
}
};
33.搜索旋转排序数组(好题)
左边一段的点都满足一个性质:上面的点比nums[n - 1]大
右边一段的点都满足一个性质:上面的点不比nums[n - 1]大
这就是所谓的二段性
算法:两次二分(比较自然的想法),先找到最小值点,然后在target所在的区间段进行二分
// 2019年12月28日 星期六 15:55:29
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = nums.size();
if (n == 0) return -1;
int l = 0, r = n - 1;
if (nums[0] > nums[n - 1]) { // 若发生翻转,找到最小值
while(l < r) {
int mid = l + r >> 1;
if (nums[mid] <= nums[n - 1]) r = mid;
else l = mid + 1;
}
// 确定target区间
if (target <= nums[n - 1]) {
l = l, r = n - 1;
} else {
l = 0, r = r - 1;
}
}
while (l < r) {
int mid = l + r + 1>> 1;
if (nums[mid] > target) r = mid - 1;
else l = mid;
}
if (nums[l] == target) return l;
return -1;
}
};