动态规划
基本思想: 将待求解的问题分解成若干个子问题,先求解子问题,然后从这若干个子问题(和分治区别:往往不独立)的解中得到原问题的解。有些子问题或许被重复计算多次,在动态规划中,会保存每个被计算过子问题的解,将其填入表中。
动态规划一般步骤
- 找出最优解的性质(将要计算的东西分成两部分,两部分都是最优解才满足整体最优),刻画其结构特征
- 递归定义最优值
- 自底向上计算最优值
- 根据最优值时得到的信息,构造最优解
1-3是基本步骤,第四步可以视情况省去
实现思路
以硬币问题解释:
1、确定状态:
最后一步(最优策略中使用的最后一枚硬币ak);
化成最优子问题(最少的硬币评出更小的面值27-ak)
2、转移方程:根据子问题的定义直接得到f[X] = min{f[X-2]+1,f[X-5]+1,f[X-7]+1}
3、初始条件(用转移方程算不出来的,需要手动定义)和边界情况(不要越界)
4、计算顺序:要利用之前的计算结果,一步一步得到最后的结果
例题
1. Coin Change问题
比如: 三种硬币:2 5 7。买27的东西,用最少钱付清,且不着钱
分析: 这是一个求最大最小值的问题(75555)
用贪心,尽量大——7775,少一块凑不出来 ==>不对
最优策略一定是有K枚硬币a1、a2、……ak面值加起来是27,这说明一定有最后一枚硬币ak,除掉这枚硬币,前面硬币的面值加起来的值是27-ak
使用递归解
递归问题:重复计算很多,效率低下下面的递归可以看出来有很多重复计算
从0开始算起,一次算3种,一共算27步,时间复杂度:n*m(n种硬币,m块钱)
// An highlighted block
public class DynamicProgramming {
/*
硬币问题,动态规划
*/
public static int getMinNums(int[] coins, int money) {
int[] arr = new int[money+1];
arr[0] = 0;
for (int i = 1; i <= money; i++) {
int min = Integer.MAX_VALUE;
for (int j = 0; j < coins.length; j++) {
int t1 = coins[j];
//注意这里判断了一下arr[i-t1]是不是最大整数,是为了防止最大整数加1越界
if (i - t1 < 0||arr[i - t1]==Integer.MAX_VALUE) {
continue;
} else {
min = Math.min(min, arr[i - t1] + 1);
}
}
arr[i] = min;
}
return arr[money];
}
/*
硬币问题递归解法
*/
public static int getMinNumsRecur(int[] coins, int money) {
if (money == 0) {
return 0;
}
int res = Integer.MAX_VALUE;
for (int i = 0; i < coins.length; i++) {
if (money >= coins[i]) {
int res1 = Integer.valueOf(getMinNumsRecur(coins, money - coins[i]));
if(res1!=Integer.MAX_VALUE) {
res = Math.min(res, res1 + 1);
}
}
}
return res;
}
public static void main(String[] args) {
int[] coins = {
2,5,7};
int money = 27;
System.out.println("动态规划解法");
System.out.println(getMinNums(coins,money));
System.out.println("递归解法");
System.out.println(getMinNumsRecur(coins,money));
}
}
//我笔试时遇到一种变体:在最少的