【17】暴力递归改dp(下)

目录

一.两人玩游戏

二.象棋游戏

三.鲍勃存活

四.凑钱方案数问题


一.两人玩游戏

 题目:有一个正整数数组,A和B两个人轮流拿数组的最左或最右的数值,返回最终的最高分数。

暴力递归版本 

	public static int win1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		// 0.。N-1范围上,先手获得最优分和后手获得最优分,返回较大的
		return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
	}	

    // 先手函数
	// 当前该你拿,arr[i..j]
	// 返回你的最好分数
	public static int f(int[] arr, int i, int j) {
		if (i == j) {
			return arr[i];
		}
		// i..j
		return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
	}

	// 后手函数
	// 当前步该你拿,是对方在arr[i..j]范围上拿
	// 返回你的最好分数
	public static int s(int[] arr, int i, int j) {
		if (i == j) {
			return 0;
		}
		// i..j 我在哪个范围选数,是由对手决定的,对手一定会将较少的留给你
		return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
	}

记忆化搜索版本

	public static int win1(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int[][] fdp=new int[arr.length][arr.length];
		int[][] sdp=new int[arr.length][arr.length];
		for(int i=0;i<arr.length;i++) {
			for(int j=0;j<arr.length;j++) {
				fdp[i][j]=-1;
				sdp[i][j]=-1;
			}
		}
		// 0.。N-1范围上,先手获得最优分和后手获得最优分,返回较大的
		return Math.max(f(arr, 0, arr.length - 1, fdp, sdp), s(arr, 0, arr.length - 1, fdp, sdp));
	}

	// 先手函数
	// 当前该你拿,arr[i..j]
	// 返回你的最好分数
	public static int f(int[] arr, int i, int j, int[][] fdp, int[][] sdp) {
		if (i == j) {
			return arr[i];
		}
		if(fdp[i][j]!=-1) {
			return fdp[i][j];
		}
		// i..j
		fdp[i][j]=Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
		return fdp[i][j];
	}

	// 后手函数
	// 当前步该你拿,是对方在arr[i..j]范围上拿
	// 返回你的最好分数
	public static int s(int[] arr, int i, int j, int[][] fdp, int[][] sdp) {
		if (i == j) {
			return 0;
		}
		if(sdp[i][j]!=-1) {
			return sdp[i][j];
		}
		// i..j 我在哪个范围选数,是由对手决定的,对手一定会将较少的留给你
		sdp[i][j]=Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
		return sdp[i][j];
	}

动态规划版本

每个位置都依赖于该位置左侧和下侧一个格子。所以这里可以按照对角线交替求解。

	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]);
	}

关键点:把图画出来,标出base case,找出一般位置的依赖关系。

二.象棋游戏

题目:马起始位于(0,0)位置,随后给出目标位置(x, y),要求马必须走k步到达目标位置,问从起始到达目标方案数

暴力递归版本 

	public static int getWays(int x, int y, int step) {
		return process(x, y, step);
	}

	// 潜台词:从(0, 0)位置出发
	// 要去往(x, y)位置,必须跳step步
	// 返回方法数
	public static int process(int x, int y, int step) {
		// 越界判断,有多少种到达越界位置,0种方法
		if (x < 0 || x > 8 || y < 0 || y > 9) {
			return 0; 
		}
		// 已经跳完了,不能动了
		if (step == 0) {
			return (x == 0 && y == 0) ? 1 : 0;
		}
		// 要到达的位置不越界,也有步数可以跳
		return process(x - 1, y + 2, step - 1)
				+ process(x + 1, y + 2, step - 1)
				+ process(x + 2, y + 1, step - 1)
				+ process(x + 2, y - 1, step - 1)
				+ process(x + 1, y - 2, step - 1)
				+ process(x - 1, y - 2, step - 1)
				+ process(x - 2, y - 1, step - 1)
				+ process(x - 2, y + 1, step - 1);
	}

