本文已参与「新人创作礼」活动,一起开启掘金创作之路。
\
动态规划理解
例子1-斐波那契数列
动态规划就是利用空间换时间
以斐波那契数列为例
static int f(int n){ if(n == 1) return 1; if(n == 2) return 1; return f(n-1)+f(n-2); }
编辑
当想要求解f(6)时, f(4),f(3)需要求解多遍,很浪费时间,如果把第一遍的求解结果直接存起来,再次需要求解时直接调用,就可以节省时间了。
如果求f(6)函数的调用过程为:f(6)->f(5)->f(4)->f(3)->f(2)->f(1)->f(1)->f(2)->f(3)->f(4),复杂度是O(n),而递归方式的斐波那契数列复杂度是O(2^n)
例子2
编辑
利用递归处理
public class Main1 { public static void main(String[] args) { System.out.println(f(4,2,4,4)); } static int f(int n,int start,int aim,int k){ return process1(start,k,aim,n); } //机器人现在在cur位置 //还有rest步 //需要走到aim位置 //一共有1-2个位置 //返回机器从cur走rest步后,停到aim的方法数 static int process1(int cur,int rest,int aim,int n){ if(rest == 0){//没有步数了,如果此时在aim位置,就说明有一种方法 return cur==aim ? 1 : 0; } //rest > 0 还需要继续走 if(cur == 1){ //只能走到第二个位置 return process1(2,rest-1,aim,n); } if(cur == n){ //只能走到倒数第二个位置 return process1(n-1,rest-1,aim,n); } //中间位置 return process1(cur-1,rest-1,aim,n) + process1(cur+1,rest-1,aim,n); } }
优化1:将重复计算的值缓存起来
编辑
在7位置还有10步等价于在6位置还有9步加在8位置还有9步,此过程出现很多重复计算,可以将已经计算过的数缓存起来
public class Main1 { public static void main(String[] args) { System.out.println(f(4,4,2,4)); //一共4个位置,从位置2走4步走到位置4的走法 } static int f(int n,int k,int begin,int aim){ int [][] table= new int[n+1][k+1]; //改表用于缓存计算过的结果 for(int i = 0; i < n+1; i++) for(int j = 0; j < k+1; j++) table[i][j] = -1; //某个位置的值为-1代表这个位置没被计算过 return process(table,begin,k,aim,n); //返回问题最终结果 } //当前位置cur的范围是1-n //剩余步数rest的范围是0-k //将这些取值放到table[n+1][k+1]中 static int process(int[][] table,int cur,int rest,int aim,int n){ //之前计算过 if(table[cur][rest] != -1) return table[cur][rest]; //计算过就直接返回值 //之前没有计算过 int ans = 0; //存储最终方法数的结果 if(rest == 0){ //如果剩余步数为0 ans = cur == aim ? 1 : 0; } else if(cur == 1){ //如果当前在第一个位置 ans = process(table,2,rest-1,aim,n); } else if(cur == n){ //如果当前在最后一个位置 ans = process(table,n-1,rest-1,aim,n); }else { //如果当前在中间 ans = process(table,cur-1,rest-1,aim,n) + process(table,cur+1,rest-1,aim,n); } table[cur][rest] = ans; //将计算结果缓存到表中 return ans; } }
优化2:可以根据递归过程直接将二维表画出
根据第一个语句
if(rest == 0){//没有步数了,如果此时在aim位置,就说明有一种方法 return cur==aim ? 1 : 0; }
当step等于0时,只有当cur等于目标位置时,才填入1,其他都填入0,所以可以填好第一列
根据第二个语句
if(cur == 1){ //只能走到第二个位置 return process1(2,rest-1,aim,n); } if(cur == n){ //只能走到倒数第二个位置 return process1(n-1,rest-1,aim,n); }
当cur为1或n时,分别等于cur为2,step-1和cur为n-1,step-1位置的数,也就是第一行都依赖于左下,最后一行都依赖于左上
根据第三个语句
return process1(cur-1,rest-1,aim,n) + process1(cur+1,rest-1,aim,n);
当处于中间时,分别依赖左上和左下
编辑
可以通过分析直接得出左右情况的缓存表,可以直接查询想要的信息
static int process2(int n,int k,int aim,int start){ int [][] table= new int[n+1][k+1]; for(int rest = 0; rest <= k; rest++){ //列 for(int cur = 1; cur <= n; cur++){ //行 if(rest == 0){ if(cur != aim){ table[cur][rest] = 0; }else { table[cur][rest] = 1; } }else { if(cur == 1){ table[cur][rest] = table[cur+1][rest-1]; } else if(cur == n){ table[cur][rest] = table[cur-1][rest-1]; } else { table[cur][rest] = table[cur-1][rest-1] + table[cur+1][rest-1]; } } } } return table[start][k]; }
代码优化
static int process3(int n,int k,int aim,int start){ int [][] table = new int[n+1][k+1]; table[aim][0] = 1; for(int rest = 1; rest <=k; rest++){ table[1][rest] = table[2][rest-1]; for(int cur = 2; cur < n; cur++){ table[cur][rest] = table[cur-1][rest-1]+table[cur+1][rest-1]; } table[n][rest] = table[n-1][rest-1]; } return table[start][k]; }
最终思路
起始位置:start
目标位置:aim
位移数:k
位置个数:n
当前位置:cur
剩余步数:rest
在递归中发现只有cur和rest是变化的,所以根据cur和rest画一个二维表,表的大小由n和k限制,绘制完后,每个横坐标都可看作是一个start,列都可以看作是一个k,可以根不同的start和key直接查询出到达终点aim的方法数。
例子3
编辑
public class Main2 { public static void main(String[] args) { int[] a = {5,7,4,5,8,1,6,0,3,4,6,1,7}; int first = before(a,0,a.length-1); int second = after(a,0,a.length-1); System.out.println(Math.max(first,second)); } //先手 static int before(int[] a,int L,int R){ //只有一张牌 if(L==R) return a[L]; int degree1 = a[L] + after(a,L+1,R); //先手拿L位置的牌 int degree2 = a[R] + after(a,L,R-1); //先手拿R位置的牌 //返回最大值 return Math.max(degree1,degree2); } //后手 static int after(int[] a,int L,int R){ //只有一张牌,并且是后手拿,那么等于拿不到牌 if(L == R) return 0; int degree1 = before(a,L+1,R); //对手拿走了L位置的牌 int degree2 = before(a,L,R-1); //对手拿走了R位置的牌 //返回最小值 return Math.min(degree1,degree2); } }
\