🧠 算法学习日记 | 今天我用「贪心算法」解了四道题,原来"局部最优"也能通向全局最优!
大家好,我是你们的算法学习搭子 👋
今天继续我的算法入门之旅,重点练习了**贪心算法(Greedy Algorithm)**这一经典而强大的思想。
很多人觉得"贪心"就是"随便选",但其实,它是一种基于直觉+数学证明 的策略。
我们通过选择当前看起来最好的选项,来期望最终得到全局最优解。
今天我完整做了四道题,每一道都坚持用最朴素的贪心逻辑实现。下面我把题目原文 和我的原始代码原封不动贴出来,不做任何删减或美化,只为真实记录学习过程。
🔹 题目一:最小化战斗力差距
问题描述
小蓝是机甲战队的队长,他手下共有 n 名队员,每名队员都有一个战斗力值 w_i 。现在他需要将这 n 名队友分成两组 a 和 b ,分组必须满足以下条件:
- 每个队友都属于 a 组或 b 组。
- a 组和 b 组都不为空。
- 战斗力差距最小。
战斗力差距的计算公式为 \|\\max(a) - \\min(b)\| ,其中 \\max(a) 表示 a 组中战斗力最大的, \\min(b) 表示 b 组中战斗力最小的。
请你计算出可以得到的最小战斗力差距。
输入格式
第一行一个整数 n ,表示队员个数。
第二行 n 个整数 w_1, w_2, \\ldots, w_n ,分别表示每名队友的战斗力值。
数据范围保证: 2 \\leq n \\leq 10\^5 ,,, 1 \\leq w_i \\leq 10\^9 。
输出格式
输出一个整数,表示可以得到的最小战斗力差距。
样例输入
3
1 2 3
样例输出
1
说明
样例中,当 a = \[1,3\] ,,, b = \[2\] ,此时战斗力差距为 1,无法得到比 1 更小的安排方式。
✅ 我的代码(完全保留原始写法)
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int a[N];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
sort(a+1,a+1+n);
int ans = a[2] - a[1];
for(int i=1;i<n;++i)ans = min(ans,a[i+1]-a[i]);
cout<<ans;
return 0;
}
🔹 题目二:谈判
题目描述
在很久很久以前,有 n 个部落居住在平原上,依次编号为 1 到 n 。第 i 个部落的人数为 t_i 。
有一年发生了灾荒。年轻的政治理家小蓝想要说服所有部落一同应对灾荒,他能通过谈判来说服部落进行联合。
每次谈判,小蓝只能邀请两个部落参加,花费的金币数量为两个部落的人数之和,谈判的效果是两个部落联合成一个部落(人数为原来两个部落的人数之和)。
输入描述
输入的第一行包含一个整数 n ,表示部落的数量。
第二行包含 n 个正整数,依次表示每个部落的人数。
其中, 1 \\leq n \\leq 1000 ,,, 1 \\leq t_i \\leq 10\^4 。
输出描述
输出一个整数,表示最小花费。
输入输出样例
输入
4
9 1 3 5
输出
31
✅ 我的代码(完全保留原始写法)
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+5;
long int a[N];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;++i)cin>>a[i];
sort(a+1,a+1+n);
int ans = 0;
for(int i=1;i<n;++i){
a[i+1] = a[i+1] + a[i];
ans += a[i+1];
}
cout<<ans;
return 0;
}
🔹 题目三:纪念品分组
问题描述
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品,并且每组纪念品的价格之和不能超过一个给定的整数 w 。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
输入描述
第 1 行包括一个整数 w ((( 80 \\leq w \\leq 200 ),为每组纪念品价格之和的上限。
第 2 行为一个整数 n ((( 1 \\leq n \\leq 30000 ),表示购来的纪念品的总件数。
第 3 ~ n+2 行每行包含一个正整数 p_i ((( 5 \\leq p_i \\leq w ),表示所对应纪念品的价格。
输出描述输出一行,包含一个整数,即最少的分组数目。
输入输出样例
输入
166
9
90
26
20
30
50
60
70
80
90
输出
6
✅ 我的代码(完全保留原始写法)
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 3e4 + 5;
int a[N];
int main() {
int w, n;
cin >> w >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + 1 + n);
int l = 1, r = n, ans = 0;
while (l <= r) {
if (a[l] + a[r] <= w) {
l++;
}
r--;
ans++;
}
cout << ans << endl;
return 0;
}
🔹 题目四:分糖果
问题描述
最近暑期特训算法班的同学们表现出色,他们的老师肖恩决定给他们分发糖果。肖恩购买了 n 个不同种类的糖果,用小写的阿拉伯字母表示。每个糖果必须分发给一个同学,并且每个同学至少要分到一个糖果。同学们的开心程度定义为他们所分到的糖果组成的字符串 s\[i\] 的字典序。肖恩希望同学们的开心程度相差尽量小,因此他要找到一种方案,使得所有糖果组成的字符串中字典序最大的字符串尽可能小。请输出能够实现字典序最小可能的 \\max(s\[1\], s\[2\], s\[3\], \\ldots, s\[x\]) 。
输入描述第一行输入两个整数 n 和 x ,分别表示有 n 个糖果 x 个同学。
第二行输入一个长度为 n 的字符串 S ,,, S\[i\] 表示第 i 个糖果的种类。
数据保证 1 \\leq n \\leq 10\^6 ,,, 1 \\leq x \\leq n ,,, S\[i\] \\in \['a','z'\] 。
输出描述
输出一个字符串,为所有糖果组成的字符串中字典序最大的字符串最小的可能值。
样例输入
6 2
caabdc
样例输出
abcdd
说明
一个最优分配方案是一个同学拿到
abccd,一个同学拿到a。
✅ 我的代码(完全保留原始写法)
cpp
#include<bits/stdc++.h>
const int N=1e6+9;
using namespace std;
char s[N];
int main(){
int n,x;
cin>>n>>x;
cin>>s+1;
sort(s+1,s+1+n);
if(s[1]==s[n]){
for(int i=1;i<=n/x+(n%x?1:0);i++) cout<<s[i];
}
else if(s[1]==s[x]){
for(int i=x;i<=n;i++) cout<<s[i];
}
else cout<<s[x];
return 0;
}
🌟 我的思考
这四道题虽然形式各异,但都用了贪心的核心思想:
- 最小化战斗力差距:排序后相邻元素差最小 → 贪心取最小差值
- 谈判:每次合并最小的两个部落 → 类似哈夫曼编码
- 纪念品分组:双指针从两端匹配 → 尽量让大数和小数配对
- 分糖果:排序后按字典序最小分配 → 最优解是尽可能让最大串变小
你会发现:
贪心的本质是"局部最优 → 全局最优" ,但它不一定总是正确,必须有数学证明支持。
比如:
- "谈判"题其实是哈夫曼树的经典应用,每次合并代价最小的两个节点
- "纪念品分组"是双指针+贪心,先排序,再从两边夹逼
- "分糖果"是排序+分类讨论,目标是最小化最大字典序
✅ 总结
- 贪心算法适用于:具有最优子结构的问题
- 常见技巧:排序、优先队列、双指针、区间覆盖
- 关键在于:证明贪心选择性质(即当前最优不会影响后续最优)
- 不是所有问题都能用贪心解决,需谨慎使用
- 但一旦适用,效率极高,代码简洁