动态规划
1.概念
2.个人理解
个人感觉这是一种由最简单的状态推得复杂的状态的算法,相较于编程更是一种数学思想,用来解决存在相同子问题的时候求最优,缓存前一个状态的结果,避免重复计算。
3.硬币找零应用动态规划
假设有几种硬币,并且数量无限。请找出能够组成某个数目的找零所使用最少的硬币数。
硬币数量无穷大,硬币种类未知,求最少,存在找零时多种硬币面值使用与否 看上去像是动态规划可以解决的问题
抛开算法的概念,用数学的思想列出来一些结果找规律,假设硬币面值为2,3,5
列出表
表内容每一项为硬币数量
硬币面值/找零金额 0 1 2 3 4 5 6 7 8 9 10
2 INF INF 1 INF 2 INF 3 INF 4 INF 5
3 INF INF 1 1 2 2 2 3 3 3 4
5 INF INF 1 1 2 1 2 2 2 3 2
INF表示找不到
去掉表内重复内容
硬币面值/找零金额 0 1 2 3 4 5 6 7 8 9 10
只使用2找零 INF INF 1 INF 2 INF 3 INF 4 INF 5
使用2,3找零 1 2 2 2 3 3 3 4
使用2,3,5找零 1 2 2 2 3 2
INF表示找不到
单从这十项可以得到这样的一个结论:
当使用的最大硬币面值小于找零面值时,
其找零所需的硬币数与不使用这种硬币找零所需的硬币数相同,因为该种硬币太大,比如使用2,3面值的硬币给4找零时,使用面值为3的硬币不能给面值为4的钱换零,只能用两个面值为2的硬币找零。
同时,如果找零使用的最大硬币面值大于等于找零金额,其所使用的硬币总数总是小于等于不使用此种硬币时的数量。从表中的数据得出其所使用的硬币数量为 (找零金额-最大硬币面值)所需的硬币数量+1(最大面值硬币数量)。
如果用数组按从小到大的顺序进行存储的话得到:
int[] coins = new int[]{2,3,5};
可以列出这样的一个方程:
当j<coins[i] f(i,j)=f(i-1,j);
当j>=coins[i] f(i,j)=Integer.min(1+f(i,j-coin[i]),f(i-1,j)); 由于存在找零金额为4,硬币面值使用2,3时 使用一张3面值的硬币进行找零,余一个值为1的面值,没有硬币面值能兑现这个余数的情况,所以还要与使用2对4进行找零的情况进行比较得出合理的结果
i表示使用的最大硬币面值,j表示找零金额
由于每增加使用一种硬币,存在重复的硬币数量和不符合要求的数量
如表中找零金额为6的列
硬币面值/找零金额 6
只使用2找零 3
使用2,3找零 2
只使用硬币面值为2的进行找零时使用3个硬币
使用2,3找零时使用2个硬币
求最少,需要用2替换掉3
可以用一个一维数组存储,从找零金额为0推算出只使用最小面值的硬币时所需的硬币数
方便视觉效果再贴上上面的数据
硬币面值/找零金额 0 1 2 3 4 5 6 7 8 9 10 只使用2找零 INF INF 1 INF 2 INF 3 INF 4 INF 5 使用2,3找零 1 2 2 2 3 3 3 4 使用2,3,5找零 1 2 2 2 3 2 INF表示找不到
硬币面值/找零金额 0 1 2 3 4 5 6 7 8 9 10
只使用2找零 INF INF 1 INF 2 INF 3 INF 4 INF 5
伪代码:定义数组int[] dp = new int[11]{INF,INF,1,INF,2,INF,3,INF,4,INF,5};
当加入硬币面值为3时
硬币面值/找零金额 0 1 2 3 4 5 6 7 8 9 10
使用2,3找零 1 2 2 2 3 3 3 4
替换掉dp中较大的值
dp = {INF,INF,1,1,2,2,2,3,3,3,4}
方程可以修改为:
当j>=coins[i] f(j)=Integer.min(1+f(j-coin[i]),f(j)); 由于存在找零金额为4,硬币面值使用2,3时 使用一张3面值的硬币进行找零,余一个值为1的面值,没有硬币面值能兑现这个余数的情况,所以还要与使用2对4进行找零的情况进行比较得出合理的结果
此时,动态规划的三大分析条件
1.最优子结构 我未分析出
2.边界 0
3.状态转移方程 为上面的修改后方程
4.我的Java代码实现
/** * @param coins 零钱面值数组 * @param n 需要被找零的钱 */ public int coinForChange(int[] coins, int n) { //排序从小到大 Arrays.sort(coins); //找零的钱比最小零钱都小 找不开 if (n < coins[0]) { return -1; } int coinsKind = coins.length; //定义第一种零钱面值 0-n面额下找零硬币数量数组 int[] dp = new int[n + 1]; //余数 标记只使用最小面值硬币是否可以找零 int remainder; int value; //初始化找零数量 0-n计算找零所需硬币 找不开使用Integer的最大值表示 即上表中INF for (int i = 0; i < dp.length; i++) { remainder = i % coins[0]; //找不开时 使用找零钱币为最大表示 dp[i] = remainder == 0 ? i / coins[0] : Integer.MAX_VALUE; } //从加入第二种零钱面值 开始循环到第i种零钱面值 for (int i = 1; i < coinsKind; i++) { //加入的该种零钱金额比找零的总额大的,该找零钱币数量与上一次循环得到的结果相同 for (int j = coins[i]; j < dp.length; j++) { value = 1 + dp[j - coins[i]]; //若最小面值不能找开零钱时为2^31 - 1 //+1后int型溢出为负数 此时扔找不开 重新赋值为最大 value = value >= 0 ? value : Integer.MAX_VALUE; dp[j] = Integer.min(value, dp[j]); } } return dp[n]; }
最近在刷leetcode学习算法,经常遇到动态规划解决的问题,之前忽略了刷leetcode的目的,为刷题而刷题,每次遇到都需要花功夫理解程序,这几天忽然醒悟,研究算法解决问题,理解还是很浅,如果有错误或模糊不清的地方请各位大佬指教。