一道有意思的滑动窗口题目
今天看到了一道很有意思的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 }

浙公网安备 33010602011771号