贪心算法(一) | 豆包MarsCode AI刷题

152 阅读4分钟

贪心与dp有什么区别呢

在刚接触算法的时候,我会感觉到贪心算法和动态规划有一点相似,他们在实际应用上确实是有重叠的部分,但实际上还是存在很大区别的,那么我们今天首先就来区分一下两者。

动态规划(Dynamic Programming,简称DP)和贪心算法(Greedy Algorithm)都是解决优化问题的常用方法。

动态规划
  • 动态规划是一种通过把原问题分解为相互重叠的子问题来求解复杂问题的方法。如果一个问题可以分解成若干个子问题,并且这些子问题之间还有重叠的部分,那么使用动态规划是有效的。
  • 最优子结构:问题的最优解包含了其子问题的最优解。比如说我们像之前我们想求第n个月的兔子数量,建立dp数组,其实也包含了前面月份的解。
  • 重叠子问题:子问题之间有重叠部分,即在求解过程中会多次遇到同一个子问题。
  • 无后效性:一旦某个状态被确定,就不会再受到后续决策的影响。
贪心算法
  • 贪心算法是在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,每一步都采取当前状态下最好或最优的选择。
  • 局部最优选择:每次决策只考虑当前情况下的最佳选择,而不考虑未来的后果。
  • 不可回溯性:一旦做出了一个选择,就不再更改,即使后续发现这个选择不是全局最优的。

并且从复杂度来看,动态规划通常需要更多的存储空间来保存中间结果,以避免重复计算,因此空间复杂度较高。贪心算法因为不需要保存所有中间结果,所以通常具有较低的空间复杂度。

题目示例

小U正在准备穿越一片广阔的沙漠。沙漠里有多个绿洲,每个绿洲设有补给站。所有补给站的收费相同,但提供的水量不同。从起点到终点共有 (D) 公里,小U需要规划在哪些补给站停留,以保证整个旅途中水的供应。

起点到终点的距离为 (D) 公里,小U初始携带 (W) 升水,每行走 1 公里消耗 1 升水。小U希望通过最少的补给次数安全到达终点。每个补给站的位置由 position[i] 表示,距离起点的公里数,supply[i] 表示该站可以提供的水量,单位为升。

请你帮助小U计算,要安全到达终点至少需要补给几次,或者判断是否根本无法到达终点,此时输出-1。

题目分析

我们的目标是让小U尽可能少地进行补给,同时确保他能够安全到达终点,那么就用到我们的贪心策略:

  1. 优先选择最近的补给站:每次选择最近的补给站进行补给,以确保小U能够在当前水的情况下继续前进。
  2. 最大化每次补给的水量:在选择补给站时,尽量选择提供水量最多的补给站,以减少总的补给次数。

我们需要遍历补给站:

  • 使用一个优先队列(最大堆)来存储可以到达的补给站及其提供的水量。
  • 遍历每个补给站,如果当前水足够到达该补给站,则将其加入优先队列。
  • 如果当前水不足以到达下一个补给站,从优先队列中取出提供水量最多的补给站进行补给,并增加补给次数。

题目解答

import heapq
def solution(D, W, position, supply):
    N = len(position)
    position.append(D)
    supply.append(0)
    current_water = W
    current_position = 0
    max_heap = []
    refills = 0
    for i in range(N + 1):
        distance_to_next = position[i] - current_position
        while current_water < distance_to_next:
            if not max_heap:
                return -1
            current_water += -heapq.heappop(max_heap)
            refills += 1
        current_water -= distance_to_next
        current_position = position[i]
        heapq.heappush(max_heap, -supply[i])
    
    return refills