前言
更详细的在大佬的代码随想录 (programmercarl.com)
本系列仅是简洁版笔记,为了之后方便观看
完全背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}}
零钱兑换II
. - 力扣(LeetCode)
因为纯完全背包不在乎有没有顺序,有顺序也行没有顺序也行,但是这个题目的要求是没有顺序,求组合数,所以就要考虑for循环先后顺序调换有没有影响了
组合情况:先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况
for (int i = 0; i < coins.size(); i++) { // 遍历物品for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量dp[j] += dp[j - coins[i]];}
}
排列数情况:背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。
for (int j = 0; j <= amount; j++) { // 遍历背包容量for (int i = 0; i < coins.size(); i++) { // 遍历物品if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];}
}
组合总和IV
. - 力扣(LeetCode)
if 语句的作用
确保在执行状态转移时不会访问不合法的索引,防止整数溢出。这是动态规划算法中常见的边界检查和安全性措施。
class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target + 1, 0);dp[0] = 1;for (int i = 0; i <= target; i++) { // 遍历背包for (int j = 0; j < nums.size(); j++) { // 遍历物品if (i - nums[j] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {dp[i] += dp[i - nums[j]];}}}return dp[target];}
};
零钱和
322. 零钱兑换 - 力扣(LeetCode)
class Solution {
public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount + 1, INT_MAX);dp[0] = 0;for (int i = 0; i < coins.size(); i++) { for (int j = coins[i]; j <= amount; j++) {if (dp[j - coins[i]] != INT_MAX) {dp[j] = min(dp[j - coins[i]] + 1, dp[j]);}}}if (dp[amount] == INT_MAX) return -1;return dp[amount];}
};
单词拆分
139. 单词拆分 - 力扣(LeetCode)
要考虑for循环的先后顺序
lass Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());vector<bool> dp(s.size() + 1, false);dp[0] = true;for (int j = 0; j < wordDict.size(); j++) { // 物品for (int i = wordDict[j].size(); i <= s.size(); i++) { // 背包string word = s.substr(i - wordDict[j].size(), wordDict[j].size());if ( word == wordDict[j] && dp[i - wordDict[j].size()]) {dp[i] = true;}}}return dp[s.size()];}
};
打家劫舍
打家劫舍
相邻房间不能偷,考虑两种情况,偷或者不偷
198. 打家劫舍 - 力扣(LeetCode)
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];}
};
打家劫舍II
和上一题不同点:首尾相连连成环
情况如下
- 不考虑首尾元素,首尾元素连成环无影响
- 考虑首元素,默认没有尾元素,但头元素可选可不选
- 考虑尾元素,默认没有首元素,但尾元素可选可不选
情况2,3包含了情况1,所以分成两种情况,分别取两种情况的最大值
class Solution {
public: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);}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];}
};
打家劫舍III
337. 打家劫舍 III - 力扣(LeetCode)
遍历方式:后序遍历
与前两题的不同点是这个是在二叉树的结构当中
class Solution {
public:int rob(TreeNode* root) {vector<int> result = robTree(root);return max(result[0], result[1]);//根节点投或者不投}// 长度为2的数组,0:不偷,1:偷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);//右子树// 偷cur,那么就不能偷左右节点。int val1 = cur->val + left[0] + right[0];// 不偷cur,那么可以偷也可以不偷左右节点,则取较大的情况int val2 = max(left[0], left[1]) + max(right[0], right[1]);return {val2, val1};}
};