• LeetCode——石子游戏


    Q:亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 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) 是奇数。

    A:
    以下是对 dp 数组含义的解释:
    dp[i][j].fir 表⽰,对于 piles[i...j] 这部分⽯头堆,先⼿能获得的最⾼分数。
    dp[i][j].sec 表⽰,对于 piles[i...j] 这部分⽯头堆,后⼿能获得的最⾼分数。
    举例理解⼀下,假设 piles = [3, 9, 1, 2],索引从 0 开始
    dp[0][1].fir = 9 意味着:⾯对⽯头堆 [3, 9],先⼿最终能够获得 9 分。
    dp[1][3].sec = 2 意味着:⾯对⽯头堆 [9, 1, 2],后⼿最终能够获得 2 分。

    我们想求的答案是先⼿和后⼿最终分数之差,按照这个定义也就是 (dp[0][n-1].fir - dp[0][n-1].sec),即⾯对整个 piles,先⼿的最优得分和后⼿的最优得分之差。
    写出状态转移⽅程:

    dp[i][j].fir = max(piles[i] + dp[i+1][j].sec, piles[j] + dp[i][j-1].sec)
    dp[i][j].fir = max( 选择最左边的⽯头堆 , 选择最右边的⽯头堆 )
    # 解释:我作为先⼿,⾯对 piles[i...j] 时,有两种选择:
    # 要么我选择最左边的那⼀堆⽯头,然后⾯对 piles[i+1...j]
    # 但是此时轮到对⽅,相当于我变成了后⼿;
    # 要么我选择最右边的那⼀堆⽯头,然后⾯对 piles[i...j-1]
    # 但是此时轮到对⽅,相当于我变成了后⼿。
    
    if 先⼿选择左边:
    dp[i][j].sec = dp[i+1][j].fir
    if 先⼿选择右边:
    dp[i][j].sec = dp[i][j-1].fir
    # 解释:我作为后⼿,要等先⼿先选择,有两种情况:
    # 如果先⼿选择了最左边那堆,给我剩下了 piles[i+1...j]
    # 此时轮到我,我变成了先⼿;
    # 如果先⼿选择了最右边那堆,给我剩下了 piles[i...j-1]
    # 此时轮到我,我变成了先⼿。
    
    dp[i][j].fir = piles[i]
    dp[i][j].sec = 0
    其中 0 <= i == j < n
    # 解释:i 和 j 相等就是说⾯前只有⼀堆⽯头 piles[i]
    # 那么显然先⼿的得分为 piles[i]
    # 后⼿没有⽯头拿了,得分为 0
    

    如图,需要斜着遍历:

    代码:

        class Pair {
            int first;
            int second;
    
            Pair(int first, int second) {
                this.first = first;
                this.second = second;
            }
        }
    
        public boolean stoneGame(int[] piles) {
            if (piles.length == 0)
                return false;
            int n = piles.length;
            Pair[][] dp = new Pair[piles.length][piles.length];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    dp[i][j] = new Pair(0, 0);
                }
            }
            for (int i = 0; i < n; i++) {
                //初始化
                dp[i][i].first = piles[i];
                dp[i][i].second = 0;
            }
            for (int i = n - 2; i >= 0; i--) {
                for (int j = i + 1; j < n; j++) {
                    //当前堆是先拿左边,即为左边堆的后手
                    int left = piles[i] + dp[i + 1][j].second;
                    //当前堆是先拿右边,即为右边堆的后手
                    int right = piles[j] + dp[i][j - 1].second;
                    if (left >= right) {
                        dp[i][j].first = left;
                        dp[i][j].second = dp[i + 1][j].first;
                    } else {
                        dp[i][j].first = right;
                        dp[i][j].second = dp[i][j - 1].first;
                    }
                }
            }
            return dp[0][n - 1].first > dp[0][n - 1].second;
        }
    
  • 相关阅读:
    SQL CREATE TABLE 语句
    SQL CREATE DATABASE 语句
    SQL INSERT INTO SELECT 语句
    SQL SELECT INTO 语句
    Why is chkconfig no longer available in Ubuntu?
    drag-html
    js利用offsetWidth和clientWidth来计算滚动条的宽度
    procomm plus
    很多shell命令后面的单横杠和双横杠,原来这个意思
    angular md-toast 颜色
  • 原文地址:https://www.cnblogs.com/xym4869/p/12586347.html
Copyright © 2020-2023  润新知