518.零钱兑换II

518. 零钱兑换 II - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1,0);
dp[0]=1;
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<=amount;j++){
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
};

虽然题简单,但是遍历顺序须仔细琢磨

因为纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行。

而本题要求凑成总和的组合数,元素之间明确要求没有顺序。

所以纯完全背包是能凑成总和就行,不用管怎么凑的。

本题是求凑出来的方案个数,且每个方案个数是为组合数

那么本题,两个for循环的先后顺序可就有说法了。

先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况。

代码如下:

1
2
3
4
5
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}

假设:coins[0] = 1,coins[1] = 5。

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量只有{1, 5}这种情况。而不会出现{5, 1}的情况。

所以这种遍历顺序中dp[j]里计算的是组合数!

如果把两个for交换顺序,代码如下:

1
2
3
4
5
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]];
}
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。

此时dp[j]里算出来的就是排列数!

377. 组合总和 Ⅳ

377. 组合总和 Ⅳ - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int combinationSum4(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, 0);
dp[0] = 1;
for (int i = 0; i <= amount; i++) {
for (int j = 0; j < coins.size(); j++) {
if (i - coins[j] >= 0&&dp[i] < INT_MAX - dp[i-coins[j]])
dp[i] += dp[i - coins[j]];
}
}
// for(int i=0;i<coins.size();i++){
// for(int j=coins[i];j<=amount;j++){
// dp[j]+=dp[j-coins[i]];
// }
// }
return dp[amount];
}
};

总结:

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

70. 爬楼梯(进阶版)

57. 爬楼梯(第八期模拟笔试) (kamacoder.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <vector>
using namespace std;

int main(){
int n,m;
cin>>n>>m;
vector<int> dp(n+1,0);
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i-j>=0)
dp[i]+=dp[i-j];
cout<<dp[n];
}

322. 零钱兑换

322. 零钱兑换 - 力扣(LeetCode)

定义::凑足总额为j所需钱币的最少个数为

递推公式:

初始化:

遍历顺序:都行

1
2
3
4
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 - coins[i]]是初始值则跳过
dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
1
2
3
4
for (int i = 1; i <= amount; i++)   // 遍历背包
for (int j = 0; j < coins.size(); j++) // 遍历物品
if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX )
dp[i] = min(dp[i - coins[j]] + 1, dp[i]);

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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], dp[j - coins[i]] + 1);
}
}
if (dp[amount] == INT_MAX)
return -1;
return dp[amount];
}
};

279.完全平方数

279. 完全平方数 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public:
int numSquares(int n) {
// dp[j]表示和为j的完全平方数的最少数量
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j * j <= i; j++) {
dp[i] = min(dp[i], dp[i - j * j] + 1);
}
}
return dp[n];
}
};

139.单词拆分

139. 单词拆分 - 力扣(LeetCode)

没思路

定义:dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词

递推公式:如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。

初始化:dp[0]=true,下标非0的dp[i]初始化为false

遍历顺序:有序!所以先背包后物品!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class 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] = 1;
for (int i = 0; i <= s.size(); i++) {
for (int j = 0; j < i; j++) {
string sub = s.substr(j, i - j);
if (wordset.find(sub) != wordset.end() && dp[j])
dp[i] = true;
}
}
return dp[s.size()];
}
};

198.打家劫舍

198. 打家劫舍 - 力扣(LeetCode)

定义:dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]

递推公式:dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);

初始化:dp[0]=nums[0],dp[1]=max(nums[0],nums[1]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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() + 1, 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 - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};

213.打家劫舍II

213. 打家劫舍 II - 力扣(LeetCode)

变化为环状,需要单独考虑首尾元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int rob1(vector<int> nums, int start, int end) {
if (end == start) return nums[start];
vector<int> dp(nums.size(), 0);
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 res1 = rob1(nums, 0, nums.size() - 2);
int res2 = rob1(nums, 1, nums.size() - 1);
return max(res1, res2);
}
};

337.打家劫舍 III

337. 打家劫舍 III - 力扣(LeetCode)