一道有意思的滑动窗口题目

今天看到了一道很有意思的LeetCode题目,记录一下。

 

题目:

可获得的最大点数

几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 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

 

思路很直接,求最大的k张牌,要么左边取k个,要么右边取k个,要么左右各取一部分。滑动窗口记录即可。

 

第一种解法:先坐标滑动 k 个,再从前k个逆向滑动到后 k 个。

例如:数组 1 2 3 4 5 6, k=3,窗口内数字为:

  [1] 

  [1, 2]

  [1, 2, 3]

  [6, 1, 2]

  [5, 6, 1]

  [4, 5, 6]

代码如下:

 1 class Solution {
 2     public int maxScore(int[] cardPoints, int k) {
 3         
 4         int sum = 0, len = cardPoints.length;
 5         for(int i = 0; i < k; i++)
 6             sum += cardPoints[i];
 7         int max = sum;
 8         for(int i = 0; i < k; i++)
 9         {
10             sum += cardPoints[len - i - 1];
11             sum -= cardPoints[k - i - 1];
12             max = Math.max(max, sum);
13         }
14         return max;
15     }
16 }

 

 

第二种思路:

把牌堆看成一个环状数组处理,其实就是后面k个数和前面k个数拼起来,即求循环起点为size - k,循环结束条件为size + k的滑动窗口中最大的和就行了,环状数组可以用取模实现。

这种思路其实和第一种一样,不过一直是正向滑动,没有改变滑动反向

 1 class Solution {
 2 public:
 3     int maxScore(vector<int>& cardPoints, int k) {
 4         int ans = 0;
 5         int n = cardPoints.size();
 6         int left = n - k, right = n - k;
 7         int sum = 0;
 8         for (int i = n - k; i < n + k; ++i) {
 9             sum += cardPoints[right % n];
10             if (right - left + 1 > k) {
11                 sum -= cardPoints[left % n];
12                 ++left;
13             }
14             ++right;
15             ans = max(ans, sum);
16         }
17         return ans;
18     }
19 };

 

 

第三种思路:

这种思路是官方提供的,非常有启发性。

记数组 cardPoints 的长度为n,由于只能从开头和末尾拿 k 张卡牌,所以最后剩下的必然是连续的 n−k 张卡牌。

我们可以通过求出剩余卡牌点数之和的最小值,来求出拿走卡牌点数之和的最大值。

class Solution {
    public int maxScore(int[] cardPoints, int k) {
        int n = cardPoints.length;
        // 滑动窗口大小为 n-k
        int windowSize = n - k;
        // 选前 n-k 个作为初始值
        int sum = 0;
        for (int i = 0; i < windowSize; ++i) {
            sum += cardPoints[i];
        }
        int minSum = sum;
        for (int i = windowSize; i < n; ++i) {
            // 滑动窗口每向右移动一格,增加从右侧进入窗口的元素值,并减少从左侧离开窗口的元素值
            sum += cardPoints[i] - cardPoints[i - windowSize];
            minSum = Math.min(minSum, sum);
        }
        return Arrays.stream(cardPoints).sum() - minSum;
    }
}

 

 

第四种思路

其实是和前面的滑动窗口差不多,但是使用前缀和和后缀和数组进行了预处理,那么能够获得的卡牌点数就是从前面拿x张牌从后面拿y张牌,x+y=k

 1 class Solution {
 2     public int maxScore(int[] cardPoints, int k) {
 3         int n = cardPoints.length;
 4         int[] pre = new int[n + 1];
 5         int[] ord = new int[n + 1];
 6         // 前缀
 7         for(int i = 1; i <= n; i++){
 8             pre[i] = pre[i - 1] + cardPoints[i - 1];
 9         }
10         // 后缀
11         for(int i = n - 1; i >= 0; i--){
12             ord[i] = ord[i + 1] + cardPoints[i];
13         }
14         // 如果需要的牌的数量正好等于拥有的牌的数量 全拿走就行了
15         if(n == k) return pre[k];
16         int max = Integer.MIN_VALUE;
17         int cnt = 0;
18         while(k >= 0){
19             // 前面x张+后面y张能够得到的最大的牌数情况
20             max = Math.max(max, pre[k] + ord[n - cnt]);
21             k--;
22             cnt++;
23         }
24         return max;
25     }
26 }

 

posted @ 2022-02-15 14:37  r1-12king  阅读(41)  评论(0)    收藏  举报