while不取等,找小于等于target,判断右边界
整数的二分查找
Y总的两个板子:
理解:我感觉不管是整数还是浮点数,他们的本质都是找到check(mid)满足这个条件的l下标,或者l代表的数,这是本质。
bool check(int x) {/* ... */} // 检查x是否满足某种性质// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:int bsearch_1(int l, int r){while (l < r){int mid = l + r >> 1;if (check(mid)) r = mid; // check()判断mid是否满足性质else l = mid + 1;}return l;}// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用: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;}
根据例题对上面的板子进行讲解和理解
上面图的红和绿之前的区间本质是下面题要求的那部分,位置就是下面要求的答案
- acwing 789.数的范围
自己的理解:
下面 a[mid] >= x的时候,找到满足这个check的第一个下标,如果不存在==的情况,那么找到的一定是最小的,并且大于x的下标!!!!
同理第二个,也是找到满足a[mid] <= x的时候,找到满足第一个下标
#include <iostream>using namespace std;const int N = 1e5 + 10;int n , m;int a[N];int main() {scanf("%d%d", &n, &m); // m作为查询次数for (int i = 0; i < n ; i++) {scanf("%d", &a[i]);}while (m--) {int x;scanf("%d", &x); // m次查询,每次查询的内容int l = 0, r = n - 1; // 开始应用板子,目的是为了选出第一个大于等于x的位置。如果//元素不存在,选出的l或者事r本质是第一个大于x元素的位置。while (l < r) {int mid = l + r >> 1; // 这里实际做题的时候先写上 l + r >> 1;后面true的时候 r = mid的不用变,后面如果是 l = mid必须+1if(a[mid] >= x) r = mid; // 首先if里面本质就是上面板子的check,r的取值也是需要根据实际解答else l = mid + 1; // 这里不用思考,上面确定以后,直接可以确定}if (a[l] != x) cout << "-1 -1" << endl; // 如果存在,板子一定找得到,但是不存在,很明显,上面的板子二分查找的是满足第一个大于x的位置。else {cout << l << " ";int l = 0, r = n - 1;while (l < r) {int mid = l + r + 1 >> 1;if (a[mid] <= x) l = mid; // 因为true的时候,l = mid所以上面mid防止无线循环,必须 +1;else r = mid - 1;}cout << l << endl;}}}
实用算法的二分查找(他是假定了数组中一定存在要查找的元素了)
int l = 0;int r = n - 1;while (l <= r){int mid = l + r >> 1;if (a[mid] > x) r = mid;else if (a[mid] < x) l = mid + 1;else return mid;}
对这里的理解:
找到满足mid * mid <= x,并且l和r几乎相等的情况
#include <iostream>using namespace std;int main() {double x;cin >> x;double l = 0, r = x; // 因为开根号,数据一定在 0到x,相当于找一个区间内的某个元素while (r - l > 1e - 6) { // 下面输出的时候,如果输出四位小数,这里-6就能满足要求,下面输出每加一位,这里-6对应变成-7double mid = (l + r) / 2;if (mid * mid <= x) l = mid; // 这里就是浮点数的好处,没有所谓的边界问题。对于l和r的更新操作全部都是一样的。else r = mid;}cout << l;return 0;}
求一个数的三次根号
这里和上面二次根号的很明显的一点,l 可以是一个负数,这就导致,如果采用根号的方式,我把范围定界在输入的数的范围之内,l会出现问题。
幸好做的题目规定数的范围 -10000 到 10000
#include <iostream>using namespace std;int main() {double x;cin >> x;double l = -10000, r = 10000;while (r - l > 1e-8) {double mid = (l + r) / 2;if (mid * mid * mid <= x) l = mid;else r = mid ;}printf("%lf", l);return 0;}
lc剑指offer53

