LeetCode 198- 打家劫舍
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解题思路
- 确定dp数组(dp table)以及下标的含义
dp[i]:将第i个房间算上,能偷盗的总金额最大是多少
- 确定递推公式
一个房间只有两个状态,偷or不偷,我们如果偷当前房间,那就是nums[i]+dp[i-2]
(因为前一个房间不能偷)。如果不偷,那值就是dp[i-1]
。我们需要最大的,故dp[i] = max(nums[i]+dp[i-2],dp[i-1])
- dp数组如何初始化
dp[0]初初始化为第一个房间的钱数量,dp[1]初始化为第一个房间和第二个房间金额最大值。
- 确定遍历顺序
正序遍历即可
- 举例推导dp数组
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) 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 - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};
总结:
- 要清楚dp数组含义,才能写好动态规划。
LeetCode 213- 打家劫舍 II
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
解题思路
本题是一个环,有如下几种情况,我们偷盗时,可以选的点有如下几种
所以我们只需要分情况讨论就可以解决环的问题。
- 确定dp数组(dp table)以及下标的含义
dp[j]:将第i个房间算上,能偷盗的总金额最大是多少
- 确定递推公式
和上一个一模一样,不过我们取的范围是分情况讨论的,并且情况3已经被情况1和情况2涵盖了。
- dp数组如何初始化
dp[0]初始化为nums[0],其余全部为0.
- 确定遍历顺序
正序遍历即可
- 举例推导dp数组
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
int result1 = robRange(nums, 0, nums.size() - 2); // 情况二
int result2 = robRange(nums, 1, nums.size() - 1); // 情况三
return max(result1, result2);
}
// 198.打家劫舍的逻辑
int robRange(vector<int>& nums, int start, int end) {
if (end == start) return nums[start];
vector<int> dp(nums.size());
dp[start] = nums[start];
dp[start + 1] = max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
};
总结:
- 环的情况,只需要列举出第一个房子和最后一个房子的情况,并分情况讨论即可。本来还以为需要统一处理。
LeetCode 337- 打家劫舍 III
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述:小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
解题思路
这题是二叉树和dp的组合。每个节点有两个状态,即偷or不偷。我们需要记录每个节点的状态,并返回给其父节点。我们需要在父节点处理其孩子节点的偷取情况。所以使用后序遍历。
- 确定dp数组(dp table)以及下标的含义
长度为2的数组,dp[0]代表不偷该节点所能得到的最大价值,dp[1]代表偷节点。(以当前节点为根节点)
- 确定递推公式
每个节点有两种情况,偷或不偷。如果偷当前节点,那其孩子节点就不偷。
即current[1] = left[0]+right[0]+current-val
若不偷当前节点,则两边孩子节点都选取偷或不偷的最大值相加即可
即current[0] = max(left[0],left[1])+max(right[0],right[1])
遇到空节点时偷不偷价值都是0。
- dp数组如何初始化
不需要初始化
- 确定遍历顺序
后序遍历,先遍历孩子节点再处理其根节点。
- 举例推导dp数组
class Solution {
public:
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
// 长度为2的数组,0:不偷,1:偷
vector<int> robTree(TreeNode* cur) {
if (cur == NULL) return vector<int>{0, 0};
vector<int> left = robTree(cur->left);
vector<int> right = robTree(cur->right);
// 偷cur,那么就不能偷左右节点。
int val1 = cur->val + left[0] + right[0];
// 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
return {val2, val1};
}
};
总结:
- 抽象的一