打家劫舍问题是动态规划的经典问题,下面将用leetcode里四个例题讲解这个问题。
这个小偷属实是太聪明了,估计是程序员退休转行了。[doge]
第一题 :leetcode 198 打家劫舍I
class Solution {
public:
int rob(vector<int>& nums) {
//dp数组含义:偷这个房子 则前一个 房子不能偷 即 dp[i] = dp[i - 1] + nums[i]
// 不偷这个房子 则前前一个可以偷 即 dp[i] = dp[i - 2];
// 两者取最大值即可
int n = nums.size();
if(n == 1) return nums[0];
vector<int> dp(n + 1);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);//初始化为前两个房子中最大的
for(int i = 2; i < n; i++){
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
}
return dp[n - 1];
}
};
第二题 :leetcode 213 打家劫舍II
class Solution {
public:
int getmoney(vector<int>& nums, int start, int end){
//解题思想:和打家劫舍I的区别是这次的房间号是一个环 也就是最后一个房间和第一个房间是连在一起的
// 此时需要破环(最后一个和第一个只能选择偷一个,所以从这里破)
//两种情况:1、去掉最后一个 2、去掉第一个 去掉之后就变成了打家劫舍I了
if(start == end) 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 - 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 res1 = getmoney(nums, 0, nums.size() - 2);//去掉最后一个
int res2 = getmoney(nums, 1, nums.size() - 1);//去掉第一个
return max(res1, res2);
}
};
第三题 :leetcode 337 打家劫舍III
/**
* 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:
// {
//先暴力解法 直接递归计算 money = max(偷父节点的钱, 不偷父节点的钱 + 当前节点的钱)
// if(root == NULL) return 0;
// if(root->left == NULL && root->right == NULL) return root->val;
// //偷父节点 则次父节点的左右孩子不可偷
// int ans1 = root->val;
// if(root->left){
// ans1 += rob(root->left->left) + rob(root->left->right);
// }//不偷左孩子
// if(root->right){
// ans1 += rob(root->right->left) + rob(root->right->right);
// }//不偷右孩子
// int ans2 = 0;
// ans2 = rob(root->left) + rob(root->right);
// return max(ans1, ans2);
//此法时间复杂度:n^2 时间复杂度:log n 超时了
//}
int rob(TreeNode* root){
vector<int> ans = getmoney(root);
return max(ans[0], ans[1]);
}
//将DP数组初始化为一个int rob(TreeNode* root)只有两个元素的数组 表示偷还是不偷当前结点的最大值
vector<int> getmoney(TreeNode *cur){
if(cur == NULL) return vector<int> {0,0};
//后序遍历
vector<int> left = getmoney(cur->left);
vector<int> right = getmoney(cur->right);
//如果偷当前结点 则左右孩子都不能偷
int ans1 = cur->val + left[0] + right[0];
//不偷当前节点,则左右孩子都可以偷
int ans2 = 0;
ans2 = max(left[0], left[1]) + max(right[0], right[1]);
return {ans2, ans1};// 注意ans2是不偷当前节点的最大值 ans1是偷当前节点的最大值,别写反
//此解法时间复杂度 O(n)
}
};
第四题 :leetcode 740 删除并获得点数
class Solution {
public:
int deleteAndEarn(vector<int>& nums) {
//这题是打家劫舍问题的变形题
//分析:如何将元素之间相结合起来,从而推出动规方程
// dp[i]中的i应该表示的是元素的值而不是下标 因为下标之间没有联系
int k = nums.size();
if(k < 1) return 0;
int maxn = 0;
for(int i = 0; i < k ;i++){
maxn = max(maxn, nums[i]);
}
vector<int> cntnum(maxn + 1);
vector<int> dp(maxn + 1);
for(int i = 0; i < k; i++){
cntnum[nums[i]]++;
}
//此时此刻就变成了打家劫舍问题
//价值为1的有几个 价值为2的有几个 选了价值为2的就不再选相邻的 1 和 3 即为打家劫舍
dp[0] = 0;
dp[1] = cntnum[1];
for(int i = 2; i <= maxn; i++){
dp[i] = max(dp[i - 1], dp[i - 2] + cntnum[i] * i);
}
return dp[maxn];
}
};
以上就是打家劫舍基础问题的解,当然还有很多变形,等以后刷到了在继续更新。