返回

LeetCode 198:打家劫舍——层层推进的智慧,算法背后的谋略

后端

“打家劫舍”:经典算法问题

在算法的世界中,“打家劫舍”是一个经典问题,因为它不仅考验我们的编程技能,更考验我们的算法思维。让我们深入探讨这个问题,了解其背后的原理和解决方法。

问题背景

想象一个小偷准备劫掠一排房屋,这些房屋沿一条直线排列,每间房屋都装有一定金额的钱。然而,小偷只能劫掠相邻的房屋,而且不能连续劫掠两间相邻的房屋,因为他不想被抓。小偷的目标是最大化他劫掠的总金额,你能帮他吗?

解决方法:动态规划

为了解决这个问题,我们可以采用动态规划的方法。动态规划是一种自顶向下方法,它将问题分解成一系列重叠子问题,然后逐层解决这些子问题,最终得到整体问题的最优解。

状态定义

对于“打家劫舍”问题,我们可以定义一个状态 dp[i],表示考虑前 i 间房屋时小偷能偷到的最大金额。

状态转移方程

对于 i > 1 的情况,我们可以考虑两种策略:

  1. 偷第 i 间房屋: 此时,小偷可以得到 nums[i] 的金额,但不能偷相邻的第 i-1 间房屋,因此他只能考虑前 i-2 间房屋的收益,即 dp[i] = nums[i] + dp[i-2]
  2. 不偷第 i 间房屋: 此时,小偷可以偷相邻的第 i-1 间房屋,因此他可以考虑前 i-1 间房屋的收益,即 dp[i] = dp[i-1]

我们取这两种策略中的较大值作为 dp[i],即 dp[i] = max(dp[i-1], nums[i] + dp[i-2])

代码示例

def rob(nums):
    n = len(nums)
    if n == 0:
        return 0
    if n == 1:
        return nums[0]

    dp = [0] * (n+1)
    dp[1] = nums[0]

    for i in range(2, n+1):
        dp[i] = max(dp[i-1], nums[i-1] + dp[i-2])

    return dp[n]

通过动态规划,我们可以高效地解决“打家劫舍”问题,时间复杂度为 O(n),空间复杂度为 O(n)

贪心算法

除了动态规划,我们还可以使用贪心算法解决此问题。贪心算法是一种自底向上方法,它在每一步都做出局部最优选择,期望最后得到全局最优解。

算法思路

对于“打家劫舍”问题,贪心算法的思路是:

  1. 初始化两个变量:不偷,分别表示考虑前 i 间房屋时,偷或不偷第 i 间房屋所能获得的最大金额。
  2. 遍历房屋数组:
    • 如果偷第 i 间房屋能获得的金额大于不偷第 i 间房屋,则偷第 i 间房屋,更新 nums[i] + 不偷
    • 否则,不偷第 i 间房屋,更新 不偷
  3. 返回 不偷 中较大的值。

代码示例

def rob_greedy(nums):
    偷 = 0
    不偷 = 0

    for num in nums:
        偷_新 = 不偷 + num
        不偷_新 = max(偷, 不偷)

        偷 = 偷_新
        不偷 = 不偷_return max(偷, 不偷)

贪心算法的时间复杂度同样为 O(n),空间复杂度为 O(1)

结论

无论是动态规划还是贪心算法,都能有效解决“打家劫舍”问题。动态规划从全局最优的角度考虑,贪心算法则从局部最优的角度出发,各有千秋。在实际应用中,我们可以根据问题的具体情况选择最合适的算法。

常见问题解答

  1. 为什么动态规划的方法更适合处理这个问题?

因为动态规划采用自顶向下方法,可以考虑所有可能的方案,并从全局最优的角度做出决策。而贪心算法虽然高效,但可能会错过某些更优的方案。

  1. 贪心算法是否适用于任何场景?

不适用于任何场景。贪心算法适用于局部最优能导致全局最优的情况。对于“打家劫舍”问题,贪心算法可以找到局部最优解,但在某些情况下可能无法得到全局最优解。

  1. 动态规划和贪心算法的优缺点是什么?

动态规划

  • 优点:能得到全局最优解,适用于复杂问题。
  • 缺点:时间复杂度可能较高。

贪心算法

  • 优点:时间复杂度较低,适用于局部最优能导致全局最优的问题。
  • 缺点:可能无法得到全局最优解。
  1. 什么时候使用动态规划?

当问题满足以下条件时,可以使用动态规划:

  • 问题可以分解成重叠子问题。
  • 子问题的最优解可以组合成整体问题的最优解。
  1. 什么时候使用贪心算法?

当问题满足以下条件时,可以使用贪心算法:

  • 每一步都能做出局部最优选择。
  • 局部最优选择能最终导致全局最优解。