java 二分搜索获得大于目标数的第一位_遇到「最值问题」还在无脑动态规划?二分法考虑一下呗

目录

  1. 前言
  2. 二分法基础及变种结构
  3. 小试牛刀
  4. 打怪升级
  5. 出师试炼

前言

一般来说,遇到「最值问题」通用的方法都是动态规划,而有一类「最值问题」可以用其他方法更加巧妙、简单方便的解决,这类问题的常见问法是「使……最大值尽可能小」。

这类问题也是大厂笔试面试常见题型,2020 年美团笔试题、字节面试题中都出现过。

这类问题有三大特征:

  1. 求最值(一般是最小值)
  2. 值的搜索空间是线性的
  3. 对该值有一个限制条件,并且若 x 满足条件,则 [x, +∞) 都满足条件,反之 (-∞, x] 都不满足条件。
e5956e2b16fb17c680a5d2f79ce1c689.png

你说的我都懂,可问题是,这是啥意思呢?叉会腰,冷静一下。

为了方便大家理解这类问题到底是个什么玩意儿,本汪在这里列出 leetcode 上的一道题目作为例子:

给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

不熟悉二分法的同学初遇此题,看到关键词“分割成 m 份”、“最小”,就想到了动态规划。诚然此题具备了使用动态规划的一切前提条件,也确实可以通过动态规划做出来,但其时间复杂度为 O(n^2 * m) , 空间复杂度为 O(n * m).

如果你看了本汪的这篇文章,学会了用二分法解决这一类问题,那么时间复杂度可以优化到 O(n * logx), 空间复杂度可以优化到 O(1),并且思路非常简洁,代码实现也极其简单。


二分法基础及变种结构

在讲具体的方法之前,本汪先和大家一起回顾下二分法,熟悉二分法的同学可以直接跳过这部分。

最早接触二分思想的地方应该是在二分搜索中。(本质上类似快排中的 partition 思想)

二分法是在有序线性搜索空间内,利用有序性,每次搜索通过排除一半的剩余搜索空间,使 O(n) 级别的线性搜索优化到 O(logn) 级别的方法。

a664c91f4d487c7c6fd7e076d59cbf87.png

二叉树:我都会二分,不会还有人不会二分法吧?不会就让往西汪那小子教你

二分法的基本代码非常简单,这里就不多提了,下面用类 java 语言给出其针对「最值问题」的变种结构:

// nums 数组是搜索空间,m 是限制条件// check(x, m) 表示在搜索空间 nums 中的点 x,满足了限制条件 mpublic int binary(int[] nums, int m){     初始化 l, r 为搜索空间的起始点和终点    while(l > 1; //二分,一次搜索可以排除 mid 的左边或者右边(剩余搜索空间的一半)       if(check(mid, m)) r = mid; //因为这一类最值问题要找的是最小,mid 满足条件了,(mid, r] 就不是最小的答案了        else l = mid + 1; // mid 不满足条件,根据有序性,[l, mid] 里的数全不满足条件    }   return r;}

根据结构我们可以看出,遇到这类问题的时候只需要找到「搜索空间」、「检查条件」,然后套用结构就能轻轻松松地解决问题。

下面就让我们来用二分法解决刚刚提到的问题。


小试牛刀

题目

给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。

思路

前文提及,解决本题只需要找到「搜索空间」、「检查条件」,剩下的就是闭着眼睛套结构的事儿了。

先找「搜索空间」,因为此类问题的搜索空间都是线性的,所以找到了起始点终点,也就找到了搜索空间。

看问题描述 “子数组各自和的最大值最小”,也就是说我们搜索的点是 "子数组各自和的最大值",那么搜索空间的起始点是数组 nums 中的最大值,即 min(nums),搜索空间的终点是数组 nums 中的所有元素之和,即 sum(nums)。因此我们找到了搜索空间,为 [min(nums), sum(nums)].

再看「检查条件」。给定搜索空间 nums,和点 x,判断 x 是否满足限制条件 m。

