877. 石子游戏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35923749/article/details/84995957

877. 石子游戏

1. 题目描述
题目链接

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 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 。
提示:
2 <= piles.length <= 500
piles.length 是偶数。
1 <= piles[i] <= 500
sum(piles) 是奇数。

2. 题目分析
刚看到这题目时,我单纯的以为只要使用贪心算法,每次在开始处和结束处选择更大的那堆石子,最后拿到总的石子数就会是最多的,后面发现有一些用例测试过不去。所以重新思考这个流程,发现,这个题目要求的是的全局最优解,而不是局部最优解,我们如果使用贪心算法,找到的只是局部最优解,但不一定是全局最优解。

比如说[6,9,4,2],贪心算法的话,第一人首先会选择6,那么第二人则选择9,第一人再选择4,第二人会选择2,那么最终第一人获得石子数:6+4=10;第二人:9+2=11;11 > 10,所以第二人会获胜,这显然不是我们不要的结果。

那如果找全局最优解呢?其中困扰我的一点是,我并不知道当前的决定是如何影响后面的结果,那我要怎么样才能找到最优解。同时,我要考虑让第一人每次拿到最优解,又要让第二人也是每次拿到最优解,想着想着就把自己绕晕了。。。。

这说明我动态规划的思想还没完全成熟,很难在这种递归子问题中旅顺一条最优解的思路【蓝瘦,香菇】。
像这种有两个角色的问题,我一开始思考问题是,分别从两个角色的角度思考,那么就会有两个变量,所以想着想着,自己都乱了,更不用说找到实现问题的编码思路了。所以针对这种问题,最好是只从一个角度出发,既然另外一个角色会依赖第一个角色的决定,那么用替换法,通过一个第一角度替换第二角色的角度。

比如说这个选择石子的问题,我们每次只能拿两端的石头堆的石头,但我们又不知道拿完后剩下的石头堆的情况,因此我们考虑先解决子问题。例如我们求出2个相邻石头堆的胜负情况,我们可以根据求出的数据求出相邻3个石头堆的胜负情况,以此类推,我们可以根据n-1个相邻石头堆的胜负情况,求出n个相邻石头堆的胜负情况,即我们的原问题。

每次取石头堆只能从两端取,那么第一个可以选择的石子堆的方式有两种:

  • 第一种:第一个人拿第i堆,那么第二个就可以从第(i+1)堆和第(j)堆中选择最优的石子堆(前面已经分析过了,不一定是更多的那一堆);
  • 第二种:第一个人拿第j堆,那么第二个就可以从第(i)堆和第(j-1)堆中选择最优的石子堆(前面已经分析过了,不一定是更多的那一堆);
    因此,第一人选择的时候,必须选择这两种方法中的最优石子堆。

根据我们的类推,我们可以设:

dp[i][j]表示为,从第i堆到第j堆石子中,第一个人最多可以比第二个人多拿到的石子数,即在一次选择石头堆的过程中,有两个步骤,第一个人在第(i+1)堆和第(j)堆中选择最优的石子堆,那么第二个就会根据第一人的选择,从剩下的石头堆中选择最优的石子堆,然后两者相减,差值取最大,那么这个结果就是当前第一个人做出的最优选择。

状态转换方程是:dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])。

  • piles[i] - dp[i+1][j]表示第一人取走第(i)堆的石头堆, dp[i+1][j]表示第二个就可以从第(i+1)堆和第(j)堆中选择最优的石子堆,然后piles[i] - dp[i+1][j]表示第一个人最多可以比第二个人多拿到的石子数;
  • piles[j] - dp[i][j-1]表示第一人取走第(j)堆的石头堆,dp[i][j-1]表示第二个就可以从第(i)堆和第(j-1)堆中选择最优的石子堆,然后piles[j] - dp[i][j-1]表示第一个人最多可以比第二个人多拿到的石子数;
  • 对于dp[i][i]来说,就是只有一堆石子可供选择,那么第一个直接选择这一堆,自然是piles[i]本身;

3. 解决思路

状态:dp[i][j]表示为,从第i堆到第j堆石子中,第一个人最多可以比第二个人多拿到的石子数。
状态转换方程是:dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])

另外一定小机灵的分析方法,既然根据题目提供的条件,偶数堆+总数为奇数,说明一定存在有个胜利者,那么就一定存在一条选择方式能保证顺利到达胜利,所以只要第一个人选择这条路径,那么也能一定获得胜利。也就是说,第一个开始的人选择最优解一定会赢。

4. 代码实现(java)

  • 方法一:
package com.algorithm.leetcode.dynamicAllocation;

/**
 * Created by 凌 on 2018/12/12.
 * 描述:877. 石子游戏
 */
public class StoneGame {
    public static void main(String[] args) {
//        int[] nums = {5,3,4,5};
//        int[] nums = {4,5,7,9,2,6};
        int[] nums = {3,7,2,3};
        boolean result = stoneGame(nums);
        System.out.println(result);
    }

    public static boolean stoneGame(int[] piles) {
        int len = piles.length;
        int[][] dp = new int[len][len];
        for (int i = 0; i < len; i++) {
            dp[i][i] = piles[i];
        }
        //依次计算相邻2个石头堆到n个石头堆的情形
        //两层循环的意思:每次外层循环控制i到j中间的数量,j是从第2堆到第n堆,第二层循环控制从第一堆(下标为0)到第dis堆
        for (int dis = 1; dis < len; dis++) {
            for (int i = 0; i < len-dis; i++) {
                int j = dis+i;
                dp[i][j] = Math.max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1]);
            }
        }
        return dp[0][len-1] > 0;
    }
}

  • 方法二:
 public static boolean stoneGame(int[] piles) {
    return true;
}

猜你喜欢

转载自blog.csdn.net/qq_35923749/article/details/84995957