题目:
几张卡牌 排成一行,每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。
每次行动,你可以从行的开头或者末尾拿一张卡牌,最终你必须正好拿 k 张卡牌。
你的点数就是你拿到手中的所有卡牌的点数之和。
给你一个整数数组 cardPoints 和整数 k,请你返回可以获得的最大点数。
算法:
**算法一:双指针 **
本质上是窗口长度为k,求窗口内的最大值。那我们初始时,设置长度为k的窗口[0,0]+[n-k,n-1],其中初始值left=0,right=n-k,left右移,right右移,求窗口内最大值即可
func maxScore(cardPoints []int, k int) int {
left, right := 0, len(cardPoints) - 1
ans := 0
score := 0
// 从右侧拿k张牌
for i := 0; i < k; i ++ {
score = score + cardPoints[right]
right --
}
if k >= len(cardPoints) {
return score
}
ans = score
// 跳出上面的for循环时,[right, n - 1]有k + 1个数,所有right右移一位
for right = right + 1; right < len(cardPoints); right ++ {
score = score - cardPoints[right]
score = score + cardPoints[left]
left ++
if score > ans {
ans = score
}
}
return ans
}
**方法二:滑动窗口 **
从头尾取出长度为k个连续的数,则中间还剩下n- k个,问题转换为cardPoints的和sum 与 cardPoints长度为n - k的滑动窗口的差的最大值
func maxScore(cardPoints []int, k int) int {
n := len(cardPoints)
sum, windowSum := 0, 0
for i := range cardPoints {
sum = sum + cardPoints[i]
}
ans := 0
// 下面的for循环处理不了k == n的情况,k == n是continue不会生效,生效了ans也是0
if k == n {
return sum
}
// 求滑动窗口之和
for left, right := 0, 0; right < len(cardPoints); right ++ {
windowSum = windowSum + cardPoints[right]
if right - left + 1 < n - k {
continue
}
if sum - windowSum > ans {
ans = sum - windowSum
}
windowSum = windowSum - cardPoints[left]
left ++
}
return ans
}
**方法三:滑动窗口优化 **
对于方法二的求解过程,注意到我们要求滑动窗口长度为n - k的区间和的最小值,前缀和可以在O(1)时间得到结果,
func maxScore(cardPoints []int, k int) int {
n := len(cardPoints)
// 前缀和小技巧1,数组长度n + 1
prefixSum := make([]int, n + 1)
for i := range cardPoints {
prefixSum[i + 1] = prefixSum[i] + cardPoints[i]
}
sum := prefixSum[n]
minWindowSum := math.MaxInt64
windowSize := n - k
for i := 0; i + windowSize <= n; i ++ {
// 前缀和小技巧2,prefixSum[i + windowSize] - prefixSum[i]求区间和[i + 1, i + windowSize]
minWindowSum = min(minWindowSum, prefixSum[i + windowSize] - prefixSum[i])
}
return sum - minWindowSum
}
func min(a, b int) int {
if a < b {
return a
}
return b
}