一、贪心
贪心算法是两极分化很严重的算法。简单的问题会让你觉得理所应当,难一点的问题会让你怀疑人生。在解决贪心问题我们有时不仅要理解贪心策略,也要学会证明贪心策略的正确性来提高思维的严密性。
贪心算法通过局部最优选择寻求全局最优解。本文讲解最大子段和问题,采用从前往后累加策略,若当前和小于零则重置,时间复杂度 O(n)。同时讲解纪念品分组问题,先排序后使用双指针,将最小值与最大值配对尝试放入同一组,无法配对则最大值单独一组。文章提供了 C++ 代码实现及贪心策略的反证法与交换论证法证明,帮助读者理解贪心思维并掌握相关经典题型。

贪心算法是两极分化很严重的算法。简单的问题会让你觉得理所应当,难一点的问题会让你怀疑人生。在解决贪心问题我们有时不仅要理解贪心策略,也要学会证明贪心策略的正确性来提高思维的严密性。
贪心算法也称作贪心策略:企图用局部最优找出全局最优。 (1)把解决问题的过程分成若干步; (2)解决每一步时,都选择"当前看起来最优的"解法; (3)希望"得到全局的最优解"
总体特点:目光短浅 (1)对于绝大多数题目,贪心策略的提出并不是很难,难的是证明它是正确的。因为贪心算法相较于暴力枚举,每一步并不是把所有情况都考虑进去,而是只考虑当前看起来最优的情况。但是局部最优并不等于全局最优,所以我们必须要能严谨的证明我们的贪心策略是正确的。一般证明策略有:反证法,数学归纳法,交换论证法等等。 (2)当问题的场景不同时,贪心的策略也会不同。因此,贪心策略的提出是没有固定的套路和模板的。
(1)重点放在各种各样的策略上,把各种策略当成经验来吸收; (2)在平常学习的时候,我们尽可能的证明一下这个贪心策略是否正确,这样有利于培养我们严谨的思维。
链接:最大子段和

贪心想法:从前往后累加,我们会遇到下面两种情况: (1)目前的累加和 > 0:那么当前累加和还会对后续的累加和做出贡献,那我们就继续向后累加,然后更新结果。 (2)目前的累加和 < 0:对后续的累加和做不了⼀点贡献,直接大胆舍弃计算过的这⼀段,把累加和重置为 0,然后继续向后累加。 这样我们在扫描整个数组⼀遍之后,就能更新出最大子段和 注:考虑到如果结果可能为负数所以可以在累加过程中一边更新结果不用做过多断判断。全为负数时既为求最大值。
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
LL a[N];
int main(){
int n; cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
LL sum = 0;
LL ret = -1e20;
for(int i = 1; i <= n; i++){
sum += a[i];
ret = max(ret, sum);
if(sum < 0) sum = 0;
}
cout << ret << endl;
return 0;
}
(1)证明方法:反证法 (2)证明思路:抓住向后加的时候,只要不小于 0,就会一直加下的主线去在累加的过程中算出⼀段区间和 sum[a,b] < 0,如果不舍弃这⼀段,那么 [a, b] 段之间就会存在⼀点,「以某个位置为起点」就会「更优」,分为下面两种情况:
(1)情况一:在 ab 段存在⼀个 c 点,从这个 c 位置开始,「不越过」的累加和比从 a 开始的累加和更优:(元素全为负数不考虑,因为全为负数时该贪心策略本身就是正确的)

如果存在这⼀点,那么 sum[c,k] > sum[a,k],但是如果 sum[c,k] > sum[a,k],那么 sum[a,c − 1] < 0,这与我们的贪心策略矛盾既在区间加上元素 b 之前,区间中任意以 a 为左端点的一个子区间区间和都大于 0。故该情况不成立的. (2)情况二:在 ab 段存在⼀个点 c,从这个位置开始,「越过 b 或者刚好在 b」的累加和比从 a 开始的累加和更优:

如果存在这⼀点,那么:sum[c,b] > sum[a,b],但是如果 sum[c,b] > sum[a,b],那么 sum[a,c − 1] < 0,与我们的贪心策略矛盾既在区间加上元素 b 之前,区间中任意以 a 为左端点的一个子区间区间和都大于 0。故该情况不成立的. 故证毕
链接:纪念品分组

先将所有的纪念品排序,每次拿出当前的最小值 x 与最大值 y: (1)如果把 x+y ≤ w:就把这两个放在⼀起; (2)如果 x+y > w:说明此时最大的和谁都凑不到⼀起,y 单独分组,x 继续留下在进行下⼀次判断。 直到所有的物品都按照上述规则分配之后,得到的组数就是最优解
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 3e4 + 10;
int a[N];
int main(){
int n, w; cin >> w >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
int l = 1, r = n;
int ret = 0;
while(l <= r) // 相遇时给物品还未分配
{
if(a[l] + a[r] <= w){ l++; r--; }
else { r--; }
ret++;
}
cout << ret << endl;
return 0;
}
(1)证明方法:交换论证法 (2)证明思路:对于区间 [ai…aj],如果存在最优解但是 ai 与 aj 的分配方式与我们贪心解的分配方式不一样,我们通过调整方式调整成贪心解
(1) a[i] + a[j] > w 时: 贪心解会把 a[j] 单独分组,a[i] 留待下次考虑. 最优解也必定会把 a[j] 单独分组,因为没有更小的 a[i] 值与 a[j] 组合。此时贪心解与最优解一致。 (2)a[i] + a[j] ≤ w 时: 贪心解会把两者组合分在一个组里面;最优解可能有以下几种情况 情况一: ◦a[j] 和 a[k] 和一组: ▪如果 a[i] 单独⼀组的话,a[j] + a[k] 为一组,可以调整交换 a[i] 和 a[k],此时并不影响结,与贪心解一致 ▪如果 a[i] 和 a[l] 一组的话,交换 a[l] 和 a[k],就会变成 (a[i] + a[j]),(a[l] + a[k]),总体变成 a[l] + a[k] ≤ a[j] + a[k] ≤ w,不影响最终结果,和贪心解⼀致 情况二: ◦a[j] 单独一组: ▪如果 a[i] 也单独⼀组的话,最优解还不如贪心解分的组少,矛盾。 ▪如果 a[i] 和另⼀个 a[k] 组的话,我们可以把 a[k] 和 a[j] 交换,此时并不影响结,与贪心解一致 总之:综上所述,我们可以通过不断的「调整」,使的最优解在「不改变其最优性」的前提下,变得和贪心解一致。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online