LeetCode打家劫舍题解:gh_mirrors/leet/leetcode项目详解
你是否在面对LeetCode上的动态规划问题时感到无从下手?是否想快速掌握这类问题的解题思路?本文将带你深入理解动态规划算法,并通过gh_mirrors/leet/leetcode项目中的实例,详细讲解如何解决经典的"打家劫舍"问题。读完本文后,你将能够:
- 理解动态规划的基本原理和适用场景
- 掌握"打家劫舍"问题的解题思路
- 学会如何优化动态规划算法的空间复杂度
- 能够独立解决类似的动态规划问题
动态规划简介
动态规划(Dynamic Programming,简称DP)是一种通过将复杂问题分解为重叠子问题,并存储子问题的解来避免重复计算的算法设计方法。它通常用于解决具有最优子结构和重叠子问题性质的问题。
在gh_mirrors/leet/leetcode项目中,动态规划相关的题目和解答主要集中在C++/chapDynamicProgramming.tex文件中。该文件包含了多个经典的动态规划问题,如最大子数组和、编辑距离、最小路径和等。
动态规划的核心思想是将一个复杂问题分解为若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,动态规划求解的子问题往往不是相互独立的,而是存在重叠。因此,动态规划会存储子问题的解,避免重复计算。
"打家劫舍"问题分析
"打家劫舍"问题是一个经典的动态规划问题,其描述如下:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
问题建模
我们可以用一个数组nums表示每间房屋的金额。我们需要找到一个子序列,使得子序列中任意两个元素不相邻,并且子序列的和最大。
让我们定义状态dp[i]表示前i间房屋能够偷窃到的最高金额。那么,对于第i间房屋,我们有两种选择:
- 偷窃第
i间房屋:此时不能偷窃第i-1间房屋,所以dp[i] = dp[i-2] + nums[i] - 不偷窃第
i间房屋:此时dp[i] = dp[i-1]
因此,状态转移方程为:dp[i] = max(dp[i-1], dp[i-2] + nums[i])
边界条件
- 当只有一间房屋时,
dp[0] = nums[0] - 当有两间房屋时,
dp[1] = max(nums[0], nums[1])
解决方案
根据上述分析,我们可以实现一个动态规划算法来解决"打家劫舍"问题。
基本实现
int rob(vector<int>& nums) {
if (nums.empty()) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); ++i) {
dp[i] = max(dp[i-1], dp[i-2] + nums[i]);
}
return dp.back();
}
空间优化
观察上述代码,我们发现dp[i]只依赖于dp[i-1]和dp[i-2]。因此,我们可以使用两个变量来存储这两个值,从而将空间复杂度从O(n)优化到O(1)。
int rob(vector<int>& nums) {
if (nums.empty()) return 0;
if (nums.size() == 1) return nums[0];
int prev_prev = nums[0];
int prev = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); ++i) {
int current = max(prev, prev_prev + nums[i]);
prev_prev = prev;
prev = current;
}
return prev;
}
相关问题拓展
"打家劫舍"问题有多个变体,如房屋排列成环形、房屋是二叉树结构等。这些变体问题的解题思路与基础问题类似,但需要对状态定义和转移方程进行适当调整。
环形房屋问题
如果房屋排列成环形,那么第一间和最后一间房屋也相邻。这种情况下,我们可以将问题分解为两个子问题:
- 不偷窃第一间房屋
- 不偷窃最后一间房屋
然后取这两个子问题的解的最大值。
二叉树房屋问题
如果房屋按照二叉树排列,那么我们需要定义每个节点的两种状态:
- 偷窃当前节点
- 不偷窃当前节点
状态转移方程如下:
- 偷窃当前节点:
rob(root) = root.val + not_rob(root.left) + not_rob(root.right) - 不偷窃当前节点:
not_rob(root) = max(rob(root.left), not_rob(root.left)) + max(rob(root.right), not_rob(root.right))
总结
动态规划是解决具有重叠子问题和最优子结构性质的问题的有效方法。"打家劫舍"问题是动态规划的经典应用,通过定义合适的状态和状态转移方程,我们可以高效地解决这类问题。
在gh_mirrors/leet/leetcode项目中,还有许多其他动态规划问题的详细解答,如C++/chapDynamicProgramming.tex中收录的最大子数组和、编辑距离等问题。通过学习这些问题的解法,我们可以进一步加深对动态规划的理解和应用能力。
希望本文对你理解动态规划和解决"打家劫舍"问题有所帮助。如果你想深入学习更多算法问题,可以通过以下方式获取项目完整代码:
git clone https://gitcode.com/gh_mirrors/leet/leetcode
通过不断练习和思考,相信你一定能够熟练掌握动态规划这一强大的算法设计技巧,并能够解决更加复杂的实际问题。
推荐资源
- 项目官方文档:README.md
- 动态规划专题:C++/chapDynamicProgramming.tex
- 树相关问题:C++/chapTree.tex
通过这些资源,你可以进一步拓展算法知识,提升解题能力。祝你在算法学习的道路上越走越远!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