动态规划版本

	public static int dpWays(int x, int y, int step) {
		if (x < 0 || x > 8 || y < 0 || y > 9 || step < 0) {
			return 0;
		}
		int[][][] dp = new int[9][10][step + 1];
		dp[0][0][0] = 1;
		for (int h = 1; h <= step; h++) {
			for (int r = 0; r < 9; r++) {
				for (int c = 0; c < 10; c++) {
					dp[r][c][h] += getValue(dp, r - 1, c + 2, h - 1);
					dp[r][c][h] += getValue(dp, r + 1, c + 2, h - 1);
					dp[r][c][h] += getValue(dp, r + 2, c + 1, h - 1);
					dp[r][c][h] += getValue(dp, r + 2, c - 1, h - 1);
					dp[r][c][h] += getValue(dp, r + 1, c - 2, h - 1);
					dp[r][c][h] += getValue(dp, r - 1, c - 2, h - 1);
					dp[r][c][h] += getValue(dp, r - 2, c - 1, h - 1);
					dp[r][c][h] += getValue(dp, r - 2, c + 1, h - 1);
				}
			}
		}
		return dp[x][y][step];
	}

	public static int getValue(int[][][] dp, int row, int col, int step) {
		if (row < 0 || row > 8 || col < 0 || col > 9) {
			return 0;
		}
		return dp[row][col][step];
	}

三.鲍勃存活

问题:有一个大小为N*M的网格,给出鲍勃的起始位置(a,b),鲍勃必须走K步,求鲍勃活下来的概率(不越界)。

4^k就是Bob走K步的所有情况,所以概率就是用生存的( 方法数/4^k )

暴力递归版本

	public static String bob1(int N, int M, int i, int j, int K) {
		long all = (long) Math.pow(4, K);
		long live = process(N, M, i, j, K);
		long gcd = gcd(all, live);
		return String.valueOf((live / gcd) + "/" + (all / gcd));
	}

	// N*M的区域,Bob从(row,col)位置出发,走rest步之后,获得的生存方法数
	public static long process(int N, int M, int row, int col, int rest) {
		if (row < 0 || row == N || col < 0 || col == M) {
			return 0;
		}
		// row, col没越界
		if (rest == 0) {
			return 1;
		}
		// 还没走完,row, col也没有越界
		long live = process(N, M, row - 1, col, rest - 1);
		live += process(N, M, row + 1, col, rest - 1);
		live += process(N, M, row, col - 1, rest - 1);
		live += process(N, M, row, col + 1, rest - 1);
		return live;
	}

	public static long gcd(long m, long n) {
		return n == 0 ? m : gcd(n, m % n);
	}

记忆化、动态规划套路一样。。。

四.凑钱方案数问题

题目:有一个无重复元素的正整数数组,里面的元素可以重复使用任意次,给定一个目标面值aim,求达到目标aim的组合方法数。

暴力递归版本 

	// arr里都是正数,没有重复值,每一个值代表一种货币,每一种货币可以使用无限张
	// 最终要找的零钱数就是aim
	// 找零方法数返回
	public static int way1(int[] arr, int aim) {
		return process(arr, 0, aim);
	}
	
	// 可以自由使用arr[index...]所有的面值
	// 需要搞定的钱数是rest
	// 返回找零的方法数
	public static int process(int[] arr, int index, int rest) {
		if(index==arr.length) {
			return rest==0?1:0;
		}
		// arr[index] 0张 1张 ... 不要超过rest的钱数
		int ways=0;
		for(int zhang=0;arr[index]*zhang<=rest;zhang++) {
			ways+=process(arr, index+1, rest-arr[index]*zhang);
		}
		return ways;
	}

 动态规划版本

public static int way2(int[] arr, int aim) {
		if(arr==null||arr.length==0)){
			return 0;
		}
		int[][] dp=new int[arr.length+1][aim+1];
		dp[arr.length][0]=1;
		for(int index=arr.length-1;index>=0;index--) {
			for(int rest=0;rest<=aim;rest++) {
				int ways=0;
				for(int zhang=0;arr[index]*zhang<=rest;zhang++) {
					ways+=dp[index+1][rest-arr[index]*zhang];
				}
				dp[index][rest] = ways;
			}
		}
		return dp[0][aim];
	}

上面第三层的循环可以进行优化

? = a + b + c + ...   

X = b + c + ...

=>  ? = X + a

	public static int way2(int[] arr, int aim) {
		if(arr==null||arr.length==0)){
			return 0;
		}
		int[][] dp=new int[arr.length+1][aim+1];
		dp[arr.length][0]=1;
		for(int index=arr.length-1;index>=0;index--) {
			for(int rest=0;rest<=aim;rest++) {
				dp[index][rest]=dp[index+1][rest];
				if(rest-arr[index]>=0) {
					dp[index][rest]+=dp[index][rest-arr[index]];
				}
			}
		}
		return dp[0][aim];
	}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

看未来捏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值