碎碎念:加油加油
参考:代码随想录
198.打家劫舍
题目链接
思想
动态规划五部曲:
- 确定dp数组以及下标的含义:考虑下标i(包含下标i),所偷的最大的金币为dp[i],最后求的是dp(nums.size()-1)
- 确定递推公式:dp[i] =max(dp[i-2]+nums[i], dp[i-1])。偷i:dp[i-2]+nums[i],不偷i:dp[i-1]。
- dp数组的初始化:dp[0]=nums[0],dp[1] = max(dp[0], dp[1])
- 确定遍历顺序:for循环,从小到大遍历
- 打印dp数组:主要用来debug。
题解
// cpp
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};
# python
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
return dp[len(nums) - 1]
反思
213.打家劫舍II
题目链接
思想
本题和上一题的区别:上一题给的是一个线性的数组,本题的房间首尾相连变成了环。
对环的处理:
- 考虑选首元素,不选尾元素
- 考虑选尾元素,不选首元素
- 首尾元素都不选
第1种情况和第2种情况包含了第三种情况。
把需要考虑的数组的范围传入上一题的函数里,再对两种情况取得的值取最大值即可。
题解
// cpp
class Solution {
public:
int robRange(vector<int>& nums, int start, int end) {
if (end == start) 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 - 2] + nums[i], dp[i - 1]);
}
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);
}
};
# python
class Solution:
def robRange(self, nums, start, end):
if start == end:
return nums[start]
dp = [0] * len(nums)
dp[start] = nums[start]
dp[start + 1] = max(nums[start], nums[start + 1])
for i in range(start + 2, end + 1):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
return dp[end]
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
result1 = self.robRange(nums, 0, len(nums) - 2)
result2 = self.robRange(nums, 1, len(nums) - 1)
return max(result1, result2)
反思
遇到环形问题的时候,可以考虑把它展开为线性的情况。
337.打家劫舍III
题目链接
思想
本题需要在一个树形结构中偷钱币。依然要注意相邻节点不能偷。
每个节点只有两个状态,偷或者不偷,所以我们可以用一个一维的长度为2的dp数组来表示状态。dp[0]表示不偷当前节点所获得的最大金币,dp[1]表示偷当前节点所获的最大金币。
递归三部曲:
- 参数和返回值:返回值是dp数组,参数是根节点。
- 终止条件:遍历到NULL,返回初始为0的vector数组。
- 单层递归逻辑:后序遍历
左:leftdp = robtree(cur->left);
右:rightdp = robtree(cur->right);
中:偷当前节点,不偷左右孩子 val1 = cut->val + leftdp[0] + rightdp[0]
不偷当前节点,偷左右孩子 val2 = max(leftdp[0], leftdp[1]) + max(rigthdp[0], rightdp[1])
return {val2, val1}
题解
/**
* 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:
vector<int> robTree(TreeNode* cur) {
if (cur == NULL) return vector<int>{0, 0};
vector<int> left = robTree(cur->left);
vector<int> right = robTree(cur->right);
int val1 = cur->val + left[0] + right[0]; // 取当前节点 不取左右孩子
int val2 = max(left[0], left[1]) + max(right[0], right[1]); // 不取当前节点 取左右孩子
return {val2, val1};
}
int rob(TreeNode* root) {
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
};
# python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def robTree(self, cur):
if not cur:
return (0, 0)
left = self.robTree(cur.left)
right = self.robTree(cur.right)
val1 = cur.val + left[0] + right[0]
val2 = max(left[0], left[1]) + max(right[0], right[1])
return (val2, val1)
def rob(self, root: Optional[TreeNode]) -> int:
dp = self.robTree(root)
return max(dp)
反思
本题是树形递归遍历问题也是动态规划问题。