代码随想录算法训练营第四十五天 | 打家劫舍
198.打家劫舍
- dp[j]: 表示投到第j家最多能偷dp[j]的钱
- 递推公式: dp[j] = max(dp[j-2] + nums[j], dp[j-1])
- 初始化:dp[0] = nums[0], dp[1] = max(dp[0], dp[1])
- 遍历顺序:从小到大
- 打印dp数组
class Solution {
public:
int rob(vector<int>& nums) {
// dp[j]: 表示偷第j家最多能偷多少
int n = nums.size();
vector<int> dp(n);
if(n == 0) return 0;
if(n == 1) return nums[0];
// 递推公式: dp[j] = dp[j - 2] + nums[j]
// 初始化:
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for(int j = 2; j < n; ++j) {
dp[j] = max(dp[j - 2] + nums[j], dp[j-1]);
}
for(int val : dp) cout << val << " ";
return dp[n - 1];
}
};
213.打家劫舍II
思路: 首元素和尾元素只能有一个,所以分两种情况,加入首元素和加入尾元素分别求,然后取最大的
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n);
if(n == 0) return 0;
if(n == 1) return nums[0];
if(n == 2) return max(nums[0], nums[1]);
return max(robRange(nums, 0, n -1), robRange(nums, 1, n));
}
int robRange(vector<int>& nums, int start, int end) {
vector<int> dp(end);
dp[start] = nums[start];
dp[start + 1] = max(nums[start], nums[start + 1]);
for(int j = start + 2; j < end; ++j) {
dp[j] = max(dp[j - 2] + nums[j], dp[j-1]);
}
return dp[end-1];
}
};
打家劫舍III
动规五部曲:
- dp[0] dp[1]分别表示不偷当前节点得到的最大值和偷当前节点的最大值
- 递推公式:
- 偷当前节点: dp[0] = cur->val + left_dp[0] + right_dp[0],如果偷了当前节点,那么左右孩子就不可以偷了
- 不偷当前节点:dp[1] = max(left_dp[0], left[1]) + max(right_dp[0], right_dp[1]) 分别计算左右孩子偷与不偷的最大值相加即可
- 初始话:初始化为任意值都可以因为dp值不以来当前dp数组,而只依赖于左右孩子的dp数组
- 遍历顺序:由递推公式可知,要计算当前dp值需要先获取左右孩子的dp值,所以应用后序遍历
class Solution {
public:
int rob(TreeNode* root) {
// dp[0]表示不偷当前节点的最大值,dp[1]表示偷当前节点的最大值
vector<int> dp = traversal(root);
return max(dp[0], dp[1]);
}
vector<int> traversal(TreeNode* root) {
if(root == nullptr) return {0, 0};
vector<int> left_dp = traversal(root->left);
vector<int> right_dp = traversal(root->right);
int val1 = root->val + left_dp[0] + right_dp[0];
int val2 = max(left_dp[0], left_dp[1]) + max(right_dp[0], right_dp[1]);
return {val2, val1};
}
};