目录
1423. 可获得的最大点数
方法一 递归
方法二 滑动窗口
方法三 环形数组
方法四 preSum
2021年2月6日题目链接:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/
难度中等69
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 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
提示:
1 <= cardPoints.length <= 10^5
1 <= cardPoints[i] <= 10^4
1 <= k <= cardPoints.length
通过次数11,010提交次数22,286
首先想到的递归但是 1e5 超时,然后递归写的还不好,多了一些不必要的条件,这个之后要训练训练,
但是递归的过程是把所有的可能组合方式都求了一遍,时间复杂度会达到
O(N*k)
n=数组的大小
反向思考,这题用滑动窗口,两边不好滑动,就滑动中间求最小,窗口的size是 n-k,最后sum-min就得到最后要求的结果
注意 滑动窗口的时候窗口大小固定的情况,可以先求0~windowSize这个窗口和,然后之后再滑动
官方题解用了accumulate,算是小技巧吧,直接循环即可求和,
也可以left,right这样滑动,但是这样从0 开始滑动加扩大对于窗口大小固定的这样写反而需要考虑的较多,比如如果没写好循环条件就需要考虑windowSize=0
以下给出两种代码
固定滑动窗口还用left right 滑动有点没必要的代码一:
int maxScore(vector& cardPoints, int k) {
int n=cardPoints.size();
int m=n-k;
int sum=0;
for(int i=0;i0){ //必须要考虑m是否大于0
minSum=sum;
while(right=m){
minSum=min(minSum,tmp);
tmp-=cardPoints[left];
left++;
}
right++;
}
} else{
minSum=0;
}
return sum-minSum;
}
参考别人的改进代码
这个方法只用遍历一次数组。
需要注意的是,需要根据 i 的位置,计算滑动窗口是否开始、是否要移除最左边元素:
当 i >= windowSize 时,为了固定窗口的元素是 k 个,每次移动时需要将 i - windowSize 位置的元素移除。
当 i >= windowSize - 1 时,滑动窗口内的元素刚好是 k 个,开始计算滑动窗口的最小和。
最后,用 cardPoints 的所有元素之和,减去滑动窗口内的最小元素和,就是拿走的卡牌的最大点数。使用 Python2 写的代码如下。
class Solution(object):
def maxScore(self, cardPoints, k):
"""
:type cardPoints: List[int]
:type k: int
:rtype: int
"""
N = len(cardPoints)
windowSize = N - k # 窗口的大小
sums = 0
res = float("inf") # 正无穷大
for i in range(N):
sums += cardPoints[i]
if i >= windowSize:
sums -= cardPoints[i - windowSize]
if i >= windowSize - 1:
res = min(res, sums)
return sum(cardPoints) - res作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/san-chong-fang-fa-tuo-zhan-si-lu-dong-tu-fb9m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
固定滑动窗口一般写法代码二:
int maxScore(vector& cardPoints, int k) {
int n=cardPoints.size();
int windowSize=n-k;
int sum=0;
int tmp=0;
for(int i=0;itmp){
minSum=tmp;
}
}
return sum-minSum;
}
可以构造一个环形数组 = {后k个元素,前k个元素}),因为卡牌必须是连续的k张卡牌,也就是一个滑动窗口,窗口在环形数组中一次一次右移,找到最大的一个窗口就是结果。
作者:closes
链接:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/huan-xing-shu-zu-by-closes-0zk9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
//参考别人题解思路自己写的
//主要难点就是循环的那个开始以及边界,以及环形数组的处理
int maxScore(vector& cardPoints, int k) {
int n=cardPoints.size();
int res=INT_MAX;
int tmp=0;
for(int i=n-k;i
定义preSum[i] 是不包含i的数组 [0,i)的和
然后利用这个求固定窗口的最小值
int maxScore(vector& cardPoints, int k) {
int n=cardPoints.size();
int windowSize=n-k;
vector preSum(n+1,0);
for(int i=1;i<=n;i++){
preSum[i]=preSum[i-1]+cardPoints[i-1];
}
int sum=preSum[n];
//[i,j]内的和 = preSum[j+1]-preSum[i]
int min=INT_MAX;
int tmp=0;
for(int i=0;i<=n-windowSize;i++){
int j=i+windowSize-1;
tmp=preSum[j+1]-preSum[i];
if(tmp
参考链接:三种方法拓展思路,动图帮助理解
今天这个题目,难点还是需要把抽取卡牌的问题转变为一个滑动窗口的问题。
问题抽象之后,preSum 和 滑动窗口 两种解法就已经呼之欲出了。
每日一题的作用再次凸显,同一类题目都是换汤不换药,学会思路和套路之后,就很快秒杀!
作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/san-chong-fang-fa-tuo-zhan-si-lu-dong-tu-fb9m/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
个人体会:
1、学会转换思路 反向求解 max->min
2、如果用滑动窗口,循环数组等这些需要找好循环条件,还是要多练习