二分法 以及简单题目运用-自我回顾~~~

看了y老师的和知乎大神的对于二分法的讲解~~,回顾回顾

目录

二分法概念:

10道简单的二分模板题应用思路解析

 题目1:二分查找 和 题目2:35.搜索插入位置

题目3 :Sqrt(X)  和  题目4 :有效的完全平方数

题目5: 278.第一个错误的版本  

题目6 : 剑指Offer 57 .和为s的两个数字  和 题目7:猜数字大小

题目7:猜数字大小

题目8:267.寻找重复数 

题目9: 162.寻找峰值 

题目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;
}

代码运行结果

 


 以上及对二分法是思路解析和一些简单的题目应用

总结:

二分法比较灵活,关键在于想到用什么确定性质,从而划分区间~~

  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值