【LeetCode 1406】Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务

212 阅读2分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务活动详情

一、题目描述

博弈论,给定一个数组,Alice和Bob两个人可以依次轮流取,每次取前1、2或者3个,获得数组的值,累加,直到数组取完。谁的数字大谁赢,Alice先手,求谁必赢。

二、思路分析

一道有趣的博弈论,博弈论我们往往需要去找到最微小的、起源的那种状态(类似于道家哲学中的一生二、二生三、三生万物),而在本题中,起初的状态是n堆数组吗?其实并不是,真正方便我们入手观察的,是反向的过程,一开始只有一个数字。所以局面是倒着推过来的。

  • 当 n == 1 时,先手一定能拿到当前的这个数字,但具体是谁赢,仍需要去分析。如果是正数,则先手必赢,如果是负数,则后手必赢,如果是0,则平局。也由此我们意识到,在推导的过程中我们并不需要去管结果是什么,我们只需要知道,从当前的局面出发,先手能拿到最多的数之和是多少。

  • 对于其他的更多的情况,我们设当前所在的节点为 i,tot[i]为sum(i,n),ans[i]为先手从 i 出发能取得的最多的数。则ans[i] = max(ans[i], tot[i] - ans[i + j]),其中 j 为[1,2,3]。这个式子很好理解,即先手可以取前一、二、三堆,它能取得的最大的总和,即是后手所能取得的最小的值,用 tot[i] - ans[i + j] 即可得到先手能取得的石子数量。

最后做一次判断即可。

三、AC代码

class Solution {
public:
    string stoneGameIII(vector<int>& stoneValue) {
        int n = stoneValue.size();
        int ans[100005],tot[100005];
        for (int i=0; i<=100000; i++) {
            tot[i]=0;
            ans[i]=-999999999;
        }
        for (int i=n-1; i>=0; i--) {
            tot[i] = tot[i+1] + stoneValue[i];
        }
        ans[n]=0;
        stoneValue.push_back(0);
        stoneValue.push_back(0);
        stoneValue.push_back(0);
        for (int i=n-1; i>=0; i--) {
            for (int j=1; j<=3 && i+j<=n; j++) {
                ans[i] = max(ans[i], tot[i]-ans[i+j]);
            }
        }
        if (ans[0] > tot[0]-ans[0]) {
            return (string)"Alice";
        } else 
        if (ans[0] < tot[0]-ans[0]) {
            return (string)"Bob";
        } else {
            return (string)"Tie";
        }
    }
};

四、总结

博弈论的题目,需要去观察它的源头是什么,以及它的转移规律是什么,能牢牢抓住这两点,我们就有成功的希望。