算法设计里,贪心和动态规划经常一起出现,但它们解决问题的方式其实差得很远。前者盯着当前这一步,觉得眼前最划算就先拿;后者会把子问题结果存下来,少走回头路。不是谁高级谁低级,而是问题本身决定了该用哪种思路。
一、先把两个概念说清楚
1.1 贪心算法(Greedy Algorithm)
贪心算法的做法很直接:每一步都选当前看起来最好的方案,而且选了就不再改。它依赖两个条件:一是问题有'贪心选择性质',也就是局部最优能推到全局最优;二是有'最优子结构',整体最优能拆成子问题最优。
这种方法的好处是实现简单,运行也快。但坑也很明显——只要局部选择和整体最优不是一回事,结果就会跑偏。
1.2 动态规划(Dynamic Programming, DP)
动态规划处理的是另一类问题。它把大问题拆成很多重叠子问题,把算过的结果记下来,再用这些结果拼出最终答案。核心前提通常是'最优子结构'和'子问题重叠'。
实际写 DP 时,我一般先想三件事:状态怎么定义,状态怎么转移,边界条件是什么。这个顺序看起来朴素,但少一步都容易写歪。DP 的代价也很明确:状态多了,空间和推导难度都会上来。
二、几个例子看差别
2.1 找零钱:有些面额贪心能过,有些不行
题目很常见:给定硬币面额 coins 和金额 amount,求凑出金额所需的最少硬币数,凑不出就返回 -1。
场景 1:面额满足贪心性质,比如 [1,5,10,25]
像这种标准面额,先拿大面额通常没问题,因为它本身就设计成了适合贪心的结构。代码也很短,直接按面额降序取就行。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 贪心找零:仅适用于硬币面额满足贪心选择性质的场景
int greedyChange(vector<int>& coins, int amount) {
sort(coins.rbegin(), coins.rend());
int count = 0;
for (int coin : coins) {
if (amount <= 0) break;
int num = amount / coin;
count += num;
amount -= num * coin;
}
return amount == ? count : ;
}
{
vector<> coins = {, , , };
amount = ;
res = (coins, amount);
(res == ) {
cout << << endl;
} {
cout <<

