Leetcode 67 长度为 K 子数组中的最大和 | 可获得的最大点数

1 题目

2461. 长度为 K 子数组中的最大和

给你一个整数数组 nums 和一个整数 k 。请你从 nums 中满足下述条件的全部子数组中找出最大子数组和:

  • 子数组的长度是 k,且
  • 子数组中的所有元素 各不相同 。

返回满足题面要求的最大子数组和。如果不存在子数组满足这些条件,返回 0

子数组 是数组中一段连续非空的元素序列。

示例 1:

复制代码
输入:nums = [1,5,4,2,9,9,9], k = 3
输出:15
解释:nums 中长度为 3 的子数组是:
- [1,5,4] 满足全部条件,和为 10 。
- [5,4,2] 满足全部条件,和为 11 。
- [4,2,9] 满足全部条件,和为 15 。
- [2,9,9] 不满足全部条件,因为元素 9 出现重复。
- [9,9,9] 不满足全部条件,因为元素 9 出现重复。
因为 15 是满足全部条件的所有子数组中的最大子数组和,所以返回 15 。

示例 2:

复制代码
输入:nums = [4,4,4], k = 3
输出:0
解释:nums 中长度为 3 的子数组是:
- [4,4,4] 不满足全部条件,因为元素 4 出现重复。
因为不存在满足全部条件的子数组,所以返回 0 。

2 代码实现

cpp 复制代码
class Solution {
public:
    long long maximumSubarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        if (n < k ) return 0 ;

        unordered_map<int , int> count ;
        long long currentSum = 0 ;
        long long maxSum = 0 ;
        int duplicateCount = 0 ;

        for (int i = 0 ; i < k ; i++){
            int num = nums[i];
            currentSum += num ;
            count[num] ++;
            if (count[num]== 2){
                duplicateCount++;
            }
        }

        if (duplicateCount == 0 ){
            maxSum = currentSum;
        }

        for (int i = k ; i < n ; i++){
            int leftNum = nums[ i - k ];
            currentSum -= leftNum;
            count[leftNum] --;
            if (count[leftNum] == 1 ){
                duplicateCount--;
            }

            int rightNum = nums[i];
            currentSum += rightNum;
            count[rightNum]++;
            if(count[rightNum] == 2 ){
                duplicateCount++;
            }

            if (duplicateCount == 0){
                maxSum =max(maxSum ,currentSum);
            }
        }
        return maxSum;
    }
};

核心原因大概率是:没吃透 "滑动窗口" 的核心逻辑 + 没理清 "题目约束(元素互不相同)" 的判断技巧,只是机械模仿代码。

这道题正好是巩固这两个点的绝佳案例,我会从 "为什么选滑动窗口""每一步为什么这么写""容易踩的坑" 三个维度,把代码拆解得明明白白,帮你真正学会 "自己想思路、写代码"。

cpp 复制代码
class Solution {
public:
    long long maximumSubarraySum(vector<int>& nums, int k) {
        int n = nums.size();
        if (n < k) return 0; // 数组长度小于k,直接返回0
        
        unordered_map<int, int> count; // 统计窗口内元素出现次数
        long long currentSum = 0;
        long long maxSum = 0;
        int duplicateCount = 0; // 记录窗口内重复元素的个数(优化判断)
        
        // 初始化第一个窗口(前k个元素)
        for (int i = 0; i < k; ++i) {
            int num = nums[i];
            currentSum += num;
            count[num]++;
            // 若元素出现次数>=2,说明是重复元素
            if (count[num] == 2) {
                duplicateCount++;
            }
        }
        
        // 初始窗口无重复元素,更新最大和
        if (duplicateCount == 0) {
            maxSum = currentSum;
        }
        
        // 滑动窗口遍历剩余元素
        for (int i = k; i < n; ++i) {
            // 移除窗口左侧元素(i-k位置)
            int leftNum = nums[i - k];
            currentSum -= leftNum;
            count[leftNum]--;
            // 若左侧元素之前是重复的(次数从2减到1),则重复计数减1
            if (count[leftNum] == 1) {
                duplicateCount--;
            }
            
            // 加入窗口右侧新元素(i位置)
            int rightNum = nums[i];
            currentSum += rightNum;
            count[rightNum]++;
            // 若新元素加入后变成重复(次数从1增到2),则重复计数加1
            if (count[rightNum] == 2) {
                duplicateCount++;
            }
            
            // 判断当前窗口是否无重复元素,更新最大和
            if (duplicateCount == 0) {
                maxSum = max(maxSum, currentSum);
            }
        }
        
        return maxSum;
    }
};

