Leetcode 198.打家劫舍
题目链接:Leetcode 198.打家劫舍
题目描述: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路: 本题是一道比较经典的动态规划问题:
dp[i]
:考虑下标i
(包括i
)以内的房屋,最多可以偷窃的金额为dp[i]
。- 递推公式:
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
我们有两种选择,一种是偷第i
间,另一种是不偷第i
间,二者取最大值即可 - 初始化:
dp[0]=nums[0],dp[1]=max(nums[0],nums[1])
- 遍历顺序:很容易想到是从前向后遍历
代码如下:
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() + 5);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); i++) // 分为不拿nums[i]和拿nums[i]
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
return dp[nums.size() - 1];
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
Leetcode 213.打家劫舍II
题目链接:Leetcode 213.打家劫舍II
题目描述: 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路: 本题与上道题Leetcode 198.打家劫舍相比,仅仅多了首尾元素也相邻这一条件,因此仍然可以套用上道题的代码,只不过需要额外分类讨论:
- 偷第一个屋子,则房屋范围变为
[0, nums.size() - 2]
- 不偷第一个屋子,则房屋范围变为
[1, nums.size() - 1]
最后二者取最大值即可。
代码如下:
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
// 分为两种情况,一种是选第一个屋子,另一种是不选第一个屋子
int result1 = Rob(nums, 0, nums.size() - 2);
int result2 = Rob(nums, 1, nums.size() - 1);
return max(result1, result2);
}
// 198.打家劫舍的逻辑
int Rob(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];
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
Leetcode 337.打家劫舍 III
题目链接:Leetcode 337.打家劫舍 III
题目描述: 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
。除了 root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。 给定二叉树的 root
。返回在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路: 本题可以利用记忆化搜索解决,利用后序遍历,并不断保存搜索过的节点,递归寻找最高金额。
代码如下:(记忆化搜索)
class Solution {
public:
unordered_map<TreeNode*, int> umap; // 保存搜索的结果
int rob(TreeNode* root) {
// 后序遍历:可以将结果返回根节点
if (root == nullptr)
return 0;
if (root->left == nullptr && root->right == nullptr)
return root->val;
if (umap[root])// 如果搜索过了直接返回
return umap[root];
// (1)当前偷父亲节点,则下次偷孙子节点
int val1 = root->val;
if (root->left)
val1 += rob(root->left->left) + rob(root->left->right);
if (root->right)
val1 += rob(root->right->left) + rob(root->right->right);
// (2)偷孩子节点
int val2 = rob(root->left) + rob(root->right);
umap[root] = max(val1, val2); // 保存搜索结果
return umap[root];
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( l o g n ) O(logn) O(logn)
当然本题也可以利用动态规划来解决:
- 大小为
2
的dp
数组:下标为0
记录不偷该节点所得到的的最大金钱,下标为1
记录偷该节点所得到的的最大金钱。 - 遍历顺序:后序遍历
- 递推公式:如果是偷当前节点,那么左右孩子就不能偷
val1 = cur->val + left[0] + right[0]
;
如果不偷当前节点,那么左右孩子就可以偷,需要选择最大的一个。val2 = max(left[0], left[1]) + max(right[0], right[1])
代码如下:(树形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 == nullptr)
return vector<int>{0, 0};
// 后序遍历
vector<int> left = RobTree(cur->left);
vector<int> right = RobTree(cur->right);
// (1)偷cur
int val1 = cur->val + left[0] + right[0];
// (2)不偷cur
int val2 = max(left[0], left[1]) + max(right[0], right[1]);
return {val2, val1};//注意不要反了,第一个代表不偷,第二个代表偷
}
};
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( l o g n ) O(logn) O(logn)
总结: 最后一道题还是有难度的,不仅需要理解动态规划,还要掌握二叉树的遍历。
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!