1. 基本方法
1. 使用二分的目的:最常见的是从挨个遍历的o(n) 复杂度降低到 o(logn)
2. 能使用二分法的条件:满足二段性质(不一定要单调性,只要满足某种分类的性质就可以)
3. 理解取整方式(针对整数二分)
注意:实际A和B不应该画到一个数轴上,是两种不同的二分情况;死循环是两个点中,A在左边或者右边,和B无关!示意图有点不严谨,以后再改~
2. 最基本的二分题目
69. x 的平方根
class Solution {
public:
/*
1.注意返回整数类型
2.红色段性质:m^2<=x;红色点:满足m^2<=x最大的整数
3.x是INT_MAX的话+1会溢出,所以需要转换
4.mid*mid可能溢出,所以用除法不等式
*/
int mySqrt(int x) {
int l = 0, r = x;
while(l < r)
{
int mid = l + (long long)r + 1 >> 1;
if(mid <= x / mid) l = mid;
else r = mid - 1;
}
return l;
}
};
278. 第一个错误的版本
class Solution {
public:
/*
1.蓝色段性质:从m点起往后全是错误版本;蓝色点:错误版本中最早出现的那个
*/
int firstBadVersion(int n) {
int l = 1, r = n;
while(l < r)
{
int mid = l + (long long)r >> 1;
if(isBadVersion(mid)) r = mid;
else l = mid + 1;
}
return l;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置(剑指 Offer 53 - I. 在排序数组中查找数字 I)
class Solution {
public:
/*
1.第一个二分找左区间:找蓝色点:>=x的最小的数
2.第二个二分找右区间:找红色点:<=x的最大的数
3.找到一个点后需判断二分结果是否正确(二分保证有解,但不保证一定正确)
*/
vector<int> searchRange(vector<int>& nums, int x) {
if(nums.empty()) return {-1,-1};
int l = 0, r = nums.size() - 1;
while(l < r)
{
int mid = l + r >> 1;
if(nums[mid] >= x) r = mid;
else l = mid + 1;
}
if(nums[l] != x) return {-1,-1};
else
{
int low = l;
int l = 0, r = nums.size() - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(nums[mid] <= x) l = mid;
else r = mid - 1;
}
int high = l;
return {low,high};
}
}
};
3. 稍微转化一下的二分题目
275. H 指数 II
class Solution {
public:
/*
1.蓝色段性质:[m,len]的篇数和 =< [m]次;蓝色点:满足[m,len]的篇数和<[m]的最大h,所以需要[m,len]越长越好
2.判断的前提是序列正常排列:空序列和全0序列需要剔除
*/
int hIndex(vector<int>& citations) {
if(citations.empty() || citations[0] == 0 && citations.back() == 0) return 0;
int l = 0, len = citations.size() - 1, r = len;
while(l < r)
{
int mid = l + r >> 1;
if(citations[mid] >= len - mid + 1 ) r = mid;
else l = mid + 1;
}
return len- l + 1;//返回[m,len]长度
}
};
287. 寻找重复数
class Solution {
public:
/*
1.额外空间只能o(1)就不能用hash;暴力做法是对n个数遍历n次,看每个数是否重复,复杂度o(n^2),二分可以由抽屉原理优化
2.对1-n的数值进行二分,红色段性质:数值[h,n]之间的数,实际出现次数>n-h+1;红色点:最大的h
*/
int findDuplicate(vector<int>& nums) {
int n = nums.size() - 1;
int l = 1, r = n;
while(l < r)
{
int mid = (l + r + 1) >> 1;
int cnt = 0;
for(int i = 0; i < nums.size(); i ++)
{
if(nums[i] >= mid && nums[i] <= n) cnt ++;
}
if(cnt > n - mid + 1) l = mid;
else r = mid - 1;
}
return l;
}
};
35. 搜索插入位置
class Solution {
public:
/*
1.蓝色段性质:>=x;蓝色点:>=x的最小的值;若==x则是x的位置,若>x则是x需要插入的位置
2.没有>=x的点:肯定二分不出来,所以直接插在数组最后即可
*/
int searchInsert(vector<int>& nums, int target) {
if(nums.empty() || target > nums.back()) return nums.size();//边界:可能为空;可能比所有值大
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;
}
return l;
}
};
剑指 Offer 53 - II. 0~n-1中缺失的数字
class Solution {
public:
/*
1.蓝色段性质:m!=nums[m];蓝色点:满足m!=nums[m]最左边的点
2.递增数组的back==n-2,说明前面刚好是0~n-2,只差n-1。
*/
int missingNumber(vector<int>& nums) {
if(nums.back() == nums.size() -1) return nums.size();
int l = 0,r = nums.size()-1;
while(l<r)
{
int mid = (l+r)>>1;
if(mid != nums[mid]) r = mid;
else l = mid + 1;
}
return l;
}
};
162. 寻找峰值
class Solution {
public:
/*
1.[m]>[m+1],说明[l,m]前面肯定有答案;反之[m]>[m-1],[m,r]有答案
2.其实二分就是一直用l r来更新区间,最终l==r则找到答案。
mid的作用就是判断l更新还是r更新,不同更新方式反过来确定mid需要上取整还是下取整,否则会死循环
*/
int findPeakElement(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] > nums[mid + 1]) r = mid;
else l = mid +1;
}
return l;
}
};
4. 旋转数组中的二分
33. 搜索旋转排序数组
class Solution {
public:
/*
1.直接看数组是没有单调性的,但可以找到中间不连续的地方,分成两段,就有单调性了
2.找旋转数组的分段点:蓝色段性质:<=back;蓝色点:<=back的最小的数(其实也包含了没旋转的情况)
3.判断target在左半段还是右半段,用二分找这个数即可(注意可能不存在)
*/
int search(vector<int>& nums, int target) {
if(nums.empty()) return -1;
int l = 0, r = nums.size() - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if (nums[mid] <= nums.back()) r = mid;
else l = mid + 1;
}
int min_idx = l;
if(target <= nums.back())//注意题目说不存在重复元素,存在的话可能找不全,但至少也能找出一个
{
l = min_idx;
r = nums.size() - 1;
}
else
{
l = 0;
r = min_idx - 1;
}
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[l] == target) return l;
else return -1;
}
};
剑指 Offer 11. 旋转数组的最小数字(153. 寻找旋转排序数组中的最小值)
class Solution {
public:
/*
1.找旋转数组的分段点:蓝色段性质:<=back;蓝色点:<=back
2.但由于[0]可能和back相等,二分不出来,将末尾与[0]相等的值剔除
3.如果是升序,就不用二分了
*/
int minArray(vector<int>& nums) {
if(nums.empty()) return -1;
int n = nums.size();
while(n > 1 && nums[n - 1] == nums[0]) n --;//数组不止1个数的情况下,在尾部剔除首尾相同的值
if(nums[n - 1] > nums[0]) return nums[0];//如果是升序,就不用二分了
int l = 0, r = n - 1;
while(l < r)
{
int mid = (l + r ) >> 1;
if(nums[mid] <= nums[n - 1]) r = mid;//也可以用<nums[0]
else l = mid + 1;
}
return nums[l];
}
};
5. 二维矩阵中的二分
74. 搜索二维矩阵
class Solution {
public:
/*
1.数组铺平其实就是一个连续的一纬数组,二分查找即可
2.注意坐标转换:m = x*col+y
*/
bool searchMatrix(vector<vector<int>>& nums, int target) {
if(nums.empty() || nums[0].empty()) return false;//判空保证下面的row和col都非0
int row = nums.size(), col = nums[0].size();
int l = 0, r = row * col - 1;
while(l < r)
{
int mid = l + r >> 1;
int x = mid / col, y = mid % col;
if(nums[x][y] >= target) r = mid;
else l = mid + 1;
}
if(nums[l / col][l % col] == target) return true;
else return false;
}
};
剑指 Offer 04. 二维数组中的查找(240. 搜索二维矩阵 II)
class Solution {
public:
/*
1.每次查询右上角的元素,<target说明该行不用找了,>target说明该列不用找了
*/
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.empty() || matrix[0].empty()) return false;
int row = matrix.size(), col = matrix[0].size();
int m = 0, n = col - 1;
while(m < row && n >= 0)
{
if (target == matrix[m][n]) return true;
else if(target > matrix[m][n]) m ++;
else n --;
}
return false;
}
};
4. 寻找两个正序数组的中位数
此题的l r范围需要仔细思考,另外check的原理也很奇妙:之所以是蓝色段性质,是因为当i比真实值大的时候,j会比真实值小,那[j-1]和[i]会越隔越开,自然就满足check条件
class Solution {
public:
vector<int> nums1;
vector<int> nums2;
int n, m;
double findMedianSortedArrays(vector<int>& _nums1, vector<int>& _nums2) {
nums1 = _nums1;
nums2 = _nums2;
n = nums1.size();
m = nums2.size();
int len = n + m;
if(len & 1) return findKthElm((len >> 1) + 1);
else return (findKthElm(len >> 1) + findKthElm((len >> 1) + 1)) / 2.0;
}
int findKthElm(int k){
int l = max(0, k - m), r = min(n, k);
while(l < r)
{
int mid = (l + r ) >> 1;
if(nums2[k - mid - 1] <= nums1[mid]) r = mid;
else l = mid + 1;
}
int nums1_max = l==0 ? INT_MIN : nums1[l - 1];
int nums2_max = l==k ? INT_MIN : nums2[k - l - 1];
return max(nums1_max, nums2_max);
}
};
部分内容来自y总的教学,致谢!