二分、三分查找(玄学)(时不时更新中~

一、二分法

1.概述

最近学了二分感觉世界又玄幻了不少, 看了许多优秀的博客和文章,各式各样的二分写法应有尽有。做了几道二分答案的题目,也是处于懵逼状态,感觉就是有思路但是代码对不对全靠运气。觉得还是需要有一个解题的模板所以接下来要总结一下下(其实就是跟着几个大佬的博客学的)
原博:博客链接1博客链接2
相信懂的都懂,二分最重要也是最难把握的细节之一就是终止边界和左右区间的取舍时的开闭情况,操作不好的话就很容易漏掉答案或者造成死循环。

2.整数二分

这里大佬总结了两个模板(用了都说好

模板一:


while (l < r) {
	int mid = (l + r) / 2;
	if (check(mid)) r = mid; 
	else l = mid + 1;
}

这个模板中我们把区间[l,r]分为[l,mid]和[mid+1,r],也就是当答案属于右半部分的时候,更新操作是r=mid或l=mid+1,计算mid时不需要+1
为什么l=mid+1?因为更新l的时候说明x属于右区间,而mid不属于右区间因此不可能成为答案

模板二:


while (l < r) {
	int mid = (l + r + 1) / 2;
	if (check(mid)) l = mid; 
	else r = mid - 1;
}

这个模板中我们把区间分为[l,mid-1]和[mid,r],也就是当答案属于左半部分的时候,更新操作是r=mid-1或l=mid,计算mid时需要+1
为什么r=mid-1?与模板一中同理。
为什么计算mid时要+1?因为防止死循环的出现

以及一套二分流程:

1.确定二分的边界
2.编写二分的代码框架(模板)
3.设计一个check()(二分的性质)
4.判断一下区间如何更新(判断使用哪个模板)
5.如果更新方式为 l = mid, r = mid -1 ,则在更新mid的时候需要加上1

3.实数二分

实数二分要注意的是精度问题
主要有两种归精度方法
1.设置eps法

while(l-r>0.0001){//精度具体看题目要求
	double mid = (l+r)/2;
	if(check(mid)) r = mid;
	else l = mid;
}

2.设置循环次数法

for(int i=0;i<100;i++){//一般是100次就够了
	double mid = (l+r)/2;
	if(check(mid)) r = mid;
	else l = mid;
}

4.几道题目

(1)力扣:搜索二维矩阵

题目链接:搜索二维矩阵
比较基础的一道题,适合学习模板之后直接套用
注意一点就是矩阵可以拉成一行,访问原先位置只需要
matrix[mid/m][mid%m](这里的m是矩阵的行数)
还有就是注意判断空集!!!

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;
            if(matrix[mid/m][mid%m]>=target) r = mid;
            else l = mid+1;
        }
        if(matrix[l/m][l%m]==target)return true;
        else return false;
    }
};

(2)力扣:寻找旋转排序数组中的最小值

题目链接:寻找旋转排序数组中的最小值
这道题初看以为要排序+二分,看了大佬的思路之后,感觉这道题是二分思维一个很好的反映。
就是我们要找的那个数必须满足nums[mid]<=nums.back()
也就是要找<=nums.back()中最小的一个数,那么直接套模板一

class Solution {
public:
    int findMin(vector<int>& nums) {
        int l = 0 , r = nums.size();
        while(l<r){
            int m = (l+r)>>1;
            if(nums[m]<=nums.back()) r = m;
            else l = m+1;
        }
        return nums[l];
    }
};

(3).力扣:H 指数 II

题目链接:H 指数 II
先理清楚题意:
要找到一个最大的数h,使得数组中至少存在h个数>=h
然后确定二分区间[0,n]
接着确定模板类型,由于如果h满足条件则h-1必定满足条件,这意味着答案属于左区间,故选择模板二
关于check()函数的设计,由于数组有序,故只需要判断倒数第h个数是否>=h即可,故代码如下

