返回

LeetCode 198:纵横交错的屋顶,巧夺豪宅,看我如何攻克打家劫舍!

前端

巧用动态规划,破解打家劫舍谜题

导言

各位技术爱好者,欢迎踏入 LeetCode 198 的精彩世界,开启一场斗智斗勇的打家劫舍之旅。在这场智力博弈中,你的目标是巧妙地选择房屋,避开警报,尽可能搜刮更多战利品。

问题拆解

想象自己是一位身怀绝技的小偷,准备在一条沿街房屋中大显身手。每间房屋都藏匿着不同数量的现金,但狡猾的屋主设置了相互连通的防盗系统,一旦相邻房屋在同夜被盗,警报将立即响起。你的任务便是巧妙地选择房屋,避开防盗系统的耳目,尽可能地搜刮更多战利品。

动态规划:化繁为简

要解决这个难题,我们引入动态规划的思想。动态规划是一种将复杂问题分解成一系列较小、重叠子问题的技术。对于打家劫舍,我们可以将问题分解为子问题:对于房屋序列中的每一间房屋,我们有两个选择:

  1. 偷窃当前房屋,并跳过下一间房屋(以避免触发警报)
  2. 不偷窃当前房屋,直接跳到下一间房屋

算法流程:循序渐进

  1. 初始化: 创建两个数组,dpprev_dp,用于存储子问题的最优解。其中,dp[i] 表示偷窃到第 i 间房屋的最大收益,prev_dp[i] 表示偷窃到第 i-1 间房屋的最大收益。
  2. 递推: 对于每间房屋,计算两种选择下的收益:
    • 选择偷窃: dp[i] = prev_dp[i-2] + loot[i]
    • 选择不偷窃: dp[i] = prev_dp[i-1]
    • 比较两种收益,选择更大的值更新 dp[i]
  3. 结果: 最后,返回 dp[n],其中 n 为房屋总数,即偷窃到最后一家房屋的最大收益。

代码示例:Python

def rob(nums):
  """
  :type nums: List[int]
  :rtype: int
  """
  n = len(nums)
  if n == 0:
    return 0

  dp = [0] * n
  prev_dp = [0] * n

  for i in range(n):
    if i >= 2:
      dp[i] = max(prev_dp[i - 2] + nums[i], dp[i - 1])
    elif i == 1:
      dp[i] = max(nums[i], dp[i - 1])
    else:
      dp[i] = nums[i]

    prev_dp[i] = dp[i - 1]

  return dp[n - 1]

总结:后发制人

通过巧妙地运用动态规划,我们可以高效地解决打家劫舍问题。这个例子展示了动态规划在解决复杂问题中的强大作用,它可以将大问题分解成较小、易于解决的子问题,从而逐步推导出最优解。

常见问题解答

  1. 为什么动态规划能解决这个问题?
    动态规划通过将问题分解成较小的、重叠的子问题,逐层递推,最终得到最优解。它适用于具有重叠子问题的优化问题,而打家劫舍问题正是这样一种问题。

  2. dp 数组和 prev_dp 数组有什么区别?
    dp 数组存储着当前子问题的最优解,而 prev_dp 数组存储着上一个子问题的最优解。这是动态规划中常用的技巧,用于保存之前计算的结果,避免重复计算。

  3. 如何选择偷窃或不偷窃当前房屋?
    选择偷窃当前房屋,则可以获得当前房屋的收益,但需要跳过下一间房屋;选择不偷窃当前房屋,则可以直接跳到下一间房屋。我们比较两种选择下的收益,选择收益更大的一个。

  4. 为什么考虑两步之前的房屋(prev_dp[i-2])?
    防盗系统会触发警报,如果相邻房屋在同夜被盗。因此,偷窃当前房屋后,我们需要跳过下一间房屋,才能偷窃两步之后的房屋。

  5. 如何处理边界情况?
    当房屋数量为 0 时,没有房屋可偷,因此返回 0。当房屋数量为 1 时,只有一个房屋可偷,因此直接返回这个房屋的收益。