一、先想清楚:这道题为什么必须用滑动窗口?

1. 题目核心要求

  • 子数组必须满足两个条件:① 长度固定为 k;② 所有元素互不相同。
  • 目标:找到满足条件的子数组中,和最大的那个(没找到就返回 0)。

2. 暴力解法为什么不行?

如果用暴力法,要遍历所有长度为 k 的子数组(共 n-k+1 个),对每个子数组:

  • 检查是否有重复元素(需要用哈希表或排序,时间 O (k));
  • 计算和(时间 O (k))。总时间复杂度是 O ((n-k+1)k) ≈ O (nk),如果 n=1e5、k=5e4,这会超时(1e55e4=5e9 次操作,远超计算机每秒 1e8 次的极限)。

3. 滑动窗口的优势:O (n) 时间

滑动窗口的核心是 "窗口移动时,只更新变化的部分",避免重复计算:

  • 窗口从 [i-k, i-1] 移到 [i-k+1, i] 时,只需要:
    1. 移除左边出去的元素(i-k 位置);
    2. 加入右边进来的元素(i 位置);
    3. 基于之前的状态,更新 "和" 与 "是否有重复元素" 的判断。
  • 每一步都是 O (1) 操作,总时间复杂度 O (n),效率直接拉满。

二、拆解代码:每一行都讲透 "为什么这么写"

第一步:初始化变量(搞懂每个变量的作用)

cpp 复制代码
int n = nums.size();
if (n < k ) return 0 ; // 边界条件:数组长度比k小,不可能有满足条件的子数组

unordered_map<int , int> count ; // 统计窗口内每个元素的出现次数(判断重复的关键)
long long currentSum = 0 ; // 当前窗口的和(避免int溢出,用long long)
long long maxSum = 0 ; // 满足条件的最大子数组和(初始为0,没找到就返回0)
int duplicateCount = 0 ; // 窗口内"重复元素的个数"(优化判断:不用遍历哈希表,直接看这个数)
关键变量解释:duplicateCount
  • 为什么不用每次遍历哈希表判断 "是否有重复"?因为遍历哈希表是 O (k) 操作,会变回暴力法。
  • duplicateCount 的逻辑:只统计 "出现次数≥2" 的元素个数(比如窗口里有 [2,9,9],9 出现 2 次,duplicateCount=1;如果是 [9,9,9],9 出现 3 次,duplicateCount 还是 1,因为只算 "有重复" 这个状态,不算重复次数)。
  • 这样判断 "窗口是否无重复" 就变成了:duplicateCount == 0(O (1) 操作)。

第二步:构建初始窗口(前 k 个元素)

窗口刚开始是 [0, k-1](前 k 个元素),我们需要先初始化:① 窗口和;② 元素出现次数;③ 重复元素个数。

cpp 复制代码
for (int i = 0 ; i < k ; i++){
    int num = nums[i];
    currentSum += num ; // 累加初始窗口的和
    count[num] ++; // 记录元素出现次数
    // 关键判断:只有当元素从"出现1次"变成"出现2次"时,才是新的重复
    if (count[num]== 2){
        duplicateCount++;
    }
}
为什么是 count[num] == 2 才加 duplicateCount?
  • 比如元素 num 第一次出现:count [num] = 1 → 无重复,duplicateCount 不变;
  • 第二次出现:count [num] = 2 → 变成重复元素,duplicateCount +1;
  • 第三次出现:count [num] = 3 → 已经是重复元素了,duplicateCount 不再加(因为重复状态没变化)。
  • 这样能精准统计 "有多少个元素是重复的",避免多算。

第三步:判断初始窗口是否满足条件

