动态规划是什么:
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
动态规划的思想与策略:
将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
在上代码之前说个例子:
假设你有1,2,3(元)三种种类的零钱,数量不限,你需要找零6元,请问有多少种方法?
很显然,是7种。我们就来说一说这个思想。
第一类:只用1元或2元或3元的零钱去找零,均为1种,共3种
第二类:只用1元,2元二种找零,会出现2种,
第三类:只用1元,3元二种找零,会出现1种,
第四类:用1元,2元,3元三种找零,会出现1种,
所以答案是7种办法。我们换一个角度进
引用块内容
行思考,将其分为3类:
第一类:只能用1元去找零,共1种
第二类:可以用1元,2元找零,会出现4种,
第三类:可以用1元,2元,3元找零,会出现7种,
在这里就会有疑问,第三类不是直接和问题一样了吗?但是不难发现的是:在第二类的结果中包含了第一类的结果,第三类的结果中也包含了第二类的结果。
第一类与第二类,第二类与第三类存在的差异就是所用的零钱种类数。
换一个角度看,可以将第三类分为2种情况:
(1)使用3元零钱:3,1,1,1—–3,2,1—–3,3(三种方法)
(2)不使用3元零钱:与第二类相同
同理将第二类分为同样的2种情况:
(1)使用2元零钱:2,2,2—–2,2,1,1—–2,1,1,1,1(三种方法)
(2)不使用2元零钱:与第一类相同
第一类其实同样可以分为2种情况:
(1)使用1元零钱:1,1,1,1,1,1(1种情况)
(2)不使用1元零钱:0种
如果将问题依次解决,那么最后一个问题的解就是问题初始的解,这也正是动态规划的魅力所在。
上面这一段文字或许你没读的太懂,所以通过代码示例进行进一步的解读。
public class 动态规划_找零问题 {
public static void main(String[] args) {
Exchange exchange = new Exchange();
int a[] = {1,2,3};//零钱面值
int count = exchange.countWays(a, 3, 6);//使用前3种零钱找零6元
System.out.println(count);
}
}
//penny存放零钱的数组 n所用数组零钱种类的数量 aim所需找钱数
class Exchange {
public int countWays(int[] penny, int n, int aim) {
//数据出错
if(n == 0 || penny == null || aim < 0){
return 0;
}
/**
*二维数组的大小设定为零钱的种类数*(找零数+1)
所加的1是找零0元
*/
int[][] pd = new int[n][aim+1];
//将找零0元的列初始化为1(无论是多少零钱去找零0元,都不存在,所以都是一种情况)
for(int i=0;i<n;i++){
pd[i][0] = 1;
}
//由于最小找零是1,所以第0行的每一列都初始化为1
/**就像这样
* 0 1 2 3 4 5 6
*(1元)【1】 【1】 【1】 【1】 【1】 【1】 【1】
*/
for(int i = 1; penny[0] * i <= aim; i++){
pd[0][penny[0] * i] = 1;
}
/**
*现在假设dp[n][m]为使用前n种货币凑成的m的种数,那么就会有两种情况:
*使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]]
*不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n] > m。(就好比你需要找零2元,假设你有一种3元的零钱,你就使用不到)
*这样就可以求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]]
*否则,dp[n][m] = dp[n-1][m]
*/
for(int i = 1; i < n; i++){
for(int j = 0; j <= aim; j++){
if(j >= penny[i]){
pd[i][j] = pd[i-1][j] + pd[i][j-penny[i]];
}else{
pd[i][j] = pd[i-1][j];
}
}
}
return pd[n-1][aim];
}
或许还是会有一丁点的疑惑(那我就再仔仔细细的分析一波):
第二类(只用1元,2元):
- 如果需要找一元钱,则不需要用到2元钱。那么和只用一元钱是一样的,即dp[n][m] = dp[n-1][m]。是1种。
- 如果需要找两元钱,不用到2块钱,即一种。
用到2元钱。然后再用(1元,2元)去找0元钱。1种。
相加共2种。 - 如果需要找三元钱,不用到2块钱,即一种。
用到2元钱。然后再用(1元,2元)去找1元钱。1种。
相加共2种。 - 如果需要找四元钱,不用到2块钱,即一种。
用到2元钱。然后再用(1元,2元)去找2元钱(见上)。2种。
相加共3种。 - 如果需要找五元钱,不用到2块钱,即一种。
用到2元钱。然后再用(1元,2元)去找3元钱(见上)。2种。
相加共3种。 - 如果需要找六元钱,不用到2块钱,即一种。
用到2元钱。然后再用(1元,2元)去找4元钱(见上)。即dp[n][m-peney[n]],3种。
相加共4种。
第三类(用1,2,3):
- 如果需要找一元钱,则不需要用到3元钱。即dp[n][m] = dp[n-1][m]。是1种。
- 如果需要找两元钱,不用到3块钱,即2种。
- 如果需要找三元钱,不用到3块钱,即2种。
用到3元钱。然后再用(前三种)去找0元钱。1种。
相加共3种。 - 如果需要找四元钱,不用到3块钱,即3种。
用到3元钱。然后再用(前三种)去找1元钱。1种。
相加共4种。 - 如果需要找五元钱,不用到3块钱,即3种。
用到3元钱。然后再用(1元,2元)去找2元钱(见上)。2种。
相加共5种。 - 如果需要找六元钱,不用到3块钱,即4种。
用到3元钱。然后再用(前三种)去找3元钱(见上)。3种。
相加共7种。
那么算到最后呢,数组是这样子的:
0 1 2 3 4 5 6
(1元) a0 【1】 【1】 【1】 【1】 【1】 【1】【1】
(2元) a1 【1】 【1】 【2】 【2】 【3】 【3】【4】
(3元) a2 【1】 【1】 【2】 【3】 【4】 【5】【7】
如果还有疑问,对不起,我已经把能讲的讲完了。。。。。