一、题目打卡
1.1 打家截舍
class Solution {
public:
int rob(vector<int>& nums) {
// 题目条件里面从 1 开始的
if(nums.size() <= 1) return nums[0];
vector<int> dp(nums.size(),0);
dp[0] = 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];
}
};
感觉如果从之前的背包问题过过渡到这个题目的话,就不是很难理解,状态变量——从0 到 i 所得到的最大价值,递推公式,是否选择索引所对应的价值,和背包问题很类似,题目的原型感觉比背包问题更好理解,不够题目应该还有不同的变种,继续看还会有什么样的变化。
1.2 打家截舍II
题目链接:. - 力扣(LeetCode)
class Solution {
public:
int get_max(vector<int> &nums){
int res = nums[0];
for(int i = 0 ; i < nums.size() ; i++){
res = max(nums[i],res);
}
return res;
}
int rob(vector<int>& nums) {
if(nums.size() <= 2) return get_max(nums); // 因为是一个环,所以只能取一个
vector<int> dp1(nums.size() - 1, 0); // 存储不包含最后一个的时候的最大价值
vector<int> dp2(nums.size() - 1, 0); // 存储不包含第一个的最大价值
dp1[0] = nums[0];
dp2[0] = nums[1];
dp1[1] = max(nums[0],nums[1]);
dp2[1] = max(nums[1],nums[2]);
// for(int i = 2 ; i < nums.size() - 1 ; i++){
// dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
// }
// for(int i = 2 ; i < nums.size() - 1; i++){
// dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i + 1]);
// }
// 优化一下
for(int i = 2 ; i < nums.size() - 1 ; i++){
dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i]);
dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i + 1]);
}
return max(dp1.back(), dp2.back());
// for(int i = 3 ; i < nums.size() ; i++){
// if(i <= nums.size() - 1) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
// if(i >= 4) dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
// }
}
};
这个题目主要是处理环这种结构,其实本质上来说,环也就是需要对首位进行区分,因为这两者不能同时被纳入 dp 数组的选择考虑(注意这里只是考虑,和选不选择没有关系,如果考虑是否选择就会陷入误区),所以也就是分成了两种情况,选择首项纳入考虑和选择尾项纳入考虑,这样分别对应了两个循环(其实也可以合并成为一个)。
在合并的过程中,需要对遍历过程中 i 的意义理解清楚,这里的 i 指的是dp数组的索引,所以对应nums的时候,如果不取第一个,就需要做 +1处理。
1.3 打家截舍III
题目链接:. - 力扣(LeetCode)
/**
* 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:
// int get_length(TreeNode* root){
// if(!root) return 0;
// return get_length(root->left) + get_length(root->right) + 1;
// }
// void recur(TreeNode* root, vector<int> &dp, int i){
// if(!root) return;
// recur(root->left, dp, i + 1);
// dp[i] = max(dp[i - 1], dp[i - 2] + root->val);
// recur(root->right, dp, i + 1);
// }
// int rob(TreeNode* root) {
// // int length = 0;
// // length = get_length(root);
// // cout << length << endl;
// // if(length <= 1) return root->val;
// // vector<int> dp(length, 0);
// // // dp[0] = root->left ? root->left->val : root->val;
// // TreeNode* tmp = root;
// // while(tmp){
// // dp[0] = root->val;
// // tmp = tmp->left;
// // }
// // dp[1] = root->left ? (max(root->left->val, root->val)) : (max(root->val, root->right->val));
// // recur(root, dp, 2);
// // for(int i = 0 ; i < length; i++){
// // cout << dp[i] << " ";
// // }
// // return dp[length - 1];
// }
// void tree2vector(vector<int> &nums, TreeNode* root){
// if(!root) return;
// tree2vector(nums, root->left);
// nums.push_back(root->val);
// tree2vector(nums, root->right);
// }
// int rob(TreeNode* root) {
// vector<int> nums;
// tree2vector(nums, root);
// for(int i = 0 ; i < nums.size() ; i++){
// cout << nums[i] << " ";
// }
// if(nums.size() <= 1) return nums[0];
// vector<int> dp(nums.size(), 0);
// dp[0] = 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.back();
// }
// pair<int,int> recur(TreeNode* root){ // 用前一位存储之前概念的 dp[i -1] 第二位存储 dp[i - 2]
// if(!root) return make_pair(0,0);
// pair<int,int> res1 = recur(root->left);
// pair<int,int> res2 = recur(root->right);
// pair<int,int> res = make_pair(0,0);
// res.first = max(res1.first + res2.first, res1.second + res2.second + root->val);
// res.second = min(res1.first + res2.first, res1.second + res2.second + root->val);
// cout << res.first << " " << res.second << endl;
// cout << "---------------" << endl;
// return res;
// int tmp = res1.first;
// res1.first = max(res1.first + res2.first, root->val + res1.second + res2.second);
// res1.second = tmp;
// int tmp1 = res2.first;
// res2.first = max(res1.first + res2.first, root->val + res1.second + res2.second);
// res2.second = tmp1;
// return make_pair(max(res1.first + res2.first, root->val + res1.second + res2.second), );
// }
// int rob(TreeNode* root) {
// // pair<int, int> res = recur(root);
// // return res.first;
// // if(!root) return 0;
// // int res1 = rob(root->left);
// // int res2 = rob(root->right);
// // cout << res1 << " " << res2 <<endl;
// // cout << "-----------" << endl;
// // return max(res1 + res2, root->val);
// }
vector<int> recur(TreeNode* root){
if(!root) return vector<int>{0,0}; // 抢当前节点获得的最大值和不抢当前节点获得的最大值
vector<int> res1 = recur(root->left);
vector<int> res2 = recur(root->right);
int tmp1 = res1[1] + res2[1] + root->val; // 抢当前节点
// 因为不抢这个节点的话,那子节点就有自己选择的权利是否抢
int tmp2 = max(res1[0], res1[1]) + max(res2[0], res2[1]); // 不抢当前节点
return vector<int>{tmp1,tmp2};
}
int rob(TreeNode* root) {
vector<int> res = recur(root);
return max(res[0],res[1]);
}
};
这个题目我做的过程,很多其实想明白了,但是没有写出来,最后发现原因就是对于状态的定义没有认知清楚,首先我想到了这个返回的值,肯定需要用两个数来表示,这样才能表征 1.1 和 1.2 中的 i - 1 和 i - 2, 但是我发现我递归记录的方法不太正确,如果按照我这个思路,正确的写法应该是这样的:
class Solution {
public:
unordered_map<TreeNode* , int> umap; // 记录计算过的结果
int rob(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return root->val;
if (umap[root]) return umap[root]; // 如果umap里已经有记录则直接返回
// 偷父节点
int val1 = root->val;
if (root->left) val1 += rob(root->left->left) + rob(root->left->right); // 跳过root->left
if (root->right) val1 += rob(root->right->left) + rob(root->right->right); // 跳过root->right
// 不偷父节点
int val2 = rob(root->left) + rob(root->right); // 考虑root的左右孩子
umap[root] = max(val1, val2); // umap记录一下结果
return max(val1, val2);
}
};
这样的话,其实选择当前节点的过程就是前序遍历,不选择当前节点,因为要处理返回值,所以就是后序遍历。
转换到dp方法,其实思路很类似,只是这里处理选择当前节点的时候,这里其实用了一小步贪心的思想,也就是无论如何都要取到最大,不管子节点是怎么考虑的,这其实也对应了上面递归方法中比较的过程,这也算是第一次接触了树形dp。