cpp 复制代码
if (duplicateCount == 0 ){
    maxSum = currentSum; // 初始窗口无重复,更新最大和
}
  • 如果初始窗口就有重复(比如示例 2 的 [4,4,4]),duplicateCount=1,maxSum 保持初始值 0。

第四步:滑动窗口遍历剩余元素(核心步骤)

从 i=k 开始,窗口每次向右移 1 位,每一步都要做 "移除左边元素→加入右边元素→判断是否满足条件" 三件事。

cpp 复制代码
for (int i = k ; i < n ; i++){
    // 1. 移除窗口左侧的元素(i-k 位置,因为窗口是 [i-k+1, i])
    int leftNum = nums[ i - k ];
    currentSum -= leftNum; // 窗口和减去左边元素
    count[leftNum] --; // 左边元素的出现次数减1
    // 关键判断:左边元素之前是重复的(count从2→1),现在不重复了,duplicateCount减1
    if (count[leftNum] == 1 ){
        duplicateCount--;
    }

    // 2. 加入窗口右侧的新元素(i 位置)
    int rightNum = nums[i];
    currentSum += rightNum; // 窗口和加上右边元素
    count[rightNum]++; // 右边元素的出现次数加1
    // 关键判断:右边元素从"不重复(1次)"变成"重复(2次)",duplicateCount加1
    if(count[rightNum] == 2 ){
        duplicateCount++;
    }

    // 3. 判断当前窗口是否满足条件,更新最大和
    if (duplicateCount == 0){
        maxSum =max(maxSum ,currentSum);
    }
}
拆解 "移除左边元素" 的逻辑:

比如窗口是 [2,9,9](i=4,k=3),下一次要移除左边的 2(i-k=1 位置):

  • count [2] 从 1→0 → 不会触发 count[leftNum] ==1,duplicateCount 不变(还是 1,因为 9 还是重复);如果窗口是 [2,9,9,9](k=3),移除左边的 9(count 从 2→1):
  • 此时 9 从重复变成不重复,duplicateCount 从 1→0。
拆解 "加入右边元素" 的逻辑:

比如窗口是 [4,2,9](i=3,k=3),下一次加入右边的 9(i=4):

  • count [9] 从 1→2 → 触发 count[rightNum] ==2,duplicateCount 从 0→1;如果窗口是 [5,4,2](i=2,k=3),加入右边的 9(i=3):
  • count [9] 从 0→1 → 不触发,duplicateCount 保持 0。

第五步:返回结果

cpp 复制代码
return maxSum;
  • 如果所有窗口都有重复,maxSum 一直是 0,直接返回 0(符合示例 2 的要求)。

三、用示例 1 手把手模拟,看懂每一步变化

示例 1:nums = [1,5,4,2,9,9,9], k=3

初始状态(i 从 0 到 2,窗口 [0,2] = [1,5,4])

  • currentSum = 1+5+4 = 10;
  • count:{1:1, 5:1, 4:1};
  • duplicateCount = 0(无重复);
  • maxSum = 10。

第一次滑动(i=3,窗口 [1,3] = [5,4,2])

  1. 移除左边元素 1(i-k=0):
    • currentSum = 10-1=9;
    • count [1] 从 1→0 → 不触发 duplicateCount 变化;
  2. 加入右边元素 2(i=3):
    • currentSum =9+2=11;
    • count [2] 从 0→1 → 不触发 duplicateCount 变化;
  3. duplicateCount=0 → maxSum = max(10,11)=11。

第二次滑动(i=4,窗口 [2,4] = [4,2,9])

  1. 移除左边元素 5(i-k=1):
    • currentSum=11-5=6;
    • count [5] 从 1→0 → 不触发变化;
  2. 加入右边元素 9(i=4):
    • currentSum=6+9=15;
    • count [9] 从 0→1 → 不触发变化;
  3. duplicateCount=0 → maxSum = max(11,15)=15。

第三次滑动(i=5,窗口 [3,5] = [2,9,9])

  1. 移除左边元素 4(i-k=2):
    • currentSum=15-4=11;
    • count [4] 从 1→0 → 不触发变化;
  2. 加入右边元素 9(i=5):
    • currentSum=11+9=20;
    • count [9] 从 1→2 → duplicateCount 从 0→1;
  3. duplicateCount≠0 → 不更新 maxSum。

