打家劫舍算法:动态规划思路与实现解析
在算法的世界里,“打家劫舍”是一道经典的动态规划题目,它以巧妙的逻辑和实用的场景,让我们能深入体会动态规划思想如何解决实际问题。接下来,我们将全面剖析这一算法,从解题思路梳理,到代码实现,再到对算法结构和优点的解读,带你掌握解决这类问题的关键 。
一、题目理解与需求分析
(一)题目描述
你是个专业小偷,计划偷窃沿街房屋,每间房有一定现金。但相邻房屋装有防盗系统,若相邻房屋同一晚被闯入就会报警。给定代表每个房屋现金金额的非负整数数组,要计算不触动警报时,一夜能偷窃到的最高金额。比如数组 [1,2,3,1]
,最高金额是 4
(偷第 1、3 间房 );数组 [2,7,9,3,1]
,最高金额是 12
(偷第 1、3、5 间房 ) 。
(二)核心矛盾提炼
- 选择矛盾:对于每个房屋,有两种选择——偷或者不偷。但如果偷当前房屋,就不能偷相邻的前一个房屋;如果不偷,那最高金额就取决于前面房屋能偷到的最高值。
- 最优子结构:整个问题的最优解(偷完所有房屋能得到的最高金额 ),依赖于其子问题的最优解(前
n - 1
个房屋、前n - 2
个房屋等能偷到的最高金额 )。
二、解题思路:动态规划的应用
(一)状态定义
我们定义 dp[i]
表示偷窃到第 i
个房屋时,能得到的最高金额。这样就把原问题拆解成了一个个子问题,通过求解子问题的最优解来得到原问题的最优解。
(二)状态转移方程推导
对于第 i
个房屋,有两种情况:
- 偷第
i
个房屋:那么第i - 1
个房屋就不能偷,此时最高金额是dp[i - 2] + nums[i]
(dp[i - 2]
是前i - 2
个房屋能偷到的最高金额,加上当前房屋的金额nums[i]
)。 - 不偷第
i
个房屋:此时最高金额就等于dp[i - 1]
(即前i - 1
个房屋能偷到的最高金额 )。
所以,状态转移方程为:dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
。
(三)初始条件确定
- 当只有
1
个房屋(i = 0
)时,显然dp[0] = nums[0]
,因为只能偷这一个房屋。 - 当有
2
个房屋(i = 1
)时,要选金额较大的那个房屋偷,所以dp[1] = max(nums[0], nums[1])
。
(四)遍历求解
从前往后遍历房屋数组,根据状态转移方程依次计算 dp[i]
的值,直到遍历完所有房屋,最后 dp[n - 1]
(n
是房屋数量 )就是我们要求的最高金额。
举个简单例子,看数组 [1,2,3,1]
:
- 初始化
dp[0] = 1
,dp[1] = max(1, 2) = 2
。 - 计算
i = 2
(对应金额3
)时,dp[2] = max(dp[1], dp[0] + 3) = max(2, 1 + 3) = 4
。 - 计算
i = 3
(对应金额1
)时,dp[3] = max(dp[2], dp[1] + 1) = max(4, 2 + 1) = 4
。最终得到最高金额是4
,和题目示例结果一致。
三、代码示例(Python 实现)
def rob(nums):
n = len