LeetCode 198:打家劫舍——层层推进的智慧,算法背后的谋略
2023-11-19 02:37:31
“打家劫舍”:经典算法问题
在算法的世界中,“打家劫舍”是一个经典问题,因为它不仅考验我们的编程技能,更考验我们的算法思维。让我们深入探讨这个问题,了解其背后的原理和解决方法。
问题背景
想象一个小偷准备劫掠一排房屋,这些房屋沿一条直线排列,每间房屋都装有一定金额的钱。然而,小偷只能劫掠相邻的房屋,而且不能连续劫掠两间相邻的房屋,因为他不想被抓。小偷的目标是最大化他劫掠的总金额,你能帮他吗?
解决方法:动态规划
为了解决这个问题,我们可以采用动态规划的方法。动态规划是一种自顶向下方法,它将问题分解成一系列重叠子问题,然后逐层解决这些子问题,最终得到整体问题的最优解。
状态定义
对于“打家劫舍”问题,我们可以定义一个状态 dp[i]
,表示考虑前 i
间房屋时小偷能偷到的最大金额。
状态转移方程
对于 i > 1
的情况,我们可以考虑两种策略:
- 偷第
i
间房屋: 此时,小偷可以得到nums[i]
的金额,但不能偷相邻的第i-1
间房屋,因此他只能考虑前i-2
间房屋的收益,即dp[i] = nums[i] + dp[i-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)
。
贪心算法
除了动态规划,我们还可以使用贪心算法解决此问题。贪心算法是一种自底向上方法,它在每一步都做出局部最优选择,期望最后得到全局最优解。
算法思路
对于“打家劫舍”问题,贪心算法的思路是:
- 初始化两个变量:
偷
和不偷
,分别表示考虑前i
间房屋时,偷或不偷第i
间房屋所能获得的最大金额。 - 遍历房屋数组:
- 如果偷第
i
间房屋能获得的金额大于不偷第i
间房屋,则偷第i
间房屋,更新偷
为nums[i] + 不偷
。 - 否则,不偷第
i
间房屋,更新不偷
为偷
。
- 如果偷第
- 返回
偷
或不偷
中较大的值。
代码示例
def rob_greedy(nums):
偷 = 0
不偷 = 0
for num in nums:
偷_新 = 不偷 + num
不偷_新 = max(偷, 不偷)
偷 = 偷_新
不偷 = 不偷_新
return max(偷, 不偷)
贪心算法的时间复杂度同样为 O(n)
,空间复杂度为 O(1)
。
结论
无论是动态规划还是贪心算法,都能有效解决“打家劫舍”问题。动态规划从全局最优的角度考虑,贪心算法则从局部最优的角度出发,各有千秋。在实际应用中,我们可以根据问题的具体情况选择最合适的算法。
常见问题解答
- 为什么动态规划的方法更适合处理这个问题?
因为动态规划采用自顶向下方法,可以考虑所有可能的方案,并从全局最优的角度做出决策。而贪心算法虽然高效,但可能会错过某些更优的方案。
- 贪心算法是否适用于任何场景?
不适用于任何场景。贪心算法适用于局部最优能导致全局最优的情况。对于“打家劫舍”问题,贪心算法可以找到局部最优解,但在某些情况下可能无法得到全局最优解。
- 动态规划和贪心算法的优缺点是什么?
动态规划 :
- 优点:能得到全局最优解,适用于复杂问题。
- 缺点:时间复杂度可能较高。
贪心算法 :
- 优点:时间复杂度较低,适用于局部最优能导致全局最优的问题。
- 缺点:可能无法得到全局最优解。
- 什么时候使用动态规划?
当问题满足以下条件时,可以使用动态规划:
- 问题可以分解成重叠子问题。
- 子问题的最优解可以组合成整体问题的最优解。
- 什么时候使用贪心算法?
当问题满足以下条件时,可以使用贪心算法:
- 每一步都能做出局部最优选择。
- 局部最优选择能最终导致全局最优解。