leetcode --二分专题
一.思路
常见的二分一般是数组满足按序排列,其实广义来看,只需要满足左边符合性质1,右边满足性质2即可,分界点跟随其中的某一段性质,然后根据中点不断二分,最后当l=r时,就找完了整个数组,若这个时候找到的值等于target,则寻找完毕,输出即可!
二.两套模板
简易速记:更新L和R时,两个模板中一个时R=M,一个是L=M,模板中对M的更新可以概括为左加右不加,左加等价于(m=(l+r+1)/2),多加一个1,右不加等价于(m=(l+r)/2)
写代码时候,我习惯将含边界点(一般来说判断条件有等于号的)写在if里
上图的模板1:右不加情况:要把R更新为M
//找的点是性质2的分界点(即满足性质2)
int l,r;
while(l<r){
int m=(l+r)/2;
if(m满足性质2,可知分界点在m的左边或就是m,所以将区间更新为(l,m)) r=m;
else l=m+1;
}
return l or r;
上图的模板1:左加的情况:要把L更新为M
//找的点是性质1的分界点(即满足性质1)
int l,r;
while(l<r){
int m=(l+r+1)/2; //多加一个1
if(m满足性质1,可知分界点在m的右边或就是m,所以将区间更新为(m,r)) l=m;
else r=m-1;
}
return l or r;
三.具体的题目(leetcode)
整理了10道题目 题号如下:
1.69-------2.35-------3.34-------4.74-------5.153
6.33-------7.278-----8.162------9.287----10.275
具体题目就不放了,题目可以去官网找:https://leetcode.com/problemset/all/
//二分-1:leetcode 69
class Solution {
public:
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 r;
}
};
//二分-2:leetcode 35
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
//特判一下
int n=nums.size();
if (n==0 || nums[n-1]<target) return n;
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(target<=nums[mid]) r=mid;
else l=mid+1;
}
return r;
}
};
//二分-3:leetcode 34
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//左边和右边就都需要判定
//先找出左边的数字
int start,end;
int n=nums.size();
if(n==0) return vector<int> ({-1,-1})
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
if(nums[r]!=target) return vector<int> ({-1,-1});
else
start=r; //将小的找了出来
r=n-1
while(l<r){
int mid=l+r+1>>1;
if(nums[mid]<=target) l=mid;
else r=mid-1;
}
if(nums[r]!=target) return vector<int> ({-1,-1});
else
end=r;
return vector<int> ({start,end});
}
};
//二分-4:leetcode 74
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.empty() || matrix[0].empty()) return false;
int n=matrix.size(),m=matrix[0].size();
int l=0,r=n*m-1;
while(l<r){
int mid=l+r+1>>1;
if(matrix[mid/m][mid%m]<=target) l=mid;
else r=mid-1;
}
return matrix[r/m][r%m] == target;
}
};
//二分-5:leetcode 153
//[4,5,6,7,1,2,3] 旋转后的表,需要找到二分的点,因为旋转的是单调数组,
//所以在例子中的1右边的数都小于4,左边都大于4,
class Solution {
public:
int findMin(vector<int>& nums) {
int n=nums.size();
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid]<=nums[n-1]) r=mid;
else l=mid+1;
}
return nums[r];
}
};
//二分-6 :leetcode 33
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.empty()) return -1;
int n=nums.size();
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid]<=nums[n-1]) r=mid;
else l=mid+1;
}
int min_index=r; //这个时候报错 debug
//Last executed input 只有一个数的时候
if(nums[n-1]>=target)
l=min_index,r=n-1;
else
l=0,r=max(0,min_index-1); //如果r=min_index-1的话,就会溢出报错,所以特判一下,=-1,max将其设为0
while(l<r){
int mid1 = l+r+1>>1;
if(nums[mid1]<=target) l=mid1;
else r=mid1-1;
}
if(nums[r]==target) return r;
else return -1;
}
};
//二分-7 :leetcode 278
// Forward declaration of isBadVersion API.
//[1,2,3,4,5,4,3,2,1] 拆分性质 第一个属于[1,2,3,4] 好产品 第二个属于[5,4,3,2,1]坏产品
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int l=1,r=n;
while(l<r){
int mid=(long long)l+(long long)r>>1; //大的值溢出,所以将l和r的类型转换成long long
//或者直接在开头定义为 long long l=1,r=n;
if(isBadVersion(mid)) r=mid;
else l=mid+1;
}
return r;
}
};
//二分-8 :leetcode 162
//[1,2,3,2]滑动窗口为3,扫一遍,o(n)肯定可以将所有的峰值点找出来,但在这个题中,只需要返回一个峰值即可,
//所以将时间复杂度降低的方法,考虑二分,拆分性质,左边右边。
//[1,2,1,3,5,6,4]
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int n=nums.size();
if(n==1 || nums[0]>nums[1]) return 0;
int l=1,r=n-1;
while(l<r){
int mid=l+r+1>>1;
if(nums[mid]>nums[mid-1]) l=mid;
else r=mid-1;
}
return r;
}
};
//二分-9 :leetcode 287
//不能使用额外空间,原本可以用线性扫描+hash表来处理,但是不可以使用额外空间,暴力做法两重循环,复杂度o(n2)
//优化在于是否考虑可以将o(n2)降成o(nlogn),这里改变一下思路,不二分索引,而是二分n的大小,n+1个数,每个数从
//1到n,要重复的话,这个词就只能出现1次,所以在1-n这个角度上进行二分,每次比较的是小于等于mid的数的数量是否
//等于mid,还是大于mid,第一层二分循环上时间复杂度为o(logn),循环里面统计个数扫描时间复杂度为o(n),总共的时间
//复杂度为o(nlogn),优化了o(n)
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n=nums.size();
int l=1,r=n;
while(l<r){
int mid=l+r>>1;//用mid将其分为两部分,
int count=0;
for (int i=0;i<n;i++){
if(nums[i]<=mid) count++;
}
if(count>mid) r=mid;
else l=mid+1; //其实else就是count=mid,
}
}
};
//二分-10 :leetcode 275
//由于数组是从小到大排好序的,所以我们的任务是:在数组中找一个最大的 hh,使得后 hh 个数大于等于 hh。
//我们发现:如果 hh 满足,则小于 hh 的数都满足;如果 hh 不满足,则大于 hh 的数都不满足。所以具有二分性质。
//直接二分即可。时间复杂度分析:二分检索,只遍历 lognlogn 个元素,所以时间复杂度是 O(logn)O(logn)。
class Solution {
public:
int hIndex(vector<int>& citations) {
if (citations.empty()) return 0;
int l = 0, r = citations.size() - 1;
while (l < r)
{
int mid = (l + r) / 2;
if (citations.size() - mid <= citations[mid]) r = mid;
else l = mid + 1;
}
if (citations.size() - l <= citations[l]) return citations.size() - l;
return 0;
}
};
虽说算法靠理解,但是应对笔试面试,还是需要背一定的模板!记得面字节跳动01背包,快速和归并排序都是原题,背模板就可以,很重要重要!
感谢acwing的闫学灿大佬:https://www.acwing.com/,推荐给刷题的小伙伴们~
秋招加油~