动态规划-博弈问题-力扣877-石子游戏

       亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

示例:

输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/stone-game
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

       看到这题可能最直观会想到博弈的两个人(假设为A和B,A先取数)从数组的两边开始取数,直至把数组取完,即A先取5,B再取5,A取4,B取3,最后A赢,其实问题并没有这么简单。比如[3,9,1,2],在这个时候如果A还是首先取3,那么B肯定就把9取走了,A就输了,事实上A是非常聪明的,A总会在取数的时候做出利于全局的决策,而不是只看到局部

       那么A会怎么取数呢?要想知道A会怎么取数,我们首先定义一个二维数组dp,它的长度和宽度为石头的堆数,其中dp[i][j]的值表示从i石堆到j石堆A比B多取的石头数,那么假设有n堆石头,在A和B取完所有石头之后,要怎么表示A比B多的石头数呢?

  毫无疑问,它应该为dp[0][n-1]如果这个数是正的,那就是A赢,否则就是B。

      我们以[3,9,1,2]为例,求dp[0][n-1]

      首先,A就面临一个问题,他应该先取3还是2呢?他简单分析了一下,如果他先取3,那么他最后比B多的石头数为

dp[1][3]+piles[0],dp[1][3]表示在之后的博弈过程中,A比B多的石头数,那么,可以得到在整个过程中A比B多的石头数

dp[0][3] = dp[1][3]+piles[0],piles[0]为A已经拿到手的石头,当然可以直接相加。同样,如果A先取2,那么dp[0][3]=

dp[0][2]+piles[3],那么A到底应该先取3还是先取2呢?一开始就说了,A是非常聪明的,A当然会从dp[1][3]+piles[0]和

dp[0][2]+piles[3]这两种选一个更大的,那么我们可以得出:

                                dp[0][3] = max(dp[1][3]+piles[0],dp[0][2]+piles[3])

那么dp[1][3]和dp[0][2]应该怎么求得呢?我们可以得到更一般的递推式:

                                  dp[i][j] = max(dp[i+1][j]+piles[i],dp[i][j-1]+piles[j])

      上面我们一直在谈A取石头的过程,但是我们不能忘了,B也在取石头...B取石头的时候会是个什么情况呢?假设一开始A取3,那么B既可以取9也可以取2,B会怎么取呢?B和A一样聪明,他当然会尽可能取一个使dp尽可能小的数,最好还要使dp为负,既然这样,我们可以得出:

                                dp[1][3] = min(dp[2][3]-piles[1],dp[1][2]-piles[3])

      值得强调得是,dp表示的值为A比B多的石头数,因此B取石头的时候piles前面应该是-号,且max替换为min。更一般的递推式为:

                               dp[i][j] = min(dp[i+1][j]-piles[i],dp[i][j-1]-piles[j])

       A,B都捡石头,那么如何保证公平呢?也就是如何保证一人交替捡一次呢,如何保证这两个精明的家伙不占便宜呢?由于我们一开始是偶数堆石头,那么很容易可以得出如果还剩偶数堆石头的时候,A捡,否则,B捡。我们可用表格来描绘这个过程:

                                                      

       由之前的递推式,我们可以看出来是由小区间推大区间,故上图应该也是由黄->蓝->绿->橙(由于我们之前已经规定好,单个石堆数目的时候,B捡,故黄色格子里,A没有捡石头,石头全是B的,同理,绿色的时候也是由B先捡,B尽可能使自己捡更多石头),最后得出A和B分别捡得的石头数,箭头的方向为递推max和min函数的选择过程。下图为dp数组值的变化过程:

                                                   

下面为代码:

public static boolean stoneGame(int[] piles) {	    
		int len = piles.length;
		int dp[][] = new int[len][len];
		//只剩一个石头时,B拿,故为负
		for(int i = 0;i<len;i++){
			dp[i][i] = -piles[i];
		}
		/*
		 * size用来表示区间大小 从小区间,左到右的顺序开始遍历
		 * i表示区间的左边界  j表示区间的右边界
		 * 
		*/
		for(int size = 2;size<=len;size++){
			for(int i=0;i<=len-size;i++){
				int j = i+size-1;
				/*
				 * dp[i][j]的状态应该是由dp[i+1][j]和dp[i][j-1]转化而来
				*/
				//flag为奇数,也就是石堆为偶数堆时,A先捡
				int flag = (j - i ) % 2; 
				if(flag==1){
					dp[i][j] = Math.max(dp[i+1][j]+piles[i], dp[i][j-1]+piles[j]);
				}else {
					dp[i][j] = Math.min(dp[i+1][j]-piles[i], dp[i][j-1]-piles[j]);
				}
			}
		}
		return dp[0][len-1]>0;
    }
发布了17 篇原创文章 · 获赞 12 · 访问量 8318

猜你喜欢

转载自blog.csdn.net/Sun_Dean/article/details/103553305