从零开始的二分法

从零开始的二分法

第一关:递归

先来一个基本的二分法,用递归写出二分法:

int bsearch(vector<int> &nums, int l, int r, int target)
{
    // 在区间[l,r)进行搜索
}

算法一:递归的左闭右开区间搜索

不错,我们继续,试试闭区间内搜索:

int bsearch(vector<int> &nums, int l, int r, int target)
{
    // 在区间[l,r]进行搜索
}

算法二:递归的闭区间搜索

如果你完成了上面四个代码,说明你对二分的终止条件有了一定的认识,我们继续:

如果你像我这样,写了多的if,试试能不能简化他们,合并一些条件,比如将==合并到<里面

int bsearch(vector<int> &nums, int l, int r, int target)
{
    // search in [l,r]

    if (l == r && nums[l] != target)
    {
        return -1;
    }

    int mid = (l + r) / 2;

    if (nums[mid] == target) // HOW TO merge == into < or >
    {
        return mid;
    }
    else if (nums[mid] < target)
    {
        return bsearch(nums, mid + 1, r, target);
    }
    else
    {
        return bsearch(nums, l, mid - 1, target);
    }
}

算法三:递归的合并等于情况的搜索

如果你完成了上面的代码,说明你对终止区间的长度有了很好的理解,让我们再继续:

第二关:循环

递归需要调用栈空间,不太好,我们把递归改成循环,用while循环结构改写算法一和算法二:

此时你应该思考一个问题,While内的条件应该怎么写,是否随着区间的开闭性而改变?

如果你完成了上面的代码,说明你对二分已经有了基本的认识,我们继续,用循环结构改写算法三,试试看。

如果你都完成了上面的任务,说明你对二分法已经有了很好的理解。

二分法主要在于下面两个方面:

  1. 怎样缩小区间
  2. 终止时候的区间长度与while内条件的关系

在编写二分算法时要注意以上两个问题,才能保证二分法是正确的,各种关于二分法的变式题都是围绕着两个问题展开的。

第三关:进阶

  1. 如果一个升序列表内存在可重复元素,请写出二分法找到目标值(如果目标值有多个返回第一个元素的下标,例如1,2,3,3,4,5寻找3,应该返回2);变式:(如果目标值有多个返回最后个元素的下标,例如1,2,3,3,4,5寻找3,应该返回3)
  2. 33. 搜索旋转排序数组
  3. 。。。

不想考虑边界

我们如果不想考虑边界问题,可以使用倍增二分法,也是我极力推荐的一种方法,相比于上面的二分法,其优点为算法参数好确定,最坏答案错了也不会死循环。

其模板为:

int x = 0; // 当前值,初始值为边界左端点(左开,不包括)

// 如何确定mx的值,其x0+mx必须是边界的右端点(右闭,包括),x0为x的初始值
for (int b = mx; b > 0; b /= 2) // 步移
{
    while (x + b <= mx && check(x + b)) // 倍增走
        x += b;
}

例题:P2440

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out1.txt", "w", stdout)

int n, k;
int arr[100005];

bool check(int l)
{
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        cnt += arr[i] / l;
    }

    return cnt >= k;
}

int main()
{
    scanf("%d %d", &n, &k);

    int mx = 0;

    for (int i = 1; i <= n; i++)
    {
        scanf("%d", arr + i);
        mx = max(mx, arr[i]);
    }

    int x = 0; // 当前值,初始值为边界左端点(左开,不包括)

    // 如何确定mx的值,其x0+mx必须是边界的右端点(右闭,包括),x0为x的初始值
    for (int b = mx; b > 0; b /= 2) // 步移
    {
        while (x + b <= mx && check(x + b)) // 倍增走
            x += b;
    }

    printf("%d", x);
    return 0;
}

未完待续

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值