记录一下最近复习动态规划的心得。
动态规划的核心在于找到状态转移方程。因为动态规划的思想就是当前状态来源于上一刻的状态。
关于确定状态转移方程: 以基本的卖出股票最佳时机为例:
1.首先要确定方程中dp数组的定义和下标,也就是状态的具体含义(股票问题中表示当前资金)。
2.确定状态转移方程:
第i天的状态有两种: 持股 or 不持股。
如果持股: 因为今天(i天)买了股票,或者因为昨天(i-1)天就已经持有了股票,今日无操作
若不持股 可能是因为今天(i天)卖出了股票,或者昨天就没有股票,今日无操作。
3.初始化数组。比如第一天的时候的情况,往往需要特殊处理。
4.遍历顺序,在背包问题中具体就是物品和背包的顺序。
以凑硬币例题为例:
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
题解:
public int coinChange(int[] coins, int amount) {
if(coins.length == 0)
return -1;
int max= amount+1;
int[] dp = new int[max];
//所有位置设置最大值
Arrays.fill(dp,max);
dp[0] = 0; //初始状态
for(int i = 1;i<max;i++){ //外层循环 遍历所有价格 即从0掉amount 每次加1块钱
for(int j=0;j<coins.length;j++){ //内层循环 遍历所有硬币面值 即遍历数组里每个面值的硬币种类
if(coins[j]<=i){ //如果第j个硬币的价值大于i 就不考虑了 此时对应的硬币个数是dp[i] =amount+1
dp[i] = Math.min(dp[i],dp[i-coins[j]]+1);
// 不取第j个面值的硬币 取第j个面值的硬币
//dp[i] 是i块钱时 需要硬币的个数
}
}
}
if(dp[amount]<=amount){
return dp[amount];
}else{
return -1;
}
}
1.确定dp数组: dp[i] 表示 金额为i时,需要的硬币个数为dp[i]。
2.确定dp数组:dp[i] = Math.min(dp[i] , d[i-coins[j]]+1) 其 意义为:如果取面值为coins[j]的硬币,总金额会变成 i - coins[j] 硬币个数会+1。不取则情况不变。
3.初始化数组,硬币问题中的数组初始化比较特别,我们需要遍历从0块钱到目标金额(参数amount)的所有情况。所以我们需要一个长度为amount+1 的数组。
同时,利用Arrays.fill()方法将数组每个位置元素设为amount+1 方便后面比较。
dp[0] = 0 因为0块钱不需硬币。至此 初始化数组完成。
4.遍历顺序:有两个需要遍历的数组,一个是表示价格的dp数组,从0块钱到amount,是题解中的外层。第二个是不硬币数组,每个元素都是不同面值的硬币,是题解中的内层。在内层遍历中还要加上遍历条件(coins[j]<=i) 即当硬币面值小于等于当前金额时,才会继续下去,否则dp数组对应位置不会改变,仍为最大值amount+1。
最后的判断:dp[amount]>amount
如果为真 说明此时dp[amount] = amount+1,结合遍历顺序,说明 amount金额无法达到,返回-1
如果为假,说明此时金额可以达到,返回dp[amount]。
未完待续