
本文详解如何使用滑动窗口与哈希计数高效解决「长度为 k 且元素互异的子数组最大和」问题,重点剖析标志位逻辑缺陷、窗口有效性判断本质,并提供简洁健壮的 Python 实现。
本文详解如何使用滑动窗口与哈希计数高效解决「长度为 k 且元素互异的子数组最大和」问题,重点剖析标志位逻辑缺陷、窗口有效性判断本质,并提供简洁健壮的 Python 实现。
在 LeetCode 第 2510 题 Maximum Sum of Distinct Subarrays with Length K 中,我们需要在整数数组 nums 中找出所有长度恰好为 k 且内部元素全部互异(即无重复)的连续子数组,并返回其中元素和的最大值;若不存在满足条件的子数组,则返回 0。
初学者常尝试引入布尔标志 canIcount 来跟踪当前窗口是否“合法”,但该思路存在根本性缺陷:标志位无法准确反映窗口状态的动态变化。例如,当窗口中某元素频次从 1 增至 2 时,canIcount = False 是合理的;但当该元素频次后续从 2 减至 1 时,仅靠局部增减操作无法自动恢复 canIcount = True——因为此时还需确保其余所有元素频次均为 1。因此,依赖标志位易导致逻辑断裂(如题中第三个测试用例 [1,1,1,7,8,9], k=3 返回 0 而非期望的 24),本质是将“全局唯一性判定”错误简化为“局部变更追踪”。
✅ 正确解法的核心洞察是:一个长度为 k 的窗口内元素全部互异,当且仅当其频次字典 eleToFreq 的键数量等于 k。即直接用 len(eleToFreq) == k 替代任何手工维护的布尔标志。这既准确又无状态依赖,是滑动窗口处理“唯一性约束”的标准范式。
此外,窗口收缩(左边界 i 移动)时的频次更新也需严谨:
- 若 eleToFreq[a[i]] > 1,说明该元素在窗口中仍有多余副本,仅需 -= 1;
- 若 eleToFreq[a[i]] == 1,则移除后该元素彻底消失,必须 pop(),否则字典中残留键值对会导致 len(eleToFreq) 始终偏大,破坏唯一性判断。
以下是优化后的完整实现(已通过全部官方测试用例):
class Solution:
def maximumSubarraySum(self, nums: List[int], k: int) -> int:
sum_so_far = max_sum = 0
freq = {}
left = 0
for right, val in enumerate(nums):
# 扩展右边界:添加 nums[right]
freq[val] = freq.get(val, 0) + 1
sum_so_far += val
# 当窗口长度达到 k 时,检查并收缩
if right >= k - 1:
# ✅ 关键:仅当窗口内恰好有 k 个不同元素时才更新答案
if len(freq) == k:
max_sum = max(max_sum, sum_so_far)
# 收缩左边界:移除 nums[left]
left_val = nums[left]
if freq[left_val] > 1:
freq[left_val] -= 1
else:
freq.pop(left_val)
sum_so_far -= left_val
left += 1
return max_sum? 关键优化点说明:
- 消除冗余逻辑:删除了初始 if len(set(nums))
- 代码结构扁平化:利用 for ... enumerate 统一处理窗口扩展,避免 while 循环中 i/j 双指针的手动递增及 if/else 分支重复(如原代码中 j = k-1 下均有频次更新与 sumSoFar 累加)。
- 频次操作安全:使用 freq.get(val, 0) 替代 in 判断,更简洁;pop() 前严格校验 ==1,杜绝字典键残留。
⚠️ 注意事项:
- 时间复杂度:O(n),每个元素最多被访问两次(进窗+出窗);
- 空间复杂度:O(min(k, n)),哈希表最多存储 k 个键;
- 边界鲁棒性:当 k > len(nums) 时,循环不会进入 right >= k-1 分支,直接返回 0,符合预期。
总结而言,解决此类带约束的滑动窗口问题,应摒弃“状态标志位”这类易错抽象,转而直接、实时地验证约束条件本身(此处即 len(freq) == k)。这一原则可迁移至其他唯一性、特定频次等约束场景,是算法工程实践中兼具正确性与可维护性的关键实践。









