写完背包问题了,今天写动态规划的经典问题之一的打家劫舍问题。今天就直接上例子,因为这类问题用动态规划五部曲就可以解决,只是递推公式需要好好理解一下。
打家劫舍
dp数组及下标含义
dp[i]表示偷窃[0, i]号房能够偷窃到的最高金额
递推公式
不偷i号房,dp[i] = dp[i - 1]
偷i号房,dp[i] = dp[i -2] + nums[i]
综上,dp[i] = max(dp[i - 1], dp[i -2] + nums[i])
初始化
由递推公式知道dp[0]和dp[1]是需要初始化的,再根据dp数组的含义,得到dp[0] = nums[0],dp[1] = max(nums[0], nums[1])
遍历顺序
根据递推公式,dp[i]由dp[i -1]和dp[i - 2]决定,所以从小到大遍历
int rob(vector<int>& nums) {
if(nums.zie() == 0)
return 0;
if(nums.size() == 1)
return nums[0];
vector<int> dp(nums.size(), 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[nums.size() - 1];
}
打家劫舍2
题目:213. 打家劫舍 II - 力扣(LeetCode)
这道题的房子形成了一个环,但是换个思路就可以变成上一道题了。将0至n - 2号房和1至n - 1号房分别按第一题计算,这样就不会出现同时偷0号房和n - 1号房的情况,得到的两个结果返回较大值即可。
int robRange(vector<int> nums, int start, int end){
if(start == end)
return nums[start];
vector<int> dp(nums.size(), nums[start]);
dp[start + 1] = max(dp[start], dp[start + 1]);
for(int i = start + 2; i <= end; i++){
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[end];
}
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);
}
打家劫舍3
题目:337. 打家劫舍 III - 力扣(LeetCode)
dp数组及下标含义
dp[0]表示不偷该节点能够偷窃的最大价值,dp[1]表示偷该节点能够偷窃的最大价值
递推公式
如果不偷该节点,dp[0] = 左孩子dp数组的最大值 + 右孩子dp数组的最大值
如果偷该节点,dp[1] = root -> val + left[0] + right[0]
初始化
dp数组全初始化为0
遍历顺序
采用后序遍历,因为最后由根节点决定是偷孩子节点还是根节点
vector<int> robTree(TreeNode* root){
if(root == NULL)
return vector<int>(2, 0);
vector<int> left = robTree(root -> left);
vector<int> right = robTree(root -> right);
vector<int> dp(2, 0);
dp[0] = max(left[0], left[1]) + max(right[0], right[1]);
dp[1] = root -> val + left[0] + right[0];
return dp;
}
int rob(TreeNode* root) {
vector<int> dp = robTree(root);
return max(dp[0], dp[1]);
}
这道题有点难,树加上动态规划,不太容易想到思路。