【刷穿 LeetCode】881. 救生艇 : 归纳法证明贪心解为最优解之一

简介: 【刷穿 LeetCode】881. 救生艇 : 归纳法证明贪心解为最优解之一

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 881. 救生艇 ,难度为 中等


Tag : 「贪心」、「双指针」


i 个人的体重为 people[i],每艘船可以承载的最大重量为 limit


每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit


返回载到每一个人所需的最小船数。(保证每个人都能被船载)。


示例 1:


输入:people = [1,2], limit = 3
输出:1
解释:1 艘船载 (1, 2)
复制代码


示例 2:


输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
复制代码


示例 3:


输入:people = [3,5,3,4], limit = 5
输出:4
解释:4 艘船分别载 (3), (3), (4), (5)
复制代码


提示:


  • 1 <= people.length <= 50000
  • 1 <= people[i] <= limit <= 30000


贪心



一个直观的想法是:由于一个船要么载两人要么载一人,在人数给定的情况下,为了让使用的总船数最小,要当尽可能让更多船载两人,即尽可能多的构造出数量之和不超过 limitlimit 的二元组。


先对 peoplepeople 进行排序,然后使用两个指针 lr 分别从首尾开始进行匹配:


  • 如果 people[l] + people[r] <= limitpeople[l]+people[r]<=limit,说明两者可以同船,此时船的数量加一,两个指针分别往中间靠拢;
  • 如果 people[l] + people[r] > limitpeople[l]+people[r]>limit,说明不能成组,由于题目确保人的重量不会超过 limitlimit,此时让 people[r]people[r] 独立成船,船的数量加一,r 指针左移。


我们猜想这样「最重匹配最轻、次重匹配次轻」的做法能使双人船的重量之和尽可能平均,从而使双人船的数量最大化。


接下来,我们使用「归纳法」证明猜想的正确性。


假设最优成船组合中二元组的数量为 c1c1,我们贪心做法的二元组数量为 c2c2


最终答案 = 符合条件的二元组的数量 + 剩余人数数量,而在符合条件的二元组数量固定的情况下,剩余人数也固定。因此我们只需要证明 c1 = c2c1=c2 即可。


通常使用「归纳法」进行证明,都会先从边界入手。


当我们处理最重的人 people[r]people[r](此时 rr 为原始右边界 n - 1n1)时:


  • 假设其与 people[l]people[l](此时 ll 为原始左边界 00)之和超过 limitlimit,说明 people[r]people[r] 与数组任一成员组合都会超过 limitlimit,即无论在最优组合还是贪心组合中,people[r]people[r] 都会独立成船;
  • 假设people[r] + people[l] <= limitpeople[r]+people[l]<=limit,说明数组中存在至少一个成员能够与people[l]people[l]成船:
  • 假设在最优组合中 people[l]people[l] 独立成船,此时如果将贪心组合 (people[l], people[r])(people[l],people[r]) 中的 people[l]people[l] 拆分出来独立成船,贪心二元组数量 c2c2 必然不会变大(可能还会变差),即将「贪心解」调整成「最优解」结果不会变好;
  • 假设在最优组合中,people[l]people[l]不是独立成船,又因此当前rr处于原始右边界,因此与people[l]people[l]成组的成员people[x]people[x]必然满足people[x] <= people[r]people[x]<=people[r]。 此时我们将people[x]people[x]people[r]people[r]位置进行交换(将贪心组合调整成最优组合),此时带来的影响包括:
  • people[l]people[l] 成组的对象从 people[r]people[r] 变为 people[x]people[x],但因为 people[x] <= people[r]people[x]<=people[r],即有 people[l] + people[x] <= people[l] + people[r] <= limitpeople[l]+people[x]<=people[l]+people[r]<=limit,仍为合法二元组,消耗船的数量为 11
  • 原本位置 xx 的值从 people[x]people[x] 变大为 people[r]people[r],如果调整后的值能组成二元组,那么原本更小的值也能组成二元组,结果没有变化;如果调整后不能成为组成二元组,那么结果可能会因此变差。
  • 综上,将 people[x]people[x]people[r]people[r] 位置进行交换(将贪心组合调整成最优组合),贪心二元组数量 c2c2 不会变大,即将「贪心解」调整成「最优解」结果不会变好。


对于边界情况,我们证明了从「贪心解」调整为「最优解」不会使得结果更好,因此可以保留当前的贪心决策,然后将问题规模缩减为 n - 1n1 或者 n - 2n2,同时数列仍然满足升序特性,即归纳分析所依赖的结构没有发生改变,可以将上述的推理分析推广到每一个决策的回合(新边界)中。


至此,我们证明了「贪心组合所得的总船数」不会比「最优组合所得总船数」更多,即贪心解是最优解之一。


代码:


class Solution {
    public int numRescueBoats(int[] people, int limit) {
        Arrays.sort(people);
        int n = people.length;
        int l = 0, r = n - 1;
        int ans = 0;
        while (l <= r) {
            if (people[l] + people[r] <= limit) l++;
            r--;
            ans++;
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:排序复杂度为 O(n\log{n})O(nlogn);双指针统计答案复杂度为 O(n)O(n)。整体复杂度为 O(n\log{n})O(nlogn)
  • 空间复杂度:O(\log{n})O(logn)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.881 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
索引
【LeetCode】环形链表II+结论证明
【LeetCode】环形链表II+结论证明
61 0
|
索引
【LeetCode】环形链表+结论证明
【LeetCode】环形链表+结论证明
58 0
|
5月前
|
算法 测试技术 C#
[二分查找双指针]LeetCode881: 救生艇
[二分查找双指针]LeetCode881: 救生艇
|
5月前
|
算法 测试技术
[二分查找双指针]LeetCode881: 救生艇
[二分查找双指针]LeetCode881: 救生艇
|
算法 C++ Python
每日算法系列【LeetCode 881】救生艇
每日算法系列【LeetCode 881】救生艇
【刷穿 LeetCode】1221. 分割平衡字符串 : 归纳法证明从「最小分割点」进行分割可以得到最优解
【刷穿 LeetCode】1221. 分割平衡字符串 : 归纳法证明从「最小分割点」进行分割可以得到最优解
|
5天前
|
Unix Shell Linux
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
本文提供了几个Linux shell脚本编程问题的解决方案,包括转置文件内容、统计词频、验证有效电话号码和提取文件的第十行,每个问题都给出了至少一种实现方法。
LeetCode刷题 Shell编程四则 | 194. 转置文件 192. 统计词频 193. 有效电话号码 195. 第十行
|
2月前
|
Python
【Leetcode刷题Python】剑指 Offer 32 - III. 从上到下打印二叉树 III
本文介绍了两种Python实现方法,用于按照之字形顺序打印二叉树的层次遍历结果,实现了在奇数层正序、偶数层反序打印节点的功能。
44 6
|
2月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
82 2
|
5天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口