本题的条件是“子数组各自和的最大值”,也就是说 x 和 m 个子数组各自和相比较,都要大于或者等于它们。

「搜索空间」、「检查条件」都找到了,下面闭着眼睛套结构吧~

a37b7755e6a9d4b3ecfa1e1a549f7691.png

(瑟瑟发抖.jpg) 好的,这就上代码

代码

class Solution {   // 这个方法就是直接套结构    public int splitArray(int[] nums, int m) {        long l = 0, r = 0;        for(int num: nums) {r += num; l = Math.max(l, num);} // 初始化 l 和 r        while(l > 1;         if(check(nums, mid, m)) r = mid;         else l = mid + 1;        }        return (int)r;    }        public boolean check(int[] nums, long a, int m) { // 给定搜索空间中的点 a,看它是否大于等于所有子数组的各自和     int cnt = 1;     long sum = 0;     for(int n: nums) {      sum += n;      if(sum > a) {       sum = n;       cnt ++;       if(cnt > m) return false;      }     }     return true;    }}

打怪升级

咱们再来看一道题,小伙伴们可以先自己思考,有思路的话自己动手实现下。再看看本汪给出的思路,加深理解。

题目

珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。

珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。

11910fa07558309f1458280c33047219.png

往西汪:我请你们吃香蕉,可以给我点个赞吗

思路

老规矩,先找「搜索空间」,再找「检查条件」,最后闭着眼睛套结构。

「搜索空间」:H 小时内必须吃完 sum(piles) 根香蕉,所以初始点为 sum(piles) / H,一次最多只能吃一堆,所以终点为 max(piles)。因此,搜索空间为 [sum(piles) / H, max(piles)]。

「检查条件」:“在 H 小时内吃掉所有香蕉”,因此以 K 个 / 小时 的速度吃完香蕉的用时要小于等于 H。

下面就闭着眼睛套结构吧。

代码

class Solution {    public int minEatingSpeed(int[] piles, int H) {        int l = 1, r = 0; // 这里本汪为了代码的简洁性,在不影响时间复杂度的情况下,直接让初始点为 1        for(int i: piles) r = Math.max(r, i); // 一次最多吃一堆        while(l > 1;            if(check(piles, mid, H)) r = mid;            else l = mid + 1;        }        return r;    }    boolean check(int[] piles, int K, int H){        int t = 0;        for(double i: piles){            t += Math.ceil(i / K); // 一堆香蕉共计 i 个,需要 ⌈i / K⌉ 个小时吃完            if(t > H) return false; // 警卫回来了还没吃完        }        return true;    }}

出师试炼

我非常喜欢看我文章的小伙伴,个个都是人才,说话又好听,脑瓜子又聪明,还很主动的给我文章点赞。我相信砍翻两个小怪之后,你们已经是这类问题的专家了,下面就给几道题目供大伙随便玩玩。

56bf1dd657152e0f6c715b6465f1eff4.png

超喜欢往西汪的文章的

关卡 1

你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。

视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0]并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。

我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段([0, T])。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。

关卡 2

牛牛有 n 件带水的衣服,干燥衣服有两种方式。

一、是用烘干机,可以每分钟烤干衣服的 k 滴水。

二、是自然烘干,每分钟衣服会自然烘干 1 滴水。

烘干机比较小,每次只能放进一件衣服。

注意,使用烘干机的时候,其他衣服仍然可以保持自然烘干状态,现在牛牛想知道最少要多少时间可以把衣服全烘干。

关卡 3

你太强了!我已经没有更多的题目给你玩了。你可以凭借这套武功闯荡江湖了!

……

……

……

等等,我说笑呢。你这小身板,要不先进我的其他训练营再练练呗?

啥,你问我的其他训练营在哪里?动动小手,点个关注,新的训练营已经在建设了,嘿嘿。

0ab4b24c5aa3ecc1f8a23ddb38e862c5.png

咳咳,暗示的很明显了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值