左神算法笔记(十)——暴力递归改动态规划扩展

痞子三分冷 提交于 2020-02-10 21:25:15

题目一

换钱方法数

暴力递归

暴力递归的复杂度太高,中间存在很多的重复计算,比如0张200,2张100后面的情况跟1张200,0张100后面的情况数目相同,但是暴力递归不考虑这个问题,所以需要进行修改。

public static int coins1(int[] arr,int aim){
	if(arr == null || arr.length ==0 || aim <0){
		return 0;
	}
	return process1(arr,0,aim);
}

//int[] arr:不变的数组,面值
//index:可以任意自由使用数组中index之后的所有钱
//aim:目标钱数
//返回值:方法数
public static int process1(int[] arr,int index,int aim){
	int res = 0;
	if(index ==arr.length){
		res = aim == 0 ? 1 : 0;
	}else{
		//当前面值张数的确立
		for(int zhang = 0;arr[index]*zhang <= aim;zhang++){
			res += process1(arr,index+1,aim-arr[index]*zhang);
		}
	}
	return res;
}
	

改版1,记忆化搜索

考虑到重复问题,所以想到当aim和index固定的时候,此时可能性的数目相同,怎么样查找这样的数目,就可以想到采用hashMap的形式,利用哈希表去进行查询操作,如果当前的aim和index固定情况下数目没有保存,则存储到Map中,如果当前数值已经存在,则此时将读取数目,而不需要再次计算。

public static HashMap<String,Integer> map = new HashMap<>();
public static process_map(int[] arr,int index,int aim){
	int res = 0;
	if(index == arr.length){
		res = aim == 0? 1:0;
	}else{
		for(int zhang = 0;arr[index]*zhang <= aim;zhang ++){
			int nextAim = aim - arr[index] *zhang;
			String key = String.valueOf(index+1)+"_" + String.valueOf(nextAim);
			if(map.containsKey(key)){
				res += map.get(key);
			}else{
				res += process_map(int[] arr,index+1,nextAim);
			}
		}
	}
	map.put(String.valueOf(index)+ "_" + String.valueOf(aim),res);
	return res;
}
	

改版2

因为一共两个相关的参数,index和aim,可以按照笔记十中,将新建一个二维表,表中坐标为index和aim,index取值范围为0~N,aim范围为0-aim。观看暴力解法的代码流程,进行改版。
1. (找出最终目标)最后需要的目标为index为0时,aim取得的值。
2. (找出不依赖其他位置的初始位置)观看表中不需要依赖其他位置就可以确定值的位置,在本题中,index==arr.length的时候,可以直到,只有aim为0的时候值为1,其他index=N的位置均为0。
3. (确定位置依赖)假设现在需要确定的是(i,aim‘)的位置,此时需要确立的index的下一行中的某些位置。res += process1(arr,index+1,aim-arr[index]*zhang),所以此时下一个位置的数目由前一行中,第三个参数的这些位置,就可以求出当前位置的值。
此时题目已经转化为了一个二维数组问题,而不再是原始的题目。

此时再进行一步优化:确立了上一行跟下一行的关系,此时因为上一行跟下一行中固定位置差上数值有关系,所以当前位置可以由左边相差固定长度位置加上自己下方位置数值,则就是当前位置的值,此时计算当前值不再需要依次遍历下方的数值,下方数值每次只计算一遍,节省时间复杂度。

public static int coins4(int[] arr,int aim){
	if(arr == null || arr.length ==0 ||aim <0){
		return 0;
	}
	int[][] dp = new int[arr.length][aim +1];
	for(int i =0;i<arr.length;i++){
		dp[i][0] = 1;
	}
	for(int j = 1;arr[0]*j <= aim; j++){
		dp[0][arr[0]* j] =1;
	}
	for(int i = 1;i< arr.length;i++){
		for(int j =1;j<=aim;j++){
			dp[i][j] = dp[i-1][j];
			dp[i][j] += j - arr[i] >= 0 ? dp[i][j-arr[i]] :0;
		}
	}
	return dp[arr.length-1][aim];
}

题目二

纸牌博弈

暴力解法

public static int win1(int[] arr){
	if(arr == null || arr.length == 0){
		return 0;
	}
	return Math.max(f(arr,0,arr.length-1),s(arr,0,arr.length-1));
}

public static int f(int[] arr,int i,int j){
	if(i == j){
		return arr[i];
	}
	return Math.max(arr[i]+s(arr,i+1,j),arr[j]+s(arr,i,j-1));
}
public static int s(int[] arr,int i,int j){
	if(i == j){
		return 0;
	}
	return Math.min(f(arr,i+1,j),f(arr,i,j-1));
}

动态规划

从上述暴力递归的代码可以直到,一共包含两个参数,i和j。一共有两个函数,所以建立两张表,每个都是二维。
1. 因为i <=j,所以对于每张表中,左下部分全部为0,
2. (不依赖其他点的位置)需要的位置为对角线的位置,
3. (最后的目标位置)i=0,j=leng-1的时候为起始位置
4. (确定依赖)暴力递归中,arr[i]+s(arr,i+1,j),arr[j]+s(arr,i,j-1)以及下方的s函数中,可以看到依赖的是另一个函数左边和下面的位置
5. 对角线位置为起点,可以依次推出左边和右边对角线的上面一条平行线的数值,再依次向上推导,直到求出最后的结果。

public static int win2(int[] arr){
	if(arr ==null || arr.length == 0){
		return 0;
	}
	int[][] f = new int[arr.length][arr.length];
	int[][] s = new int[arr.length][arr.length];
	for(int j = 0;j <arr.length; j++){
		f[j][j] = arr[j];
		for(int i = j-1;i >=0;i--){
			f[i][j] = Math.max(arr[i] +s[i+1][j],arr[j] +s[i][j-1]);
			s[i][j] = Math.min(f[i+1][j],f[i][j-1]);
		}
	}
	return Math.max(f[0][arr.length-1],s[0][arr.length-1]);
}

题目三

一共有1~N个位置,假设机器人当前停留在M位置,此时机器人可以走p步,走到了1位置只能往右走,走到了N只能向左走。问走p步走到k位置有多少种走法。

暴力递归

//M表示来到的位置
//N表示一共有1~N个位置
//p是可以走的步数
//k最终停留的位置
//返回值:走法总数
public static int ways(int N,int M,int p,int k){
	if(N<2||M <1 || M>N ||p <0 || k<1){
		return 0;
	}
	if(p == 0){
		return M== k ? 1:0;
	}
	int res = 0;
	if(M ==1){
		res = ways(N,M+1,p-1,k);
	}else if(M ==N){
		res = ways(N,M-1,p-1,K);
	}else{
		res = ways(N,M+1,p-1,k)+ways(N,M-1,p-1,k);
	}
	return res;
	

	return 0;
}

动态规划

此时在本题中,参数是p和N,所以为二维表
1. (不依赖其他点的位置)p=0,N上为k时,结果为1,其他结果为0
2. (最后的位置)(p,k)上的数值为最后要求的结果。
3. (确定依赖)从(0,k)开始为1,设当前位置为(i,u)此时根据代码ways(N,M+1,p-1,k)+ways(N,M-1,p-1,k);得到相互依赖位置为(i+1,u+1)和(i+1,u-1)。从而一个节点数目的确定现在需要他左上的位置和加上右上的位置和,图像画出来之后为杨辉三角形。
4. 关于撞墙只是出了边界的不算,此时在二维表中都是一样的结果。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!