指尖划过的轨迹,藏着最细腻的答案~
题目:
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 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张卡牌,剩余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-1到0,模拟上面的过程,每次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;
}
};