[C++&Rust]LeetCode No.877 石子游戏(每日一题)

原贴地址:http://blog.leanote.com/post/dawnmagnet/lc877

题目

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

思路分析

这道题有许多思路。
我们选择两个最能考验思想的给大家讲一讲。

解法1-数学分析

首先是一个取巧的想法。在这种你取一颗,我取一颗的过程中,极有可能出现一种情况。假设有偶数个数字:
[0,1,2,3,4,5,6,7]
可以看出,头是偶数,尾是奇数,而且穿插排列,那么对于在这种状态下先手的人来说,他有一种策略,他可以拿到所有奇数或者偶数位的数字。具体的操作情况见下:
假设A先手。A想拿到所有偶数,A可以先拿0,这样在B选择的时候只剩1和7,都是奇数,而在B拿完一边之后,会露出来一个偶数2或者6,在这种情况下,A将该偶数拿走,剩下给B的又全部是奇数。
按照这个顺序,A 如果想,一定能拿到所有的偶数,那么B就被迫拿了所有的奇数,同理,A也可以选择拿到所有的奇数,那么B就被迫拿了所有的偶数。
在这种条件下,如果所有偶数位的和与奇数位的和不同,那么A就有必胜策略 ,而题目中的条件告诉我们,偶数位的和与奇数位的和相加是一个奇数,所以必定有一个大,有一个小,那么A一定有必胜策略
所以直接return true;就可以了
代码就懒得贴了。

解法2-动态规划

老方法,来个dp的模板

|递推公式|dp[i][j]=max(num[i]-dp[i+1][j],num[j]-dp[i][j-1])
–|
|边界条件|dp[i][i]=0,其余等于INT_MIN
|dp[i][j]的意义|边界范围包含[i,j]的子数组先手可以获得的最大分差

注意这个递推的时候不能按照常规的for (i…) for(j…)
会出错,因为我们的i,j不是按顺序递增的。
从递推公式可以看出,是一个典型的模拟过程,也就是说,如果有dp[i][j],且A是先手,A有两个选择,一个是选头,一个是选尾。
选头的话,剩下的数组就是[i+1][j],但此时对于B来说是先手,dp[i+1][j]的意义是B先手的情况下B可以获取的最大分差,所以我们应拿num[i]减去dp[i+1][j],才能获取A获取的最大分差。
选尾部的情况同理。

C++代码

class Solution {
public:
    bool stoneGame(vector<int>& piles) {
        int n = piles.size();
        vector<vector<int>> dp(n, vector<int>(n, INT_MIN));
        for (int i = 0; i < n; ++i)
            dp[i][i] = piles[i];
        for (int len = 1; len < n; ++len)
            for (int i = 0; i + len < n; ++i) {
                int j = i + len;
                dp[i][j]=max(piles[i]-dp[i+1][j],piles[j]-dp[i][j-1]);
            }
        return dp[0][n - 1] > 0;
    }
};

Rust代码

impl Solution {
    pub fn stone_game(piles: Vec<i32>) -> bool {
        let n = piles.len();
        let mut dp = vec![vec![i32::MIN; n]; n];
        for i in 0..n {
            dp[i][i] = piles[i];
        }
        for len in 1..n {
            for i in 0..(n - len) {
                let j = i + len;
                dp[i][j] = (piles[i] - dp[i + 1][j]).max(piles[j] - dp[i][j - 1]);
            }
        }
        dp[0][n - 1] > 0
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值