贪心?DP?

一、何时用贪心?关键条件

  1. 贪心选择性质
    当前局部最优选择一定能导致全局最优解,且不需要回溯或比较其他可能性。
    例如:找零问题时,优先用大面额硬币一定能得到最少硬币数(在硬币面额是倍数关系时)。

  2. 最优子结构
    问题的全局最优解可以通过局部最优解递推得到,且子问题独立。
    例如:活动选择问题中,每次选最早结束的活动,剩余子问题仍然是同类优化问题。

  3. 无后效性
    当前选择一旦做出,后续决策不受之前选择的影响。
    例如:Dijkstra算法中,一旦确定某个节点的最短路径,不会被后续更新。


二、典型贪心算法问题举例

1. 区间调度问题(Activity Selection Problem)
  • 问题:选择最多数量的互不重叠的区间。

  • 贪心策略:每次选结束时间最早的区间,给后续留更多空间。

  • 为何不用DP:DP需要记录所有子集组合(O(2ⁿ)),而贪心只需排序后遍历(O(n log n))。

2. 霍夫曼编码(Huffman Coding)
  • 问题:用最小二进制编码压缩数据(频率高的字符用短编码)。

  • 贪心策略:每次合并频率最低的两个节点,构建二叉树。

  • 为何不用DP:合并顺序的局部最优直接导致全局最优,无需保存中间状态。

3. 找零问题(Coin Change, 特定面额)
  • 问题:用最少数量的硬币凑出金额(如面额为1,5,10)。

  • 贪心策略:每次选最大面额硬币。

  • 为何不用DP:当硬币面额是倍数关系(如1,5,10)时贪心成立,否则需DP(如面额1,3,4,凑6时贪心失效)。

4. 最小生成树(Prim/Kruskal算法)
  • 问题:连接所有节点的边权值和最小的树。

  • 贪心策略

    • Kruskal:每次选权值最小的边,且不形成环。

    • Prim:从任意节点开始,每次选连接已选集合的最小边。

  • 为何不用DP:生成树的子问题无需记录路径,贪心的局部选择直接保证全局最优。

5. Dijkstra算法(无负权边的最短路径)
  • 问题:单源最短路径(无负权边)。

  • 贪心策略:每次从优先队列中选当前距离最短的节点,松弛其邻边。

  • 为何不用DP(如Bellman-Ford):无负权边时,局部最短路径即全局最短,无需重复松弛。


三、贪心 vs DP的对比案例

案例1:背包问题
  • 0-1背包问题:必须用DP,因为每个物品选/不选会影响后续决策。

  • 分数背包问题:可以用贪心(按单位价值排序,优先拿高价值密度的物品)。

案例2:股票买卖问题
  • 一次交易(LeetCode 121):贪心(记录历史最低点,计算当前最大利润)。

  • 多次交易(LeetCode 122):贪心(所有上涨区间都买入)。

  • 含冷却期(LeetCode 309):必须用DP,因为当前状态依赖前两天的决策。


四、如何判断是否用贪心?

  1. 尝试举反例:验证贪心策略是否能覆盖所有情况。
    例如:非倍数面额的找零问题(如1,3,4),贪心可能失败(凑6:4+1+1 vs 3+3)。

  2. 检查问题结构:若问题需要“所有可能的组合”或“撤销之前选择”,则必须用DP。

  3. 数学证明:通过归纳法或交换论证证明贪心选择的正确性。


五、总结

  • 用贪心的信号

    • 问题可以通过单向的局部最优选择推进。

    • 无需保存历史状态或比较多种路径。

    • 贪心策略能被严格证明正确。

  • 用DP的信号

    • 问题需要穷举所有可能性或存在重叠子问题

    • 当前决策依赖之前的所有选择(如背包问题)。

经典口诀

  • 贪心:“短视但高效,一步定终身”。

  • DP:“记忆化搜索,全局揽乾坤”。

贪心算法不是动态规划(DP)。虽然贪心算法和动态规划都有通过局部最优解来解决全局最优解,但二者存在明显区别。贪心算法在得到局部最优解时,不考虑之前的最优解,只考虑当前状态的局部最优解;而动态规划则会考虑到之前状态的最优解,并且注重寻找状态转移方程[^2]。 动态规划是一种算法思想,是对问题不断细分,时刻更新、回溯直至最后结束执行,也可称为带着记忆的递归,是自底向上的回溯法,可能在某个节点不是最优解,但最终一定是最优解。贪心算法则通常是从当前状态出发,做出当前看似最优的选择,不进行回溯,且不一定能得到全局最优解[^2][^3]。 以下是一个简单的贪心算法示例(找零问题)和动态规划示例(背包问题)对比: ```python # 贪心算法 - 找零问题 def greedy_change(amount, coins): coins.sort(reverse=True) change = [] for coin in coins: while amount >= coin: amount -= coin change.append(coin) return change amount = 63 coins = [25, 10, 5, 1] result = greedy_change(amount, coins) print("贪心算法找零结果:", result) # 动态规划 - 01背包问题 def knapsack(weights, values, capacity): n = len(weights) dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)] for i in range(1, n + 1): for w in range(1, capacity + 1): if weights[i - 1] <= w: dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]], dp[i - 1][w]) else: dp[i][w] = dp[i - 1][w] return dp[n][capacity] weights = [2, 3, 4, 5] values = [3, 4, 5, 6] capacity = 8 result = knapsack(weights, values, capacity) print("动态规划01背包问题结果:", result) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值