贪心算法:每一步都选择当前最优解,期望通过局部最优达到全局最优。
动态规划:通过分解问题为子问题,存储并重用子问题的解,避免重复计算。
最简单的JS ACM代码举例
贪心算法:找零问题
javascript
function greedyCoinChange(coins, amount) {
coins.sort((a, b) => b - a); // 从大到小排序
let result = [];
for (let coin of coins) {
while (amount >= coin) {
result.push(coin);
amount -= coin;
}
}
return result;
}
console.log(greedyCoinChange([1, 5, 10, 25], 36)); // 输出: [25, 10, 1]
为什么是while不是if?
javascript
while (amount >= coin)
这里的意思是:只要当前金额 amount
大于或等于当前硬币面值 coin
,就一直使用当前面值的硬币,直到金额小于当前硬币面值为止。换句话说,while
会尽可能多地使用当前面值的硬币来减少金额。
javascript
if (amount >= coin)
这里的意思是:如果当前金额 amount
大于或等于当前硬币面值 coin
,就使用一枚硬币,将硬币加入结果数组,并减少金额。但是,if
只会执行一次判断,也就是说,它只会尝试使用一枚当前面值的硬币,然后就跳过当前硬币,转而处理下一个硬币。
动态规划:斐波那契数列
javascript
function fibonacci(n) {
let dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
console.log(fibonacci(10)); // 输出: 55
区别总结
-
贪心算法:每一步选择当前最优,不保证全局最优。
-
动态规划:通过存储子问题的解,确保全局最优。
贪心算法from top to down, 动态规划 from bottom to up
为什么用贪心算法?
-
简单高效:
-
贪心算法通常实现简单,代码量少,运行速度快。
-
动态规划需要存储子问题的解,空间和时间复杂度较高。
-
-
适用于特定问题:
-
对于一些特殊问题(如硬币面额是倍数关系),贪心算法可以得到全局最优解。
-
例如,如果硬币面额是
[1, 5, 10, 25]
,贪心算法在找零问题中总是最优的。
-
-
近似解:
- 当问题规模很大时,动态规划可能无法在合理时间内求解,而贪心算法可以快速给出一个近似解。
-
实际问题中的权衡:
- 在某些实际场景中,全局最优解并不是必须的,一个接近最优的解已经足够。
为什么用动态规划?
-
保证全局最优:
- 动态规划通过分解问题为子问题,并存储子问题的解,确保找到全局最优解。
-
适用范围广:
- 动态规划可以解决更复杂的问题,尤其是当贪心算法无法保证最优解时。
-
避免重复计算:
- 动态规划通过记忆化(存储子问题的解)避免了重复计算,提高了效率。
贪心算法 vs 动态规划:找零问题
以找零问题为例:
-
贪心算法:
-
简单高效,但可能不是全局最优。
-
例如,硬币面额为
[1, 3, 4]
,目标是6
:-
贪心算法返回
[4, 1, 1]
(3 个硬币)。 -
实际最优解是
[3, 3]
(2 个硬币)。
-
-
-
动态规划:
-
保证全局最优,但实现复杂。
-
对于同样的问题,动态规划会返回
[3, 3]
。
-
什么时候用贪心算法?
-
问题具有贪心选择性质:
-
即局部最优解能导致全局最优解。
-
例如,硬币面额是倍数关系时,贪心算法总是最优。
-
-
问题规模大,需要快速求解:
- 动态规划可能无法在合理时间内求解,而贪心算法可以快速给出一个解。
-
近似解足够:
- 当全局最优解不是必须时,贪心算法是一个很好的选择。
动态规划的找零问题实现
以下是动态规划的找零问题实现,保证全局最优:
javascript
function dpCoinChange(coins, amount) {
let dp = new Array(amount + 1).fill(Infinity); // 初始化 dp 数组
dp[0] = 0; // 金额为 0 时需要 0 个硬币
for (let i = 1; i <= amount; i++) {
for (let coin of coins) {
if (i - coin >= 0) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] === Infinity ? -1 : dp[amount]; // 返回最小硬币数
}
console.log(dpCoinChange([1, 3, 4], 6)); // 输出: 2 (最优解是 [3, 3])