【每日LeetCode】石子游戏

75 阅读3分钟

1690. 石子游戏 VIIO(n2)O(n)\lgroup O(n^2)、O(n)\rgroup

class Solution:
    def stoneGameVII(self, stones: List[int]) -> int:        
        # 该轮次取石头后的分差 = 本轮两者 得分 的差 - 下一轮的分差   两者的共同作用使得 每一轮后的分差必定是最小的。
        # 由于 每轮分差固定,所以从最终结果 往回推 结果一样。  

        n = len(stones)
        dp = [0] * n     # 最右边的石子的  下标为 j 
        for i in range(n - 2, -1, -1): # 剩余石头区间  stones[i]...stones[j] 
            total = stones[i]  # 区间的求和  , 为 求 两方 本轮的分差 做准备
            for j in range(i + 1, n):
                total += stones[j]
                dp[j] = max(total - stones[i] - dp[j], total - stones[j] - dp[j - 1])  # 拿 左边, 拿右边
        return dp[-1]
 # 这种 方法 好理解, 但是 在计算 区间和那里  没有 根据前缀和 求解快
class Solution {
public:
    int stoneGameVII(vector<int>& stones) {
        int n = stones.size();
        vector<int> dp(n, 0);
        for (int i = n - 2; i >= 0; --i){
            int total = stones[i];
            for (int j = i + 1; j < n; ++j){
                total += stones[j];
                dp[j] = max(total - stones[i] - dp[j], total - stones[j] - dp[j - 1]);
            }
        }
        return dp[n - 1];  // 这里 不能用 dp[-1]  不知道为啥
    }
};

image.png

看起来差不多。可能是 数据量少,才1000。 数据量大时,前缀和在 求区间和 时优势很大

方法: 前缀和 + 动态规划

class Solution:
    def stoneGameVII(self, stones: List[int]) -> int:        
        # 该轮次取石头后的分差 = 本轮两者 得分 的差 - 下一轮的分差   两者的共同作用使得 每一轮后的分差必定是最小的。
        # 由于 每轮分差固定,所以从最终结果 往回推 结果一样。  

        n = len(stones)
        dp = [0] * n     # 最右边的石子的  下标为 j 
        # 前缀和 
        s = list(accumulate(stones, initial = 0))
        for i in range(n - 2, -1, -1): # 剩余石头区间  stones[i]...stones[j] 
            for j in range(i + 1, n):
                dp[j] = max(s[j + 1] - s[i + 1] - dp[j], s[j] - s[i] - dp[j - 1])  # 拿 左边, 拿右边
        return dp[-1]

class Solution {
public:
    int stoneGameVII(vector<int>& stones) {
        int n = stones.size();
        vector<int> dp(n, 0);

        // 求前缀和
        vector<int> s(n + 1);
        partial_sum(stones.begin(), stones.end(), s.begin() + 1);

        for (int i = n - 2; i >= 0; --i){
            for (int j = i + 1; j < n; ++j){
                dp[j] = max(s[j + 1] - s[i + 1] - dp[j], s[j] - s[i] - dp[j - 1]);
            }
        }
        return dp[n - 1];  // 这里 不能用 dp[-1]  不知道为啥
    }
};

1686. 石子游戏 VI O(nlogn)O(n)\lgroup O(n \log n)、O(n)\rgroup

image.png

class Solution:
    def stoneGameVI(self, aliceValues: List[int], bobValues: List[int]) -> int:
        # 拿 综合 最高的
        total_value = [a + b for a, b in zip(aliceValues, bobValues)]
        total_value.sort(reverse = True)  # 降序
        # # 所有Alice能拿到的石头的总价值,其中每个都多拿了Bob的对应石子,再减去本来就是Bob拿的石子,正好是Bob的所有石子
        diff = sum(total_value[::2]) - sum(bobValues)  # alice 比  bob 多的  
        if diff > 0:
            return 1  # Alice 赢
        elif diff == 0:
            return 0  # 平局
        else:
            return -1  # Bob 赢

        # 假设 排序后 a0 + b0  a1 + b1  a2 + b2  a3 + b3
        # Alice  a0  a2
        # Bob   b1  b3
        # diff = ( a0 + a2 ) - (b1 + b3) 
        #      = ( a0 + b0 + a2 + b2) - (b0 + b2 + b1 + b3) # Alice 先手拿了,对方无法再拿对应位置的。只能拿剩下位置的

image.png

class Solution {
public:
    int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
        int n = aliceValues.size();
        vector<pair<int, int>> totalvalue;
        for (int i = 0; i < n; ++i){
            totalvalue.emplace_back(aliceValues[i] + bobValues[i], i);
        }
        sort(totalvalue.rbegin(), totalvalue.rend()); // 从 大 到 小 排序
        // 分别计算 价值总和
        int sum_Alice = 0, sum_Bob = 0;
        for (int i = 0; i < n; ++i){
            if (i % 2 == 0){// Alice 先手
                sum_Alice += aliceValues[totalvalue[i].second];
            }else{
                sum_Bob += bobValues[totalvalue[i].second];
            }
        }

        // 比较 总和
        if (sum_Alice > sum_Bob){
            return 1; // Alice 赢
        }else if (sum_Alice == sum_Bob){
            return 0;
        }else{
            return -1;   // Bob 赢
        }
    }
};