518:零钱兑换ⅠⅠ
思路一:母函数
1.假设有n种零钱,分别为c1,c2…cn,需要找零钱数为m。
2.对于多项式(1+xc1+x2c1+x3c1+x4c1+……+xi*c1)(1+xc2+x2c2+x3c2+x4c2+……+xj*c2)……(1+xcn+x2cn+x3cn+x4cn+……+xk*cn),其中i=
⌊
m
/
c
1
⌋
\lfloor m/c1 \rfloor
⌊m/c1⌋,j=
⌊
m
/
c
2
⌋
\lfloor m/c2 \rfloor
⌊m/c2⌋,k=
⌊
m
/
c
n
⌋
\lfloor m/cn \rfloor
⌊m/cn⌋,结果为1+a1xb1+a2xb2+a3xb3+a4xb4+……+anxbn。
根据幂的相乘原理,结果aixbi中bi对应找零的总额,ai对应找零的方法总数。
3.所以我们只需要计算多项式乘积的结果,取得anxbn即可获得找零方法总数
4.循环遍历n次,每一次循环计算前i组多项式的乘积结果,记数组s[m]为为已经计算的i-1组多项式乘积的系数结果,t[m]为第i轮遍历完时,s[m]的临时结果。对于第i轮循环,我们需要更新系数数组t[m],其中
t
[
i
]
=
∑
i
=
1
m
s
[
j
+
λ
∗
c
i
]
t[i]=\sum_{i=1}^ms[j+\lambda*ci]
t[i]=∑i=1ms[j+λ∗ci],其中
j
+
λ
∗
c
i
=
=
i
j+\lambda*ci==i
j+λ∗ci==i。
5.因为只需要算anxcn,所以只需要保留系数数组长度只需要m即可。
class Solution {
public:
int change(int amount, vector<int>& coins) {
vector<int> s(amount+1,0);
vector<int> t(amount+1,0);
for(int j=0;j<=amount;j++)
{
s[j]=j%coins[0]==0?1:0;
}
for(int i=1;i<coins.size();i++)
{
for(int j=0;j<=amount;j++)
{
for(int k=0;j+k<=amount;k+=coins[i])
{
t[j+k]+=s[j];
}
}
for(int j=0;j<=amount;j++)
{
s[j]=t[j];
t[j]=0;
}
}
return s[amount];
}
};
思路二:动态规划
1.假设dp[i][j]代表使用coins[0]……coins[i]的硬币拼成j元价值的组合数。
2.状态迁移方程:
d
p
[
i
]
[
j
]
=
∑
k
=
0
i
d
p
[
k
]
[
j
]
+
d
p
[
i
−
c
o
i
n
s
[
i
]
]
dp[i][j]=\sum_{k=0}^idp[k][j]+dp[i-coins[i]]
dp[i][j]=∑k=0idp[k][j]+dp[i−coins[i]]
3.实际上还可以进一步做状态压缩,记dp[j]为组成j元的组合数。
4.
d
p
[
j
]
=
∑
i
=
0
n
d
p
[
j
−
c
o
i
n
s
[
i
]
]
dp[j]=\sum_{i=0}^ndp[j-coins[i]]
dp[j]=∑i=0ndp[j−coins[i]]
5.边界条件:dp[0]=1
代码:
class Solution {
public:
int change(int amount, vector<int>& coins) {
int length=coins.size();
vector<int> dp(amount+1,0);
dp[0]=1;
for(int i=0;i<length;i++)
{
for(int j=coins[i];j<=amount;j++)
{
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
};
377:组合总和
思路一:
1.记dp[i]为组成大小为i的组合个数
2.
d
p
[
j
]
=
∑
i
=
0
n
d
p
[
j
−
n
u
m
s
[
i
]
]
,
n
u
m
s
[
i
]
<
=
j
<
=
t
a
r
g
e
t
dp[j]=\sum_{i=0}^ndp[j-nums[i]],nums[i]<=j<=target
dp[j]=∑i=0ndp[j−nums[i]],nums[i]<=j<=target
3.与上面518题不同的是,此题的内循环循环遍历为nums数组的元素,外层循环是遍历从1遍历到target,因此在一次外层循环时,所有dp[j-nums[i]]都被考虑到,也就是顺序也考虑进来。比如计算dp[4],其中nums数组有两个数1和3,那么如此计算dp[4]时,dp[1]和dp[3]都有被考虑在内。
4.计算时候要考虑某个dp值过大的情况
代码:
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target + 1);
dp[0] = 1;
for (int i = 1; i <= target; i++) {
for (int& num : nums) {
if (num <= i && dp[i - num] < INT_MAX - dp[i]) {
dp[i] += dp[i - num];
}
}
}
return dp[target];
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/combination-sum-iv/solution/zu-he-zong-he-iv-by-leetcode-solution-q8zv/
思路二:
1.1.记dp[i]为组成大小为i的组合个数
2.
d
p
[
j
]
=
∑
i
=
0
n
d
p
[
j
−
n
u
m
s
[
i
]
]
,
n
u
m
s
[
i
]
<
=
j
<
=
t
a
r
g
e
t
dp[j]=\sum_{i=0}^ndp[j-nums[i]],nums[i]<=j<=target
dp[j]=∑i=0ndp[j−nums[i]],nums[i]<=j<=target
3.在计算dp数组过程中可以进一步进行状态压缩,需要借组自动排序的map,自底向上计算dp[target]。
4.首先对nums进行排序,借助红黑树为底部的map(自动排序),加入节点(0,1)
5.每一次弹出红黑树最小的节点(x,y),遍历nums数组,如果以x+nums[i]作为key的节点存在则更新其value+=y,否则插入节点(x+nums[i],y)
6.加的过程注意数字太大问题
代码:
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
map<int,int> dp;
for(int i=0;i<nums.size();i++)
{
dp[nums[i]]=1;
}
map<int,int>::iterator it=dp.begin();
while(it->first<target)
{
for(int i=0;i<nums.size();i++)
{
if(it->first+nums[i]>target)
continue;
map<int,int>::iterator t=dp.find(it->first+nums[i]);
if(t==dp.end())
dp[it->first+nums[i]]=it->second;
else
{
int temp1=t->second;
int temp2=it->second;
if(temp1<INT_MAX-it->second)
dp[t->first]=temp1+temp2;
}
}
dp.erase(it++);
}
if(it->first==target)
return it->second;
else
return 0;
}
};