动态规划
如何列出正确的状态转移方程:
- 确定base case
- 确定“状态",也就是原问题和子问题中的变量
- 确定选择,也就是导致“状态”产生变化的行为
- 明确 dp 函数 / 数组的定义
动态规划的暴力求解阶段就是回溯法。只有有的问题可以通过巧妙的定义,构造出最优子结构,找出重复子问题,可以用DP table或者“备忘录”优化,将递归树大幅剪枝,这就是动态规划的解法。——先找出动态转移方程,再穷举“状态”
而回溯法,就是穷举。
斐波那契数列
-
暴力递归
int fib(int N){ if(N == 0) return 0; if(N == 1 || N == 2) return 1; return fib(N - 1) + fib(N - 2); }
时间复杂度O(2^n)
-
带备忘录的递归解法——自顶向下
每次算出某个子问题的答案后别返回,先将其记到"备忘录"里再返回;每次遇到一个子问题先去备忘录里查
- 使用数组当“备忘录”
- 哈希表(字典)
Integer[] memo; public int fib(int n) { memo = new Integer[n+1]; return recursive(n); } private int recursive(int num){ if(memo[num]!=null) return memo[num]; if(num==0) return 0; if(num==1) return 1; return memo[num] = ( fib(num-1)+fib(num-2)); }
-
dp数组的迭代解法——自底向下
public int fib(int n) { if(n == 0) return 0; if(n == 1 || n == 2) return 1; int[] f= new int[n+1]; f[0] = 0; f[1]= 1; for(int i = 2;i<=n;i++) f[i] =f[i-1]+f[i-2]; return f[n]; }
-
优化DP表,两个状态
int fin(int N){ if(N == 0) return 0; if(N == 2 || N == 1) return 1; int prev = 1,curr = 1; for(int i = 3;i <= N;i++){ int sum = prev + curr; prev = curr; curr = sum; } return curr; }