反证法证明贪心算法的正确性 | Java 刷题打卡

简介: 反证法证明贪心算法的正确性 | Java 刷题打卡

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


题目描述



这是 LeetCode 上的 561. 数组拆分 I ,难度为 简单


Tag : 「贪心算法」


给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。


返回该 最大总和 。


示例 1:


输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4
复制代码


示例 2:


输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9
复制代码


提示:


  • 1 <= n <= 10^4104
  • nums.length == 2 * n
  • -10^4104 <= nums[i] <= 10^4104


贪心解法



我们先对数组进行排序。


由于每两个数,我们只能选择当前小的一个进行累加。


因此我们猜想应该从第一个位置进行选择,然后隔一步选择下一个数。这样形成的序列的求和值最大。


class Solution {
    public int arrayPairSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        int ans = 0;
        for (int i = 0; i < n; i += 2) ans += nums[i];
        return ans;
    }
}
复制代码


  • 时间复杂度:O(n\log{n})O(nlogn)
  • 空间复杂度:O(\log{n})O(logn)


证明



我们用反证法来证明下,为什么这样选择的序列的求和值一定是最大的:


猜想:对数组进行排序,从第一个位置进行选择,然后隔一步选择下一个数。这样形成的序列的求和值最大(下图黑标,代表当前被选择的数字)。


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


之所以我们能这么选择,是因为每一个被选择的数的「下一位位置」都对应着一个「大于等于」当前数的值(假设位置为 k ),使得当前数在 min(a,b) 关系中能被选择(下图红标,代表保证前面一个黑标能够被选择的辅助数)。


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


假如我们这样选择的序列求和不是最大值,那么说明至少我们有一个值选错了,应该选择更大的数才对。


那么意味着我们「某一位置」的黑标应该从当前位置指向更后的位置。


PS. 因为要满足 min(a, b) 的数才会被累加,因此每一个红标右移(变大)必然导致原本所对应的黑标发生「同样程度 或 不同程度」的右移(变大)


这会导致我们所有的红标黑标同时往后平移。


最终会导致我们最后一个黑标出现在最后一位,这时候最后一位黑标不得不与我们第 k 个位置的数形成一对。


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


我们看看这是求和序列的变化( k 位置前的求和项没有发生变化,我们从 k 位置开始分析):


  1. 原答案 = nums[k] + nums[k + 2] + ... + nums[n - 1]
  2. 调整后答案 = nums[k + 1] + nums[k + 3] + ... + nums[n - 2] + min(nums[n], nums[k])


由于 min(nums[n], nums[k]) 中必然是 nums[k] 被选择。因此: 调整后答案 = nums[k] + nums[k + 1] + nums[k + 3] + ... + nums[n - 2]


显然从原答案的每一项都「大于等于」调整后答案的每一项,因此不可能在「假想序列」中通过选择别的更大的数得到更优解,假想得证。


为什么要「证明」或「理解证明」?



证明的意义在于,你知道为什么这样做是对的


带来的好处是:


  1. 一道「贪心」题目能搞清楚证明,那么同类的「贪心」题目你就都会做了。否则就会停留在“我知道这道题可以这样贪心,别的题我不确定是否也能这样做”
  2. 在「面试」阶段,你可以很清晰讲解你的思路。让面试官从你的「思维方式」上喜欢上你( emmm 当然从颜值上也可以 :)

...


更多与证明/分析相关的题解:



765. 情侣牵手 : 为什么交换任意一个都是对的?:两种 100% 的解法:并查集 & 贪心

1579. 保证图可完全遍历 : 为什么先处理公共边是对的?含贪心证明 + 数组模板 ~

1631. 最小体力消耗路径 : 反证法证明思路的合法性

11. 盛最多水的容器 : 双指针+贪心解法【含证明】


最后



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


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


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


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

相关文章
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
92 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
92 2
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
20 0
|
4月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
57 6
|
4月前
|
搜索推荐 算法 Java
|
4月前
|
搜索推荐 算法 Java
经典排序算法之-----选择排序(Java实现)
这篇文章通过Java代码示例详细解释了选择排序算法的实现过程,包括算法的基本思想、核心代码、辅助函数以及测试结果,展示了如何通过选择排序对数组进行升序排列。
经典排序算法之-----选择排序(Java实现)
|
4月前
|
存储 算法 Java
LeetCode经典算法题:打家劫舍java详解
LeetCode经典算法题:打家劫舍java详解
72 2
|
4月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
54 1
|
4月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
64 1
|
4月前
|
算法 Java
HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java
HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java
52 0
下一篇
DataWorks