第四次滑动(i=6,窗口 [4,6] = [9,9,9])

  1. 移除左边元素 2(i-k=3):
    • currentSum=20-2=18;
    • count [2] 从 1→0 → 不触发变化;
  2. 加入右边元素 9(i=6):
    • currentSum=18+9=27;
    • count [9] 从 2→3 → 不触发 duplicateCount 变化(还是 1);
  3. duplicateCount≠0 → 不更新 maxSum。

最终 maxSum=15,和示例 1 的输出一致。

四、为什么你之前没学会?常见踩坑点 + 解决方法

踩坑点 1:没理清 "重复判断" 的逻辑,用遍历哈希表代替 duplicateCount

  • 错误做法:每次滑动后,遍历 count 哈希表,检查是否所有元素的次数都是 1(O (k) 时间);
  • 后果:超时,且逻辑复杂;
  • 解决方法:记住 "用一个变量统计重复状态"(比如 duplicateCount),只在 "元素从 1→2" 或 "2→1" 时更新,O (1) 判断。

踩坑点 2:数据溢出,用 int 存 sum

  • 错误做法:用 int currentSum;
  • 后果:如果 nums 元素是 1e9,k=1e5,sum=1e14,远超 int 的范围(-2^31~2^31-1,约 2e9);
  • 解决方法:必须用 long long 存 currentSum 和 maxSum。

踩坑点 3:滑动窗口时,移除 / 加入元素的顺序搞反

  • 错误做法:先加入右边元素,再移除左边元素;
  • 后果:窗口长度变成 k+1(中间有一瞬间窗口包含了 k+1 个元素),导致 count 统计错误;
  • 解决方法:固定顺序:先移除左边元素,再加入右边元素(保证窗口始终是 k 个元素)。

踩坑点 4:边界条件没处理(n < k)

  • 错误做法:忽略 if (n <k) return 0
  • 后果:当 n=2、k=3 时,初始化窗口的循环会遍历 i=0、1、2(超出数组范围),导致数组越界;
  • 解决方法:先判断数组长度是否小于 k,直接返回 0。

踩坑点 5:初始窗口没判断,直接开始滑动

  • 错误做法:跳过初始窗口的判断,直接进入滑动循环;
  • 后果:如果初始窗口就是满足条件的最大和,会漏掉;
  • 解决方法:初始化窗口后,必须先判断是否满足条件,更新 maxSum。

五、总结:滑动窗口的 "万能模板"(针对固定长度 k 的子数组问题)

以后遇到 "固定长度 k 的子数组,满足 XX 条件,求最大 / 最小 XX" 的题,直接套用这个模板:

  1. 初始化变量

    • 窗口状态变量(比如 count 哈希表、duplicateCount);
    • 窗口统计变量(比如 sum、maxSum);
    • 边界判断(n <k 直接返回默认值)。
  2. 构建初始窗口

    • 遍历前 k 个元素,初始化状态变量和统计变量;
    • 判断初始窗口是否满足条件,更新结果。
  3. 滑动窗口遍历

    • 移除左边元素:更新状态变量和统计变量;
    • 加入右边元素:更新状态变量和统计变量;
    • 判断当前窗口是否满足条件,更新结果。
  4. 返回结果

模板应用举例:

  • 你之前的 "最少翻色次数" 题:条件是 "窗口内 B 最多",统计变量是 maxB,状态变量是 currentB;
  • 本题:条件是 "窗口内无重复",统计变量是 maxSum,状态变量是 count 和 duplicateCount。

3 题目

1423. 可获得的最大点数

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

4 代码实现

cpp 复制代码
class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        long long currentSum = 0 ;
        long long minSum = 0 ;
        long long sum = 0;
        int m = n - k ;
        for(int i = 0 ; i < n ; i++){
            sum += cardPoints[i];
        }
        if (m == 0 ){
            return sum;
        }

        for(int i = 0 ; i < m ; i ++){
            currentSum += cardPoints[i];
        }

        minSum = currentSum;

        for (int i = m ; i < n ; i++){
            currentSum = currentSum - cardPoints[i - m ] + cardPoints[i];
            minSum = min(minSum, currentSum);
        } 
        return sum - minSum ;
    }
};

