左神算法笔记(十)——递归和动态规划

递归和动态规划介绍

左神算法笔记(十)——递归和动态规划_第1张图片

求n!的结果

递归方法解决:

public static long getFactorial1(int n){
	//下面的if为明确的不再递归的条件
	if(n==1){
		//不再递归之后实现的操作
		return 1L;
	}
	//将问题转化为规模缩小了的同类问题的子问题
	return (long)n*getFactorial1(n-1);
}

非递归方法:

public static long getFactorial2(int n){
	long result = 1L;
	for(int i = 1;i<=n;i++){
		result *= i;
	}
	return result;
}

汉诺塔问题:打印n层汉诺塔从最左边移动到最右边的全部过程

三个竹竿,最开始最左边有n层汉诺塔,要将其全部移动到最右边的竹竿上,在移动的过程中只能小压大,不能大压小。

现在分为三个,from,to,help。移动的全部过程分为:

  1. 将上面n-1全部挪到help上
  2. 将n挪到to上去
  3. 将n-2挪到from上,将n-1挪到to上去
  4. 。。。
    代码:
public static void process(int N,String from,String to,String help){
	if(N==1){
		System.out.println("move 1 from" + from + " to " + to;
	}else{
		process(N-1,from,help,to);
		System.out.println("move" +N +"from " +from + "to" + to);
		process(N-1,help,to,from);
	}
}

以上的代码是整个程序的整体思路,也可以将过程详细化,将整体分为六个过程,然后彼此嵌套。

打印一个字符串的全部子序列,包括空字符串。

尝试过程:字符串共有多少长度,对于每一位,都有两个决策,所以总的可能性2^N,这是一道简单的高中数学排列组合问题,所以可以进行要不要的递归,最终实现所有的情况。

public static void printAllSub(char[] Str,int i,String res){
	if(i == str.length){
		System.out.println(res);
		return;
	}
	//不要当前字符
	printAllSub(str,i+1,res);
	//要当前字符
	printAllSub(str,i+1,res+String.valueof(str[i]));
}

对于每次递归,都走要不要当前字符两条路,从而可以实现打印所有的子序列,如果题目进行修改,不包括空字符串的haul,则此时可以进行空字符串的判断,进行删除。

母牛每年生一只母牛,新出生的母牛成长三年后也可以每年生一只母牛,假设母牛不会死,求N年后,母牛的数量。假设最开始母牛是A(就一个牛)。

最开始是A,这是第一批,之后每年增加1,第二年出生的牛增加三年可以生小牛,则可以设立新出生的牛一个i=3的数值,每过一年则i–,当i<=0时则第二年加上该牛的两倍,同时让该数目的牛重新设置i,这是我自己的思路。

但是通过数值的变化可以发现数目满足一个规律,F(n) = F(n-1)+F(n-3)。今年的牛等于上一年的牛的数目加上三年前牛的数目。

给定一个二维数组,二维数组中的每个数都是正数,要求从左上角到右下,每一步只能向右或向下,沿途经过的数字要累加起来,返回最小路径和。

由暴力版本改成动态规划
自己思路:这道题可以按照上方的打印全部字符串进行计算,将结果存放到一个小跟堆中,最后返回小根堆的根就是最小路径和。非常暴力

左神尝试版本:


public static int walk(int[][] matrix,int i,int j){
	if(i==matrix.length-1 && j == matrix[0].length-1){
		return matrix[i][j];
	}
	if(i == matrix.length-1){
		return matrix[i][j] + walk(matrix,i,j+1);
	}
	if(j == matrix[0].length-1){
		return matrix[i]ij]+ walk(matrix,i+1,j);
	}
	int right = walk(matrix,i,j+1);//右边位置到右下角的最短路径和
	int down = walk(matrix,i+1,j);//下边位置到右下角最短路径和
	return matrix[i][j] + Math.min(right,down);
} 

上述暴力递归中存在很多的重复计算(一个先向下再像右,一个先向右再向下,此时都会到(1,1)位置,上述题目会存在重复计算)
当发现递归中存在重复状态,并且重复状态和与到达他的路径没有关系的时候,可以将递归改成动态规划

动态规划要求点无后效性,也就是说当给出了参数之后,结果唯一。如果给定参数之后,返回值不是唯一确定的时候,此时成为有后效性。(比如要求记录路径的时候,路径方式不一定唯一)

动态规划:
对于这道题目,可以按照上述代码,可以首先将最后一行中到右下角的距离挨个计算出,此时为最后一行的点到右下角的距离。也要先将最后一列中到右下角的距离挨个算出,为最后一列的点到右下角的距离。此时可以从右到左,再从下到上,就可以 计算出整个距离。

暴力递归改成动态规划的步骤:

  1. 先写出一个暴力递归(尝试版本)
  2. 确定无后效性,列出可变参数(哪几个可变参数可以代表返回值的状态)可变参数几维的那就是一张几维的表。
  3. 看最后需要终止的位置是哪一个,在表中确定出来。回到baseCase中,将完全不依赖的值设置好(本题中就是设置最后一行和最后一列)
  4. 最后普遍位置看看需要哪些位置,逆序返回,就是填表的顺序。
  5. 最后就可以将暴力递归改成动态规划。

给定一个数组arr和一个整数aim,如果可以任意选择arr中的数字,能不能累加得到aim,返回true或者false。

这道题是作为动态规划的练习题,可以发现题目的求解过程跟上一道题类似.
思路:设置sum,对于每一个数组中的数字,都可以进行判断,是否要当前的数字,分为两种情况,然后最后和aim相比较。如果发现最后的结果中存在aim,则返回true。

  1. 左神代码:暴力递归版本:
public static boolean isSum(int[] arr,int i,int sum,int aim){
	if(i == arr.length){
		return sum = aim;
	}
	return isSum(arr,i+1,sum,aim) || isSum(arr,i+1,sum+arr[i],aim);
}
  1. 分析是否有后效性:之前形成的累加和确定跟之后的数组无关
  2. 分析可变参数:i,sum是可变参数,arr和aim确定,所以建立二维表(i,sum)。
  3. 结合暴力版本中i==arr.length,所以i为0~N,起始位置为(0,0)。最后一行中只有sum等于aim上的值时返回true。
  4. 子过程i+1,所以可以从最后一行推出倒数第二行,上一行的位置有sum的位置和sum+arr[i]确定,从而每个位置都可以推出来。

你可能感兴趣的:(左神算法专栏)