class Solution {public:int search(vector<int>& nums, int target) {int l = 0, r = nums.size() - 1;while (l < r){int mid = l + r >> 1;if (nums[mid] >= target) r = mid;else l = mid + 1;}int cnt = 0;int si = nums.size();while (l <= si - 1){if (nums[l] == target)cnt++;else break;l++;}return cnt;}};
35搜索插入位置
二分过程修改,y总模板必须有更改变化,开始和末尾的时候
class Solution {public:int searchInsert(vector<int>& nums, int target) {int l = 0;int si = nums.size();int r = si - 1;if (si == 1){if (target <= nums[0])return 0;else return 1;}int mid;while (l < r) {mid = l + r >> 1;if(nums[mid] >= target) r = mid;else l = mid + 1;}if (l == si - 1 && nums[l] < target) return si;return l;}};
剑指11寻找数组的最小数字
旋转数组最重要的一点,以后见到直接找最小值,进行分界,如果是找最大值的话,最小值直接
(idx + size - 1)% size 返回就可以
如果数组存在重复元素,只需要r = r - 1就可以。
用最大值的方式也不是不行,就是会比较麻烦
下面的题解讲解这类题讲解的非常清楚
https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/solution/er-fen-cha-zhao-wei-shi-yao-zuo-you-bu-dui-cheng-z/
一定注意这里找的是最小的,最后一定剩下两个数,找较小的时候,mid需要和left相等,如果是找比较大的时候,这个时候mid和right相等,这个时候mid = l + r + 1 >> 1来实现。
y总的模板可以套路,首先这个题不是完整的递增和递减,其次得知道怎么写判断条件。几乎是一个背过的题目,很难自己想出来。知道二分也难想。中间大于右边,一定在右边,mid+1——r,小于左边,r = mid,不是mid - 1,等于的时候无法判断左边右边,就直接r = r - 1;
重复数组求最小值
class Solution {public:int minArray(vector<int>& numbers) {int l = 0;int si = numbers.size();int r = si - 1;while (l < r){int mid = l + r >> 1;if (numbers[mid] > numbers[r]) l = mid + 1;else if (numbers[mid] < numbers[l]) r = mid;else r = r - 1;}return numbers[l];}};
重复的数组求解最大值的代码
class Solution {public:int minArray(vector<int>& nums) {int l = 0, r = nums.size() - 1;while (l < r){int mid = l + r + 1>> 1;if (nums[l] < nums[mid]) l = mid;else if(nums[l] > nums[mid]) r = mid - 1;// 和上面的最小值的最大的区别,你试一下就可以,2,1,2,2这种的就会导致错过第一个2,相等的时候就得从右边r = r - 1else if (nums[l] == nums[mid] && nums[l] == nums[l + 1]) l = l + 1;else r = r - 1;}return nums[(l + 1) % nums.size()];}};
不重复的旋转数组找最小值
本质和上面的题是一样的,求解最小值的时候,中间值和nums[right]比较,小于的时候r = mid, 大于的时候 l = left + 1,跟左边比的时候,同样都是nums[mid] > nums[left],最小值在左边区间和右边区间都有可能。上个题目的链接讲述的非常清楚。
class Solution {public:int findMin(vector<int>& nums) {int l = 0, r = nums.size() - 1;while (l < r){int mid = l + r >> 1; // 求解最小值,求解的目标值偏向左边,具体看链接if (nums[mid] > nums[r]) // 只要大于,最小值一定不是mid,l = mid + 1;elser = mid; // 小于的时候可能存在最小值}return nums[l];}};
找最大值的方式,右边就是最小值的思路找到最小值
class Solution {public:int findMin(vector<int>& nums) {int l = 0, r = nums.size() - 1;while (l < r){int mid = l + r + 1 >> 1; // 两个元素的时候,mid和right相等,因为序列是递增的序列if (nums[mid] > nums[l]) // 最后l = mid;elser = mid - 1;}return nums[(l + 1) % nums.size()];}};
搜索旋转数组
- 这个题就是上面题解应用,先找到最小值,用二分的方法。然后选择一个区间继续查找。

class Solution {public:int bound(int l, int r, vector<int>& nums, int target){int i = r;while (l < r){int mid = l + r >> 1;if (nums[mid] >= target) r = mid;else l = mid + 1;}if (l <= i && nums[l] != target) return -1;else return l;return -1;}int search(vector<int>& nums, int target) {if (nums.size() == 1) return nums[0] == target? 0 : -1;int l = 0, r = nums.size() - 1;while (l < r){int mid = l + r >> 1;if (nums[mid] > nums[r]) l = mid + 1;else r = mid;}if (l == 0) return bound(0, nums.size() - 1, nums, target);int idx = -1;if (nums[0] > target) {return bound(l, nums.size() - 1, nums, target);} else {return bound(0, l - 1, nums, target);}return idx;}};
山脉寻找目标值

本质也是利用二分查找,但是和上面找最大最小值不一样,因为判断条件是不一样的
/**
* // This is the MountainArray's API interface.
* // You should not implement it, or speculate about its implementation
* class MountainArray {
* public:
* int get(int index);
* int length();
* };
*/
class Solution {
public:
int search1(int l, int r, MountainArray& mountainArr, int target)
{
while (l < r)
{
int mid = l + r >> 1;
if (mountainArr.get(mid) >= target) r = mid;// 逆序的时候能不能用
else l = mid + 1;
}
if (mountainArr.get(l) != target) return 0x3f3f3f3f;
else return l;
}
int search2(int l, int r, MountainArray& mountainArr, int target)
{
while (l < r)
{
int mid = l + r >> 1;
if (mountainArr.get(mid) <= target) r = mid;// 逆序的时候换成小于等于就可以。找的目标值就是下标小于等于目标值的第一个位置
else l = mid + 1;
}
if (mountainArr.get(l) != target) return 0x3f3f3f3f;
else return l;
}
int findInMountainArray(int target, MountainArray &mountainArr) {
int l = 0, r = mountainArr.length() - 1;
while (l < r)
{
// 这里没有+1,保证l和mid只要在while循环里面,就是想等的,
//并且两个元素的时候 l = mid = r - 1;这样下面的判断就永远不会越界
int mid = l + r >> 1;
if (mountainArr.get(mid) < mountainArr.get(mid + 1))
l = mid + 1;
else
r = mid;
}
int idx1 = search1(0, l, mountainArr, target);
// 注意求idx2的时候是逆序的,逆序需要使用第二个search函数
int idx2 = search2(l + 1, mountainArr.length() - 1, mountainArr, target);
int res = min(idx1, idx2);
return res == 0x3f3f3f3f ? -1 : res;
}
};