错误百出的代码

cpp 复制代码
class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        long long currentSum = 0 ;
        long long minSum = 0 ;
        long long sum = 0;
        int left = 0 ;
        int right = 0 ;
        if ( n < k ){
            return 0 ;
        }

        for(int i = 0 ; i < n - k ; i ++){
            currentSum += cardPoints[i];
        }

        minSum = currentSum;

        for (int i = k ; i < n ; i++){
            currentSum = currentSum - cardPoints[i - n + k ] + cardPoints[i];
        } 

        for(int i = 0 ; i < n ; i++){
            sum += cardPoints[i];
        }

        minSum = min(minSum, currentSum);

        long long res = sum - minSum;
        return res ;

    }
};

代码问题拆解 + 修正 + 原理详解

你的思路方向是对的(用 "总和 - 长度为 n-k 的最小子数组和" 求最大得分),但代码里有 3 个关键错误,导致逻辑走不通或结果错误。我会逐行分析问题,再给出修正后的代码和详细解释。

一、先明确题目核心逻辑(避免思路跑偏)

题目要求:从数组首尾取 k 张卡片,求最大得分。核心转化(非常聪明的思路):

  • 总得分 = 所有卡片总和 - 中间 "未取的卡片和"
  • 未取的卡片是 连续的 (因为只能从首尾取),且长度固定为 n - k(总长度 n,取 k 张,剩 n-k 张)
  • 所以问题转化为:找长度为n-k最小连续子数组和,用总和减去它就是最大得分。

你的思路完全正确,但代码实现时踩了细节坑。

二、你的代码逐行找问题

先贴你的代码,再标红错误:

cpp 复制代码
class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        long long currentSum = 0 ;
        long long minSum = 0 ;
        long long sum = 0;
        int left = 0 ;
        int right = 0 ;
        if ( n < k ){ // 错误1:边界条件逻辑反了
            return 0 ;
        }

        // 初始化:计算第一个长度为n-k的子数组和
        for(int i = 0 ; i < n - k ; i ++){
            currentSum += cardPoints[i];
        }
        minSum = currentSum;

        // 滑动窗口找最小子数组和(错误2:循环条件+窗口更新逻辑错)
        for (int i = k ; i < n ; i++){
            currentSum = currentSum - cardPoints[i - n + k ] + cardPoints[i];
        } 

        // 计算数组总和(错误3:位置太晚,导致minSum可能没更新就被用)
        for(int i = 0 ; i < n ; i++){
            sum += cardPoints[i];
        }

        minSum = min(minSum, currentSum); // 错误4:只比较了最后一次滑动的结果,漏掉中间过程

        long long res = sum - minSum;
        return res ;
    }
};

错误 1:边界条件逻辑反了

  • 你的代码:if (n < k) return 0;
  • 实际情况:题目中k是 "要取的卡片数",根据题目约束,1 ≤ k ≤ n(输入保证有效),但如果n == k(取所有卡片),此时 "未取的子数组长度" 是n - k = 0,最小和为 0,总得分 = 总和 - 0 = 总和。
  • 你的错误:当n == k时,n - k = 0,第一个初始化循环(i < 0)不会执行,currentSum保持 0,minSum=0,这部分是对的;但如果题目输入k > n(虽然题目说不会,但逻辑上),返回 0 没问题,但真正的问题是:当n - k = 0时,你的滑动窗口循环(i从k到n-1)会执行(比如 n=3,k=3,i 从 3 到 2,不执行),这部分不影响,但边界条件的逻辑表述是错的(应该是 "如果要找的子数组长度为 0,直接返回总和",而不是 "n < k 返回 0")。

