动态规划——打家劫舍三部曲
198.打家劫舍
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
思路
1.dp数组及含义
dp[i] :抢前i家所得最大金额
2.dp递推公式
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
前 i 家,看下标为 i - 1的选择:
① 抢 i - 1 ,则 i - 2 一定不抢,则dp[i] = dp[i - 2] + nums[i - 1]
②不抢 i - 1 ,则看 i-2 的情况即可 dp[i] = dp[i - 1]
两种选择取较大值即可
3.dp初始化
只抢一家:dp[1] = nums[0]
抢前两家的最大金额: dp[2] = max(nums[0], nums[1])
4.dp遍历方向
从前三家遍历到前n家
5.举例
暂无
代码
// dp[i] :前i家所得最大金额
// 共i家 ,偷 下标第i-1家,则必定不偷 下标第 i-2 家;不偷 下标第i家,则等于偷前 i-1 家
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n + 1);
if (n == 0) return 0;
if (n == 1) return nums[0];
dp[1] = nums[0];
dp[2] = max(nums[0], nums[1]);
for (int i = 3; i <= n; i++)
{
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
}
return dp[n];
}
};
打家劫舍II
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
思路
分三种情况:
1.不抢头,不抢尾;
2.抢头,不抢尾;
3.不抢头,抢尾
由于金额大于0,所以1的金额一定最少,从三种情况的2、3选较大情况即可
即 max(robRange(nums,0,n-2), robRange(nums,1,n-1))
1.dp数组及含义
dp[i] 给定数组的前i家抢到的最大金额
2.dp递推公式
dp[i] = max(dp[i - 1], dp[i - 2] + nums[start + i - 1]);
前 i 家,看下标为 i - 1的选择:
① 抢 i - 1 ,则 i - 2 一定不抢,则dp[i] = dp[i - 2] + nums[start + i - 1]
②不抢 i - 1 ,则看 i-2 的情况即可 dp[i] = dp[i - 1]
两种选择取较大值即可
3.dp初始化
只抢一家:dp[1] = nums[start]
抢前两家的最大金额: dp[2] = max(nums[start], nums[start + 1])
4.dp遍历方向
从前三家遍历到前n家,n代表数组大小
5.举例
代码
// 三种情况:1.不抢头、不抢尾;2.抢头不抢尾;3.抢尾不抢头;取三者较大值即可
// 定义子函数robRange(nums,start,end) 1.dp[i]:抢前i家可得到的最大金额;2.递推公式:抢第i - 1家 / 不抢第i - 1家
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
if (n == 1) return nums[0];
return max(robRange(nums,0,n-2),
robRange(nums,1,n-1)
);
}
private:
int robRange(vector<int>& nums, int start, int end)
{
if (end == start) return nums[start];
int n = end - start + 1;
vector<int> dp(n + 1);
dp[1] = nums[start];
dp[2] = max(nums[start], nums[start + 1]);
for (int i = 3; i <= n; i++)
{
dp[i] = max(dp[i - 1], dp[ i - 2] + nums[start + i - 1]);
}
return dp[n];
}
};
打家劫舍III
题目描述
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路
按照二叉树递归。
终止条件:遇到空节点,返回0
选择一: 偷当前的根节点,则跳过root->left和root->right,判断两者是否为空,为空返回0,不为空向下递归;
选择二: 不偷当前根节点,则选择root->left和root->right。
使用备忘录memo避免重复子问题的计算,定义为map类型
代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
map<TreeNode*, int> memo;
int rob(TreeNode* root) {
if (root == nullptr)
{
return 0;
}
auto iter = memo.find(root);
if (iter != memo.end())
{
return iter->second;
}
//选择一:抢root,不抢左右子节点,继续向下抢,此处需要先判断左右子节点是否为空,为空时可抢0,不为空时取左右子节点的子节点
int rob_root = root->val
+ ((root->left == nullptr) ? 0 : (rob(root->left->left) + rob(root->left->right)))
+ ((root->right == nullptr) ? 0 : (rob(root->right->left) + rob(root->right->right)));
//选择二:不抢root,抢左右子节点
int not_rob_root = rob(root->left) + rob(root->right);
memo[root] = max(rob_root, not_rob_root);
return memo[root];
}
};