1 题目
给你一个整数数组 nums 和两个正整数 m 和 k 。
请你返回 nums 中长度为 k 的 几乎唯一 子数组的 最大和 ,如果不存在几乎唯一子数组,请你返回 0 。
如果 nums 的一个子数组有至少 m 个互不相同的元素,我们称它是 几乎唯一 子数组。
子数组指的是一个数组中一段连续 非空 的元素序列。
示例 1:
输入:nums = [2,6,7,3,1,7], m = 3, k = 4
输出:18
解释:总共有 3 个长度为 k = 4 的几乎唯一子数组。分别为 [2, 6, 7, 3] ,[6, 7, 3, 1] 和 [7, 3, 1, 7] 。这些子数组中,和最大的是 [2, 6, 7, 3] ,和为 18 。
示例 2:
输入:nums = [5,9,9,2,4,5,4], m = 1, k = 3
输出:23
解释:总共有 5 个长度为 k = 3 的几乎唯一子数组。分别为 [5, 9, 9] ,[9, 9, 2] ,[9, 2, 4] ,[2, 4, 5] 和 [4, 5, 4] 。这些子数组中,和最大的是 [5, 9, 9] ,和为 23 。
示例 3:
输入:nums = [1,2,1,2,1,2,1], m = 3, k = 3
输出:0
解释:输入数组中不存在长度为 k = 3 的子数组含有至少 m = 3 个互不相同元素的子数组。所以不存在几乎唯一子数组,最大和为 0 。
提示:
1 <= nums.length <= 2 * 1041 <= m <= k <= nums.length1 <= nums[i] <= 109
2 代码实现
cpp
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
unordered_map<int ,int > count ;
long long sum = 0 ;
long long max_sum = 0 ;
int unique = 0 ;
int left =0;
int right = 0;
while (right < n ){
int c = nums[right];
if(count[c] == 0 ){
unique++;
}
count[c]++;
sum+= c ;
right++ ;
if (right - left == k ){
if (unique >= m ){
if (sum > max_sum){
max_sum = sum ;
}
}
int d = nums[left];
count[d] --;
sum -= d ;
if (count[d] == 0){
unique--;
}
left++;
}
}
return max_sum ;
}
};
题解
框架
cpp
// 滑动窗口算法伪码框架
void slidingWindow(string s) {
// 用合适的数据结构记录窗口中的数据,根据具体场景变通
// 比如说,我想记录窗口中元素出现的次数,就用 map
// 如果我想记录窗口中的元素和,就可以只用一个 int
auto window = ...
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
window.add(c);
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
// *** debug 输出的位置 ***
printf("window: [%d, %d)\n", left, right);
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
window.remove(d);
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
先明确框架与题目的对应关系
框架是通用的,我们需要先把题目中的 "窗口规则" 映射到框架的各个部分:
| 框架组件 | 题目中的具体含义 |
|---|---|
窗口数据结构 window |
需记录 2 件事: ① 窗口内各元素的出现次数(判断唯一元素个数); ② 窗口内元素和(计算最大和)。用哈希表 count 记录次数,用变量 sum 记录和。 |
窗口边界 left, right |
窗口固定长度为 k,right 负责扩展窗口,left 负责在窗口长度超 k 时收缩(保持窗口长度为 k)。 |
扩展窗口(right++) |
将 nums[right] 加入窗口,更新 count(次数 + 1)和 sum(和 + 1)。 |
收缩窗口条件 (needs shrink) |
窗口长度 right - left == k 时,无需继续收缩(因为窗口固定长度,每次扩展后只收缩 1 次),所以这里是 if 逻辑(而非 while)。 |
收缩窗口(left++) |
将 nums[left] 移出窗口,更新 count(次数 - 1,若次数为 0 则减少唯一元素个数)和 sum(和 - 1)。 |
| 窗口内数据更新 | 每次窗口稳定(长度为 k)后,判断唯一元素个数是否 ≥ m,若满足则更新最大和。 |
按照框架拆解解题步骤
我们将代码和框架一一对应,分步骤讲解:
1. 初始化变量(对应框架开头)
cpp
int n = nums.size();
unordered_map<int, int> count; // 窗口数据结构:记录元素出现次数
long long sum = 0; // 窗口数据结构:记录窗口元素和
long long max_sum = 0; // 结果:最大几乎唯一子数组和
int unique = 0; // 窗口内互不相同元素的个数(派生自 count)
int left = 0, right = 0; // 窗口边界
2. 扩展窗口(right 右移,对应框架 while (right < s.size()))
循环条件:right < n(遍历整个数组),每次将 nums[right] 加入窗口:
cpp
while (right < n) {
// c 是将移入窗口的元素(对应框架 `char c = s[right]`)
int c = nums[right];
// 窗口添加元素:更新 count 和 sum(对应框架 `window.add(c)`)
if (count[c] == 0) {
unique++; // 新元素,唯一元素个数+1
}
count[c]++; // 元素出现次数+1
sum += c; // 窗口和+1
// 增大窗口(对应框架 `right++`)
right++;
// 3. 窗口内数据更新(扩展后暂未收缩,窗口长度可能不足 k,暂不判断结果)
// (此时窗口长度为 `right - left`,可能小于 k,无需处理)
// 4. 判断是否需要收缩窗口(对应框架 `while (window needs shrink)`)
// 窗口长度 == k 时,需要收缩 left 保持窗口长度固定(固定长度窗口,收缩 1 次即可,用 if)
if (right - left == k) {
// 窗口稳定(长度为 k),判断是否满足"几乎唯一"条件,更新结果
if (unique >= m) {
if (sum > max_sum) {
max_sum = sum;
}
}
// 收缩窗口:移除 left 元素(对应框架 `char d = s[left]` 和 `window.remove(d)`)
int d = nums[left];
count[d]--; // 元素出现次数-1
sum -= d; // 窗口和-1
if (count[d] == 0) {
unique--; // 元素次数为 0,唯一元素个数-1
}
// 缩小窗口(对应框架 `left++`)
left++;
}
}
3. 最终返回结果
cpp
return max_sum;
框架适配关键点说明
-
固定长度窗口的收缩逻辑 :框架中的收缩是
while循环(应对可变长度窗口),但本题窗口长度固定为k,所以收缩条件是right - left == k(窗口刚达到长度k),收缩时只移动left一次(if而非while),确保窗口始终保持长度k。 -
窗口数据结构的设计 :框架中的
window是抽象的,本题根据需求拆分为count(记录次数)和sum(记录和),再派生unique变量(记录唯一元素个数),都是为了快速获取窗口状态(无需遍历窗口计算)。 -
结果更新时机 :只有当窗口稳定(长度为
k)时,才判断是否满足 "至少m个唯一元素",此时计算的和才是长度为k的子数组和,符合题目要求。
完整代码(贴合框架版)
cpp
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
// 窗口数据结构:count 记录元素出现次数,sum 记录窗口和
unordered_map<int, int> count;
long long sum = 0;
long long max_sum = 0;
int unique = 0; // 窗口内互不相同元素个数(派生自 count)
int left = 0, right = 0;
while (right < n) {
// 1. 移入右侧元素,扩展窗口
int c = nums[right];
if (count[c] == 0) {
unique++;
}
count[c]++;
sum += c;
right++; // 增大窗口
// 2. 判断是否需要收缩窗口(固定长度 k,达到长度则收缩)
if (right - left == k) {
// 3. 窗口稳定,更新结果(判断是否为几乎唯一子数组)
if (unique >= m) {
max_sum = max(max_sum, sum);
}
// 4. 移出左侧元素,收缩窗口
int d = nums[left];
count[d]--;
sum -= d;
if (count[d] == 0) {
unique--;
}
left++; // 缩小窗口
}
}
return max_sum;
}
};
核心逻辑总结(对照框架)
| 框架步骤 | 本题具体执行 |
|---|---|
| 初始化窗口数据和边界 | 初始化 count、sum、left=0、right=0 |
扩展窗口(right++) |
加入 nums[right],更新 count、sum、unique |
| 判断收缩条件 | 窗口长度 == k(固定长度,必须收缩以保持窗口大小) |
| 窗口内数据更新(结果) | 窗口稳定后,判断 unique >= m,更新最大和 |
收缩窗口(left++) |
移除 nums[left],更新 count、sum、unique |
| 循环直至数组遍历结束 | right < n 循环结束,返回最大和 |
这种适配方式完全遵循了滑动窗口的通用框架,只是根据 "固定窗口长度" 的题目特点,将收缩逻辑从 while 改为 if,同时按需设计了窗口数据结构(count+sum),让代码逻辑更清晰、更贴合框架思想。
错误百出的代码
cpp
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
unordered_map<int ,int > count ;
long long sum = 0 ;
long long max_sum = 0 ;
int unique = 0 ;
int left , right = 0;
while (right < n ){
int c = nums[right];
if(count[c] == 0 ){
unique++;
}
count[c]++;
sum+= c ;
right++ ;
if (right - left == k ){
if (unique >= m ){
if (sum > max_sum){
max_sum = sum ;
}
}
}
int d = nums[left];
count[d] --;
sum -= d ;
if (count[d] == 0){
unique--;
}
left++;
}
return max_sum ;
}
};
要解决这个问题,我们需要找到数组中长度为 k 且包含至少 m 个不同元素的子数组的最大和。你的代码思路是正确的(滑动窗口 + 哈希表统计),但存在窗口维护逻辑错误,导致结果不正确。下面先分析错误,再详细解释核心逻辑和正确实现。
一、你的代码哪里错了?
核心错误:窗口还没形成长度 k 就提前移动左指针,导致窗口始终不满足长度要求。
你的代码逻辑:
cpp
while (right < n ){
// 右指针移动,加入元素(正确)
...
right++ ;
// 检查窗口长度是否为k(正确)
if (right - left == k ){ ... }
// 无论窗口长度是否为k,都移动左指针(错误!)
count[d] --;
sum -= d ;
...
left++;
}
比如当 k=3 时:
right=0:加入元素,right=1,窗口长度1-0=1 <3,但你直接移动左指针left=1,窗口长度变为0;- 后续循环中,窗口永远无法达到长度
k,自然无法正确计算符合条件的子数组和。
修正原则 :只有当窗口长度 超过 k 时,才移动左指针缩小窗口(保证窗口始终是长度为 k 的固定窗口)。
二、核心逻辑详解(滑动窗口 + 唯一元素统计)
1. 问题本质
- 固定窗口长度
k:我们只关注所有长度为k的子数组(滑动窗口的 "固定窗口" 模式); - 约束条件:子数组中不同元素的个数 ≥ m;
- 目标:找到满足约束的子数组中,元素和最大的那个。
2. 关键问题:如何高效统计窗口内的 "唯一元素个数"?
用哈希表(unordered_map) 统计窗口内每个元素的出现次数:
- 哈希表的
key:窗口中的元素值; - 哈希表的
value:该元素在窗口中出现的次数; - 辅助变量
unique:记录窗口内不同元素的个数(避免每次遍历哈希表统计,提升效率)。
3. 唯一元素统计的核心逻辑
-
元素加入窗口(右指针移动) :当把元素
nums[right]加入窗口时:- 如果哈希表中该元素的计数为
0(说明是新加入的唯一元素),则unique++; - 然后将该元素的计数
+1,并累加元素和sum。
- 如果哈希表中该元素的计数为
-
元素移出窗口(左指针移动) :当窗口长度超过
k(需要移除左边界元素nums[left])时:- 将该元素的计数
-1; - 如果计数变为
0(说明该元素已完全移出窗口,不再是窗口内的元素),则unique--; - 最后减去该元素的值,更新
sum,并移动左指针。
- 将该元素的计数
4. 为什么这样统计 "唯一元素"?
unique直接记录 "当前窗口中出现过的不同元素的数量",比每次遍历哈希表(count.size())更高效(O (1) 操作);- 只有 "元素首次加入窗口" 时,
unique才会增加;只有 "元素完全移出窗口" 时,unique才会减少,确保统计准确。
第二次依旧写错
cpp
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
unordered_map<int ,int > count ;
long long sum = 0 ;
long long max_sum = 0 ;
int unique = 0 ;
int left , right = 0;
while (right < n ){
int c = nums[right];
if(count[c] == 0 ){
unique++;
}
count[c]++;
sum+= c ;
right++ ;
if (right - left == k ){
if (unique >= m ){
if (sum > max_sum){
max_sum = sum ;
}
}
int d = nums[left];
count[d] --;
sum -= d ;
if (count[d] == 0){
unique--;
}
left++;
}
}
return max_sum ;
}
};
你的思路其实已经很接近了!只需要 把 "移动左指针、移除左元素" 的逻辑,放进 "窗口长度等于 k" 的判断里 即可 ------ 这样就不会出现 "窗口没形成就提前缩窗" 的问题。
下面是微调后的正确代码(只改了 3 行,核心是调整代码块的缩进):
cpp
class Solution {
public:
long long maxSum(vector<int>& nums, int m, int k) {
int n = nums.size();
unordered_map<int, int> count;
long long sum = 0;
long long max_sum = 0;
int unique = 0;
int left = 0, right = 0; // 修正:left初始化0(原代码只给right赋值,left是随机值,会出错!)
while (right < n) {
int c = nums[right];
if (count[c] == 0) {
unique++;
}
count[c]++;
sum += c;
right++;
// 关键:只有窗口长度等于k时,才判断是否更新max_sum,然后缩窗
if (right - left == k) {
if (unique >= m) {
if (sum > max_sum) {
max_sum = sum;
}
}
// 缩窗逻辑:移除左元素(必须放在if里面!)
int d = nums[left];
count[d]--;
sum -= d;
if (count[d] == 0) {
unique--;
}
left++;
}
}
return max_sum;
}
};
只改了两处关键微调(原代码的核心问题):
- left 初始化 :原代码
int left , right = 0;中,left没有初始化(是随机垃圾值),会导致right - left计算错误,必须改成int left = 0, right = 0;。 - 缩窗逻辑缩进 :原代码中 "移除左元素、移动 left" 的逻辑在
if (right - left == k)外面,导致窗口没形成就缩窗;现在把这部分代码放进if里面,只有窗口长度等于 k 时才缩窗,保证窗口始终是 "长度为 k" 的固定窗口。
为什么这样微调就对了?
- 窗口形成条件:
right - left == k(因为 right 已经先移动了,窗口长度是right - left)。 - 只有窗口形成后,才做两件事:
- 检查是否满足 "唯一元素≥m",如果满足就更新最大和;
- 缩窗(移除左边界元素,移动 left),为下一个窗口做准备。
- 这样遍历下来,每个 "长度为 k" 的子数组都会被检查,且不会漏判或误判。
再用小例子验证(nums=[2,6,7,3,1,7], m=3, k=4):
- right 从 0 到 3 时,
right - left = 4(窗口形成):- 窗口元素 [2,6,7,3],unique=4≥3,sum=18,max_sum=18;
- 然后移除左元素 2,left=1,sum=16,unique=3。
- right=4 时,
right - left = 4(窗口 [6,7,3,1]):- unique=4≥3,sum=17,max_sum 保持 18;
- 移除左元素 6,left=2,sum=11,unique=3。
- right=5 时,
right - left =4(窗口 [7,3,1,7]):- unique=3≥3,sum=18,max_sum 保持 18?不对,原数组最后一个窗口是 [3,1,7,7]?哦,重新算:其实 right=5 时,窗口是 [7(index2),3(3),1(4),7(5)],sum=7+3+1+7=18,确实和之前一样。
- 整个过程每个长度为 k 的窗口都被正确处理,结果正确。