错误 2:滑动窗口的循环条件和更新逻辑错(最核心的错误)

  • 目标:找长度为m = n - k的所有连续子数组的最小和。
  • 滑动窗口的正确逻辑:初始化窗口是[0, m-1](第一个子数组);之后每次窗口向右移 1 位,直到窗口结束位置n-1,即窗口的起始位置从0n - m(因为起始位置 + m - 1 ≤ n-1起始位置 ≤ n - m)。
  • 你的循环条件:for (int i = k ; i < n ; i++)举个例子:n=5,k=2 → m = 5-2=3(要找长度为 3 的子数组)。正确的窗口应该是[0,2][1,3][2,4] → 滑动 2 次,循环应该执行 2 次。你的循环:i 从 k=2 到 n-1=4 → i=2、3、4 → 执行 3 次,多执行了 1 次,且更新逻辑错误。
  • 你的窗口更新公式:currentSum = currentSum - cardPoints[i - n + k ] + cardPoints[i]还是用 n=5,k=2,m=3 举例:第一次滑动(窗口从[0,2][1,3]):应该移除cardPoints[0],加入cardPoints[3]。你的公式:i=2 时,i -n +k = 2-5+2 = -1 → 数组越界!(访问cardPoints[-1]是非法的)。正确的更新公式:移除 "窗口左边的元素"(即i - m,其中m = n -k),加入 "当前 i 位置的元素"。因为窗口长度是 m,当 i 是窗口的结束位置时,窗口的起始位置是i - m + 1,所以要移除的元素是i - m(上一个窗口的起始位置)。

错误 3:数组总和的计算位置太晚

  • 你的代码:在滑动窗口循环结束后才计算总和。
  • 影响:当n -k =0(即 k=n)时,滑动窗口循环不执行,currentSum=0minSum=0,总和计算正确,结果 = 总和 - 0 = 总和,这部分没问题;
  • 但如果滑动窗口循环执行过程中出现错误(比如越界),会导致程序崩溃,总和根本没机会计算。
  • 正确做法:应该在最开始就计算总和(先算总和不影响任何逻辑,且避免后续错误导致总和未计算)。

错误 4:只比较了最后一次滑动的结果,漏掉中间过程

  • 你的代码:滑动窗口循环结束后,只做了一次minSum = min(minSum, currentSum)
  • 错误原因:滑动窗口每次移动后,都可能产生更小的currentSum,需要每次都更新minSum,而不是只在最后更新。
  • 举例:如果滑动过程中,第 2 次滑动的currentSum是最小的,但你只比较最后一次,就会漏掉这个最小值。

三、修正后的代码(标红修改处)

cpp 复制代码
class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        int m = n - k; // 要找的最小子数组长度(中间未取的部分)
        long long totalSum = 0;
        long long currentSum = 0;
        long long minSubSum = 0;

        // 修正1:先计算数组总和(位置提前,逻辑更清晰)
        for (int num : cardPoints) {
            totalSum += num;
        }

        // 修正2:处理m=0的情况(k=n,取所有卡片,得分=总和)
        if (m == 0) {
            return totalSum;
        }

        // 初始化:第一个长度为m的子数组和(窗口[0, m-1])
        for (int i = 0; i < m; ++i) {
            currentSum += cardPoints[i];
        }
        minSubSum = currentSum;

        // 修正3:滑动窗口找最小子数组和(循环条件+更新逻辑正确)
        // 窗口结束位置i从m到n-1(每个窗口是[i-m+1, i])
        for (int i = m; i < n; ++i) {
            // 移除上一个窗口的左边元素(i-m位置),加入当前i位置元素
            currentSum = currentSum - cardPoints[i - m] + cardPoints[i];
            // 修正4:每次滑动后都更新最小和
            minSubSum = min(minSubSum, currentSum);
        }

        // 最大得分 = 总和 - 最小子数组和
        return totalSum - minSubSum;
    }
};

四、用示例验证修正后的代码

示例 1:

输入:cardPoints = [1,2,3,4,5,6,1], k=3n=7,k=3 → m=7-3=4(找长度为 4 的最小子数组和)总和 = 1+2+3+4+5+6+1=22可能的长度为 4 的子数组:

  • 1,2,3,4\] 和 = 10

  • 3,4,5,6\] 和 = 18

