纸牌博弈问题

问题:

有一排正数,代表数值不同的纸牌排成一条线,玩家A和玩家B都可以看到。

每位玩家在拿走数字的时候,都只能从最左和最右的数中选择一个。

玩家A先拿,玩家B再拿,两人交替拿走所有的数字,

两人都力争自己拿到的数的总和比对方多。请返回最后获胜者的分数。

 

例如:

5,2,3,4

玩家A先拿,当前他只能拿走5或者4。

如果玩家A拿走5,那么剩下2,3,4。轮到玩家B,此时玩家B可以选择2或4中的一个,…

如果玩家A拿走4,那么剩下5,2,3。轮到玩家B,此时玩家B可以选择5或3中的一个,…



题解:双方都是绝对理性的个体,都会选择对自己最有利的方式。

从最基本的case来分析,如果只剩一张牌,那么先手的拿走这张牌,得到相应分数,后手的得到0;如果剩下两张牌,先手的一定拿走大的那一张,而后手的没得选择;依次类推…..

 

那么,我们就想,如何表示还剩多少张牌呢?数组下标标识头和尾就好了。

那如何表示先手后手呢?先手和后手只是下标标识不同而已,可用两个函数表示。因为后手是受先手影响的,所以也可以只用一个函数表示。

先从两个函数的来分析,第一个函数f(i,j),表示先手的选手在给定下标标识的情况下可以取得的分数。第二个函数s(i,j),表示后手的选手在给定下标标识的情况下可以取得的分数。

最后要求返回最大值,是整个数组在整个游戏过程结束时A,B两个选手所得的最大值,所以i和j应该标识数组的头和尾。即max(f(0,arr.length-1),s(0,arr.length-1))

然后根据这个逻辑就可以写出暴力递归的伪代码:

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));
		/*当i!=j时,说明至少还有两张纸牌,那么先手的人就有选择,是先拿第一张,还是拿最后一张。
		如果拿了第一张,那么剩下i+1到j的牌里他就是后手,
		同理先拿了最后一张,那么他在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时,说明至少还有两张纸牌,那么先手的人就有选择,是先拿第一张,还是拿最后一张。
		如果拿了第一张,那么后手的人在剩下i+1到j的牌里他就是先手,
		同理如果先手拿了最后一张,那么后手的人在i到j-1里先手。
		都是绝对理性的个体,所以先手一定会把更小的留给他。*/
	}

接下来就是优化的过程,直接根据递归改动态规划,不是直接就写动归。

分析递归:上述两个函数,分别表示先手的人得到的分数和后手的人得到的分数,可以容易的发现f只与i和j有关,s也只与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]);
	}

至此,问题解决,以上。

无后效性问题都可以通过暴力递归直接推出优化的动态规划版本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值