彩笔表妹的DP之旅
换硬币
题目:给你n种面值的硬币(比如2元,5元,10元)(不要问我为什么有10元的硬币)和一定数量的钱m,求可以换成功的方法数。
其实这个问题我上半年复试的时候有点印象,然后我今天又有幸找到了之前的视频重温了一下。视频中的题目问的是求交换成功的最少硬币的数量,我寻思这整挺好啊,换汤不换药。视频中的代码为:
dp[0]=0
for(i=1;i<=M;++i){ //i为从1开始遍历金钱数
dp[i]=Integer.MAX_VALUE; //原题求的是最小硬币数,初始化的时候自然等于正无穷大了
for(j=0;j<n;++j){ //遍历每种硬币种类
if(i>=A[j]&&dp[i-A[j]]!=Integer.MAX_VALUE){
dp[i]=Math.min(dp[i],1+dp[i-A[j]]);
}
}
}
if(dp[M]==Integer.MAX_VALUE){ //这种情况说明找不到能够交换的方法
f[M]=-1
}
return f[M]
不知道下次看的时候能不能看懂哈。拿上面的例子,大概意思就是这个硬币交换的最后一步到底是交换2元,还是交换5元,还是交换10元呢?然后如果钱能够交换2元的话就继续DP。
针对问题的不同,我做了如下改动:
1.dp[0]=1,意思是能够取整,那么方法数+1
2.不整maxvalue了,都变成0
3.递推公式变成dp(m)=dp(m)+dp(m-A[i])
但是实际运算时发现操作有冗余!
20+5和5+20被计算成了两种方法!
原因是原题求的是最小硬币数量,它是不在乎多算几次的。多算几次并不会改变结果;而我们算的是方法数,多算几次问题就大了!
查阅其他CSDN的代码我知道了解决的方法,就是调换Coin和Money的遍历顺序!其实很神奇。这么做的目的在于使交换方法呈现出一个非递减的顺序,这里贴一个详细解释。
上图中的例子使coin={1,5},money=6的情形。
学会了🐎
求最小重量差
题目:给定一组石头重量序列,把石头分成两堆使重量差最小。
其实这题的一个难点就是如何造DP数组。肉眼可见要造二维的DP,其中一维是遍历石头,我想了半天没找到合理的另一维以及使这两维联系在一起的思路。(又)呜呜!看了解答才发现另一维是重量。一个没想过的思路:重量差最小,则两边都要尽可能接近总重量的一半,这就变成了一个变体的0-1背包的问题!
:给定物品的重量序列,求不超过总重量一半的最大分法(重量)。
主体代码如下:
for (int i = 1; i <= len; ++i) { //i为石头个数
for (int j = 1; j <= sum / 2; ++j) { ///j从1-总重量的一半遍历
if(j>=w[i-1])
dp[i][j] = max(dp[i-1][j],w[i-1]+dp[i-1][j-w[i-1]]);
else
dp[i][j] = dp[i - 1][j];
}
}
其实后面还有一个不错的小trick,返回最小重量差不要再返回去比大小啥的。只需要返回
w
e
i
g
h
t
−
2
∗
d
p
[
i
]
[
w
e
i
g
h
t
/
2
]
weight-2*dp[i][weight/2]
weight−2∗dp[i][weight/2]就可以了。
因为
d
p
[
i
]
[
w
e
i
g
h
t
/
2
]
dp[i][weight/2]
dp[i][weight/2]求的是较轻的那方不超过
w
e
i
g
h
t
/
2
weight/2
weight/2的重量,然后
w
e
i
g
h
t
/
2
weight/2
weight/2又是中心轴。品一品。(dp=i,i+j=weight,求j-i=weight-2*dp)
就写到这吧,不知道明天的OJ测试题会不会记录。