class Solution {
public:
    int hIndex(vector<int>& citations) {
        if(citations.empty()) return 0;
        int l = 0 , r = citations.size();
        while(l < r){
            int m = (l + r + 1) >> 1;
            if(citations[citations.size()-m] >= m) l = m;
            else r = m - 1;
        }
        return l;
    }
};

鸣谢

在这里感谢各位大佬的博客以及yxc大佬的视频
让我基本上理解了基础二分查找的思想!!
感谢!!!

二、三分法

1.概述

三分法主要用于求解单峰函数的极值点问题
所谓的单峰函数,就是一个函数它有且仅有一个极值点,可以为极大值点也可以为极小值点
以仅有一极大值点的单峰函数f(x)为例,我们将所给区间[L,R]作三等分,将两个三等分点分别记作Lmid,Rmid,则有L<Lmid<Rmid<R
记极大值点为x0,则不难证明,
当f(Lmid)>f(Rmid)时,必有Rmid>x0,则此时我们可以缩小查找范围至[L,Rmid]
当f(Lmid)<f(Rmid)时,必有Lmid<x0则此时我们可以缩小查找范围至[Lmid,R]

2.模板及模板题

//以f(x)为凸函数为例
double L=0,R=1000;
for(int i=0;i<100;i++){//迭代到精度满足要求
	double lm = L+(R-L)/3;
	double rm = R-(R-L)/3;
	if(f(lm)<f(rm)) L = lm;
	else R = rm;
}

题目链接:
P3382 【模板】三分法

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double func(double a[],int N,double x)
{
	double sum = 0;
	for (int i = 0; i <= N; i++)
	{
		sum += pow(x, N - i)*a[i];
	}
	return sum;
}
int main()
{
	int N;
	double l, r;
	cin >> N>>l >> r;
	double a[100];
	for (int i = 0; i <= N; i++){
		cin >> a[i];
	}
	for (int i = 0; i < 100; i++){
		double lm = l + (r - l) / 3;
		double rm = r - (r - l) / 3;
		if (func(a, N, lm) < func(a, N, rm)){
			l = lm;
		}
		else
			r = rm;
	}
	printf("%.5lf", l);
	return 0;
}

计算多项式可以直接像上面一样暴力解
也可以用下面的秦九韶算法

double f(double x)
{
    double ans = 0;
    for(int i=0;i<=n;i++){
        ans=ans*x+a[i];
    }
    return ans;
}

三、二分答案

1.概述

二分答案的思想在我看来就是,在已知的答案区间内,二分出一个答案A,如果该答案在数据上反应出来的效果与条件不符,则缩小区间继续二分(这个思想这样说真的很抽象 )还是看例题吧

2.几道题目

(1)vj:The Meeting Place Cannot Be Changed

题目链接:
https://vjudge.net/contest/406497#problem/B
经典二分答案!!
在数轴上二分出一个点的坐标,分别统计该坐标两侧人员到达该坐标所需要的最长时间,如果左侧时间大于右侧,则查左侧,反之查右侧。最后注意精度即可。

#include<iostream>
using namespace std;
const int inf = 0xffffffff;
const int maxn = 6e4+10;
double p[maxn],v[maxn];
double high,low,m,l,r;
double maxp,minp=inf;
int n;
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%lf",&p[i]);
        maxp=max(p[i],maxp);
        minp=min(p[i],minp);
    }
    for(int i=0;i<n;i++)scanf("%lf",&v[i]);
    high=maxp,low=minp;
    while(high-low>=1e-7){
        m=(high+low)/2;
        l = r = 0;
        for(int i=0;i<n;i++){
            if(p[i]<m){
                l=max((m-p[i])/v[i],l);
            }
            else{
                r=max((p[i]-m)/v[i],r);
            }
        }
        if(l==r)break;
        else if(l>r) high = m;
        else if(l<r) low = m;
    }
    printf("%.12lf",(l+r)/2);
    return 0;
}

(2)洛谷:P2678 跳石头(二分+模拟)

题目链接:
P2678 跳石头
二分答案持续更新中~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值