【一看就会一写就废 指间算法】可获得的最大点数—— 滑动窗口(逆向思维)VS前缀和(正向思维)

48 阅读3分钟

指尖划过的轨迹,藏着最细腻的答案~

题目:

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。

每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。

你的点数就是你拿到手中的所有卡牌的点数之和。

给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。

示例 1:

输入:cardPoints = [1,2,3,4,5,6,1], k = 3
输出:12
解释:第一次行动,不管拿哪张牌,你的点数总是 1 。但是,先拿最右边的卡牌将会最大化你的可获得点数。最优策略是拿右边的三张牌,最终点数为 1 + 6 + 5 = 12 。

示例 2:

输入:cardPoints = [2,2,2], k = 2
输出:4
解释:无论你拿起哪两张卡牌,可获得的点数总是 4 。

示例 3:

输入:cardPoints = [9,7,7,9,7,7,9], k = 7
输出:55
解释:你必须拿起所有卡牌,可以获得的点数为所有卡牌的点数之和。

示例 4:

输入:cardPoints = [1,1000,1], k = 1
输出:1
解释:你无法拿到中间那张卡牌,所以可以获得的最大点数为 1 。

示例 5:

输入:cardPoints = [1,79,80,1,1,1,200,1], k = 3
输出:202

提示:

1cardPoints.length1051 \leq cardPoints.length \leq 10^5
1cardPoints[i]1041 \leq cardPoints[i] \leq 10^4
1kcardPoints.length1 \leq k \leq cardPoints.length

分析——滑动窗口(逆向思维):

拿走k张卡牌,剩余n-k张,n即是cardPoints的长度。

由于 拿走的 + 剩余的 = n(常数),因此为了使拿走的的最大,则剩余的应该最小。

由于题目要求只能从最前面或最后面拿走卡牌,则剩余的的一定是一个连续的子数组,因此题目转换为:给定一个数组cardPoints,寻找大小为n - k的最小的子数组和。即定长滑动窗口

最后使用cardPoints的和减去最小的子数组和即是最终答案。

AC代码:

class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int minSum = INT_MAX, sum = 0;
        int n = cardPoints.size();
        if (n <= k) 
            return reduce(cardPoints.begin(), cardPoints.end());

        for (int i = 0; i < n; i++) {
            // 1.进入窗口
            sum += cardPoints[i];

            int left = i - (n - k) + 1;
            if (left < 0) {
                continue;
            }
            // 2.更新答案
            minSum = min(minSum, sum);
            // 3.离开窗口
            int out = cardPoints[left];
            sum -= out;
        }

        return reduce(cardPoints.begin(), cardPoints.end()) - minSum;
    }
};

分析——前缀和(正向思维):

答案等于如下结果的最大值:

  • 前 k 个数的和。
  • 前 k−1 个数以及后 1 个数的和。
  • 前 k−2 个数以及后 2 个数的和。
  • ……
  • 前 2 个数以及后 k−2 个数的和。
  • 前 1 个数以及后 k−1 个数的和。
  • 后 k 个数的和。

那我们只需遍历前k个数,从k-10,模拟上面的过程,每次sum - cardPoints[i] + cardPoints[n - k + i],在这过过程中记录最大值即可。

AC代码:

class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        int ans = reduce(cardPoints.begin(), cardPoints.begin() + k);
        int sum = ans;
        for (int i = k - 1; i >= 0; i--) {
            sum -= cardPoints[i];
            sum += cardPoints[n - k + i];
            ans = max(ans, sum);
        }

        return ans;
    }
};