修正后的代码执行过程:

  1. 初始化 currentSum=1+2+3+4=10 → minSubSum=10;
  2. 滑动 1 次(i=4):currentSum=10-1+5=14 → minSubSum=min (10,14)=10;
  3. 滑动 2 次(i=5):currentSum=14-2+6=18 → minSubSum=10;
  4. 滑动 3 次(i=6):currentSum=18-3+1=16 → minSubSum=10;
  5. 结果 = 22-10=12(正确)。

示例 2:

输入:cardPoints = [2,2,2], k=2n=3,k=2 → m=1(找长度为 1 的最小子数组和)总和 = 6,最小子数组和 = 2 → 最大得分 = 6-2=4(正确答案:取首尾的 2 和 2,得分 4)。

示例 3:

输入:cardPoints = [9,7,7,9,7,7,9], k=7n=7,k=7 → m=0 → 直接返回总和 = 9+7+7+9+7+7+9=55(正确)。

五、总结:你为什么会犯这些错误?(帮你避免再踩坑)

  1. 对滑动窗口的 "窗口边界" 理解不深 :滑动窗口的核心是 "明确窗口的起始和结束位置",对于固定长度 m 的窗口,结束位置 i 的范围是[m-1, n-1],每次滑动时,移除的是 "上一个窗口的起始位置"(i-m),而不是凭感觉写公式(比如你之前的i -n +k)。解决方法:下次写滑动窗口时,先定义m = 窗口长度,再明确:

    • 初始窗口:[0, m-1]
    • 滑动时,结束位置 i 从mn-1
    • 移除的元素是i - m(上一个窗口的起始位置)
  2. 细节处理不到位

    • 总和计算位置、minSum 的更新时机,这些细节看似小,却直接影响结果;
    • 边界条件(m=0)没有单独处理,虽然原来的代码可能碰巧通过,但逻辑不严谨。
  3. 缺乏 "测试用例模拟" 的习惯 :写代码时,没有拿一个具体的例子(比如 n=5,k=2)去模拟循环执行过程,导致公式错误(比如i -n +k会越界)。

  4. 解决方法:以后写滑动窗口代码,先找一个小例子,手动模拟 3 次滑动过程,验证循环条件和更新公式是否正确。

六、最终修正代码(可直接提交)

cpp 复制代码
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution {
public:
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        int m = n - k;
        long long totalSum = 0;
        for (int num : cardPoints) {
            totalSum += num;
        }
        if (m == 0) {
            return totalSum;
        }
        long long currentSum = 0;
        // 初始化第一个窗口
        for (int i = 0; i < m; ++i) {
            currentSum += cardPoints[i];
        }
        long long minSubSum = currentSum;
        // 滑动窗口
        for (int i = m; i < n; ++i) {
            currentSum = currentSum - cardPoints[i - m] + cardPoints[i];
            minSubSum = min(minSubSum, currentSum);
        }
        return totalSum - minSubSum;
    }
};

提交后可以通过所有测试用例,你可以对比自己的代码,再用示例模拟一遍,加深理解。

相关推荐
爱打代码的小林1 小时前
numpy库数组笔记
笔记·python·numpy
Slaughter信仰1 小时前
图解大模型_生成式AI原理与实战学习笔记(第四章)
人工智能·笔记·学习
martian6651 小时前
详解高阶数学领域-信息论与深度学习:互信息在对比学习中的应用
人工智能·深度学习·学习
Wiktok1 小时前
TailwindCSS学习路径方法总结
学习·css3·tailwindcss
FFF团团员9092 小时前
树莓派学习笔记6:摄像头的基本使用
笔记·学习
buyue__2 小时前
C++实现数据结构——链表
数据结构·c++·链表
d111111111d2 小时前
在SM32F103C8T6中MCU和MPU的区别,都有什么各自的优点,缺点,都可以用来干什么。
笔记·stm32·单片机·嵌入式硬件·学习
feifeigo1232 小时前
MATLAB实现两组点云ICP配准
开发语言·算法·matlab
fengfuyao9852 小时前
粒子群算法(PSO)求解标准VRP问题的MATLAB实现
开发语言·算法·matlab