看了y老师的和知乎大神的对于二分法的讲解~~,回顾回顾
目录
题目6 : 剑指Offer 57 .和为s的两个数字 和 题目7:猜数字大小
题目10:在排序数组中查找元素的第一个和最后一个位置 / AcWing789.数的范围
二分法概念:
所谓二分,即通过一个性质Check把一个区间划分为两部分,一部分为满足这个性质的区间,另一部分为不满足这个性质的区间
而二分法的具体使用即找到满足Check性质和不满足Check性质的两个区间之间的边界
我们可以粗略的理解为寻找Meeting Last 和 None Meeting Begining
即寻找满足性质区间中的最后一个(ML),和 不满足性质区间的第一个(NB)
于是,我们可以使用以下两套二分模板
查找NB时的二分模板
int bsearch_1(int l, int r)
{
while (l < r)//NB模板, None Meeting Beginning
{
int mid = l + r >> 1;//此处原理是向下取整,我的理解是套模板就完事了
if (check(mid)) // check()判断mid是否满足性质
{
l = mid + 1;
}
else
{
r = mid;
}
}
return l;
}
对于if(check)mid 满足性质时:代码解释:
因为此时mid是满足性质区间的值,而我们又要寻找NB(不满足性质区间的第一个),所以
此时我们可以缩小边界,即把区间缩小范围到[MID+1,R]之内
对于mid不满足性质时的代码解释:
由于此时mid不满足性质,说明mid右边的区间都不满足性质,同理
我们可以把边界缩小到[L,MID]
查找ML时的二分模板
int bsearch_2(int l, int r)//ML模板,Meeting Last Beginning
{
while (l < r)
{
int mid = l + r + 1 >> 1;//此处原理是向上取整,我的理解是套模板就完事了
if (check(mid))
{
l = mid;// check()判断mid是否满足性质
}
else
{
r = mid - 1;//不满足性质,包括mid
}
}
return l;
}
对于if(check)mid 满足性质时:代码解释:
由于我们要寻找ML,即满足性质的最后一个,此时mid恰好又满足性质区间,说明此刻mid左边的区间都满足性质,则,我们可以把边界缩小到[MID,R]范围之内
则:L= MID;
对于不满足性质时的代码解释:
同上,寻找ML,则很明显的,当mid不满足性质时,我们可以把边界范围缩小到[L,MID-1]
PS:此刻MID也不满足性质
以上即对二分模板的思路解析
10道简单的二分模板题应用思路解析
题目1:二分查找 和 题目2:35.搜索插入位置
题目1:二分查找
这题是单纯的套模板题,我们只需要确定Check性质的划分即可
该题代码:
class Solution {
public:
int search(vector<int>& nums, int target)
{
int n=nums.size();
int l=0;int r=n-1;
while(l<r)
{
int mid=l+((r-l)>>1);
if(nums[mid]<target)//NB
{
l=mid+1;
}
else
{
r=mid;
}
}
if(nums[l] != target)
{
return -1;
}
return l;
}
};
把Check确认为nums[mid]<target,同时运用NB模板进行查找即可
代码运行结果:
题目2:35.搜索插入位置
思路:利用性质 (nums[mid] < target) 来缩小边界
代码
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
int n = nums.size();
int l = 0; int r = n - 1;
while (l < r)
{
int mid = l + ((r - l) >> 1);
if (nums[mid] < target)//
{
l = mid + 1;
}
else
{
r = mid;
}
}
if (nums[l] < target)
{
return l + 1;
}
return l;
}
};
由于该题目我们使用的是NB模板
所以,得到的l为不满足性质区间的第一个,必然>=target,所以对于nums[l]>=target的情况,只需要直接返回l,因为插入在l之后即可
而对于nums[l]<target的特殊情况,需返回下标l+1,为新插入的位置
代码运行结果:
题目3 :Sqrt(X) 和 题目4 :有效的完全平方数
两道题几乎无区别==,,只是一个为判断,一个返回值
Sqrt(X)代码:
class Solution {
public:
int mySqrt(int x)
{
if(x <= 1)
{
return x;
}
int l=0;int r=x/2;
while(l < r)
{
int mid=l+((r-l+1)>>1);
if((long long)mid*mid <= x) //寻找ML 模板1
{
l=mid;
}
else
{
r=mid-1;
}
}
return l;
}
};
值得一提的是,此时需要把mid*mid的值强转为long long类型再进行判断,因为mid平方可能会超过int类型的数据范围~~~~~~~~~
代码运行结果
有效的完全平方数代码:
class Solution {
public:
bool isPerfectSquare(int num)
{
long l=1;long r=num;
while(l<r)//NB模板
{
long long mid=l+((r-l)>>1);
if((mid*mid) < num)//如果mid*mid<num,则可以排除l,mid区间
{
l=mid+1;
}
else
{
r=mid;
}
}
if((l*l) == num)
{
return true;
}
else
{
return false;
}
}
};
同理:该题由于需要使用边界L/R进行判断,所以都使用long long类型
代码运行结果:
题目5: 278.第一个错误的版本
本题同样为简单模板应用题目,但需要注意
isBadversion(N) == True时说明此刻N为错误的版本
所以我们只需要单纯的把性质划分为是否满足isBadversion
如果是,即isBadversion(MID)== True
说明MID区间右边(包括MID)都为错误的版本,则把区间缩小道[L,MID]
包括MID是因为我们要寻找第一个错误的版本,即满足性质的第一个
代码如下:
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
//该题为寻找二分边界。****寻找边界*****
int firstBadVersion(int n)
{
int l=1;int r=n;
while(l<r)
{
int mid=l+((r-l)>>1);
if(isBadVersion(mid))//满足性质,说明mid此时为错的版本,后面都为错,所以缩小r边界
{
r=mid;
}
else//不满足性质,说明此时mid为正确版本。
{
l=mid+1;
}
}
return l;
}
};
代码运行结果:
题目6 : 剑指Offer 57 .和为s的两个数字 和 题目7:猜数字大小
题目代码
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
int n=nums.size();
if(n == 1)
{
return nums;
}
vector<int>ans;
for(int i=0;i<n;i++)
{
int l=0;int r=n-1;
while(l<r)
{
int mid=l+((r-l)>>1);
if(nums[mid] < (target-nums[i]))//NB模板
{
l=mid+1;
}
else
{
r=mid;
}
}
if(nums[l] != (target-nums[i]))
{
continue;
}
else
{
ans.push_back(nums[i]);
ans.push_back(nums[l]);
break;
}
}
return ans;
}
};
思路:
利用性质划分区间,该题性质 (nums[mid] < (target-nums[i]))
如果满足性质说明我们要找的值在mid右边,则l=mid+1,缩小边界
反之不满足性质说明值在mid左边,只需把边界缩小道[l,mid]即可,(需包含mid,因为mid有可能恰好等于)
代码运行结果:
题目7:猜数字大小
简单的二分模板运用,不多赘述,代码如下
思路:只需要把性质划分为guess(X)得到的值是否大于0即可
class Solution {
public:
int guessNumber(int n)
{
int l=1;int r=n;
while(l<r)
{
int mid=l+((r-l)>>1);
if(guess(mid) <= 0)//满足性质说明数字n在区间[L,MID]之间,所以缩小区间
{
r=mid;
}
else//不满足性质说明mid大于n,则把区间缩小到[MID+1,R]
{
l=mid+1;
}
}
return l;
}
};
代码运行结果:
题目8:267.寻找重复数
解题思路:抽屉原理
同时由于数字范围在1~n之间,则可以运用二分的思想进行查找
具体:先猜一个数mid,同时对数组中大于等于mid的元素进行计数(计数器res)
然后通过性质res>mid进行区间划分
具体代码:
class Solution {
public:
int findDuplicate(vector<int>& nums)
{
int n=nums.size();
int l=1;int r=n;
while(l<r)
{
int mid=l+((r-l)>>1);
int res=0;
for(int i=0;i<n;i++)
{
if(nums[i] <= mid)
{
res++;
}
}
if(res > mid)//如果res大于mid,说明此时左区间里出现重复数字
{
r=mid;
}
else//说明res
{
l=mid+1;
}
}
return l;
}
};
代码运行结果:
题目9: 162.寻找峰值
解题思路:
通过爬坡的性质来进行性质划分
即性质(是否大于左值),如果满足性质,说明仍在进行爬坡,否则则说明可能遇到峰值
L区间不包括mid原因为我们实际比较的是mid+1,满足性质说明mid已经可以不需要被包括
整体代码:
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int l = 0;
int r = nums.size() - 1;
while (l < r)
{
int mid=l+((r-l)>>1);
if (nums[mid] < nums[mid+1]) //满足性质说明此时仍在进行爬坡,则缩小区间到[mid+1,r]
{
l = mid + 1;
}
else
{
r = mid;
}
}
return r;
}
};
代码运行结果
题目10:在排序数组中查找元素的第一个和最后一个位置 / AcWing789.数的范围
同时该题也在acwing中,为模板题目
思路:
简单的二分划分,但该题需要同时运用模板1和模板2
如果对二分概念熟悉应该比较容易
思路:
先运用NB模板查找元素起始位置,再运用ML模板查找元素终止位置
两边代码都贴以下~~:
LeetCode
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
vector<int>C;
int n=nums.size();
if(n == 0)
{
C.push_back(-1);
C.push_back(-1);
return C;
}
int l=0;int r=n-1;
while(l<r)//运用NB模板
{
int mid=l+r>>1;
if(nums[mid] < target)
{
l=mid+1;
}
else
{
r=mid;
}
}
if(nums[l] != target)
{
C.push_back(-1);
C.push_back(-1);
return C;
}
else
{
C.push_back(l);
l=0;r=n-1;
while(l<r)//ML模板
{
int mid=(l+r+1)>>1;
if(nums[mid] <= target )
{
l=mid;
}
else
{
r=mid-1;
}
}
C.push_back(l);
}
return C;
}
};
AcWing
#include<iostream>
using namespace std;
const int N =1e6+10;
int main()
{
int A[N];
int n,q;cin>>n>>q;
for(int i=0;i<n;i++)scanf("%d",&A[i]);
while(q--)
{
int x;cin>>x;
int l=0;int r=n-1;
while(l<r)
{
int mid=l+r>>1;
if(A[mid]<x)//NB
{
l=mid+1;
}
else
{
r=mid;
}
}
if(A[l] != x)
{
cout<<"-1 -1"<<endl;
}
else
{
printf("%d ",l);
l=0;r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(A[mid] <= x)//ML
{
l=mid;
}
else
{
r=mid-1;
}
}
printf("%d\n",l);
}
}
return 0;
}
代码运行结果
以上及对二分法是思路解析和一些简单的题目应用
总结:
二分法比较灵活,关键在于想到用什么确定性质,从而划分区间~~