动态规划(DP)
动态规划,常用于:数学,管理科学,计算机科学,经济和生物信息学。
特征:一个问题,可以拆分成一个一个的子问题,解决子问题,顺带解决这个问题
核心思想:拆分子问题,记住过程,减少重复运算。
例子
1+1+1+1+1+1=? 等于6
在上面式子的再加上一个“ 1+”。答案是多少?
直接通过以往计算过的答案,再加1
例题:青蛙跳阶问题:
一只青蛙,可以一次跳上1级台阶,也可以一次跳上2级台阶。求这只青蛙跳10级台阶有多少种跳法?
分析:
要跳到第10级台阶,要么从第8级台阶,跳2级到第10级。要么从第9级跳1步到第十级。
要跳到第9级:可以从第8级跳1步到达,也可以从第7级跳两步到达。
要跳到第8级:可以从第7级跳1步到达,也可以从第6级跳两步到达。
............
方法一:暴力递归
//暴力递归 //时间复杂度=解决子问题的时间*子问题个数(存在大量重读运算) public class 青蛙跳阶 { public static void main(String[] args) { long l1 = System.currentTimeMillis(); int ways = ways(30); long l2 = System.currentTimeMillis(); System.out.println("有" + ways + "种跳法"); System.out.println(l2 - l1); } public static int ways(int n) { if (n == 1) { return 1; } if (n == 2) { return 2; } return ways(n - 1) + ways(n - 2); } }
该方法时间复杂度过大,程序运行速度较慢,考虑使用数据字典进行优化
方法二:使用数据字典存储子问题的解(暴力递归的优化)
public class 青蛙跳阶_数据字典优化 { public static void main(String[] args) { long l1 = System.currentTimeMillis(); int ways = ways(30); long l2 = System.currentTimeMillis(); System.out.println("有" + ways + "种跳法"); System.out.println(l2 - l1); } static Map<Integer, Integer> map = new HashMap(); public static int ways(int n) { if (n == 1) { return 1; } if (n == 2) { return 2; } if (map.containsKey(n)) { return map.get(n); } else { map.put(n, ways(n - 1) + ways(n - 2)); return map.get(n); } } }
方法三:动态规划
动态规划和带字典的递归有些不同,但解法类似。都有减少重复运算。
动态规划的特征:
最优子结构:f(n) = f(n-1) + f(n-2), f(n-1)和 f(n-2)就是f(n)的最优子结构。
状态转移方程:f(n) = f(n-1) + f(n-2)
边界:f(1) =1 ,f(2) = 2
重叠子:重复的运算:f(10) = f(9) + f(8),f(9) = f(8)+f(7). f(8)就是重叠子
public class 青蛙跳阶_动态规划 { public static void main(String[] args) { System.out.println(ways(10)); } public static int ways(int n) { if (n == 1) { return 1; } if (n == 2) { return 2; } int a = 1; int b = 2; int temp = 0; for (int i = 3; i <= n; i++) { temp = a + b; a = b; b = temp; } return temp; } }
动态规划的解题思路
什么样的问题适用动态规划?
如果一个问题,可以把所有的答案穷举出来,而且存在重叠子问题,就可以考虑使用动态规划。
动态规划的经典应用场景:
最长递增子序列 ---20年蓝肽子问题
最小距离 --- 数字三角形
背包问题
凑零钱问题
等等等。。。
动态规划的解题思路
核心思想:拆分子问题,记住过程,减少重叠子运算
穷举分析
确定边界
找出规律,确定最优子结构
写出状态转移方程
代码实现