【算法】删除子数组的最大得分 & 最多 K 个重复元素的最长子数组——不定长滑动窗口与哈希频率约束

【算法】删除子数组的最大得分 & 最多 K 个重复元素的最长子数组------不定长滑动窗口与哈希频率约束

    • [1695. 删除子数组的最大得分](#1695. 删除子数组的最大得分)
      • [1. 题目介绍](#1. 题目介绍)
      • [2. 题目示例](#2. 题目示例)
      • [3. 算法思路](#3. 算法思路)
      • [4. 核心代码](#4. 核心代码)
      • [5. 示例测试(总代码)](#5. 示例测试(总代码))
    • [2958. 最多 K 个重复元素的最长子数组](#2958. 最多 K 个重复元素的最长子数组)
      • [1. 题目介绍](#1. 题目介绍)
      • [2. 题目示例](#2. 题目示例)
      • [3. 算法思路](#3. 算法思路)
      • [4. 核心代码](#4. 核心代码)
      • [5. 示例测试(总代码)](#5. 示例测试(总代码))
    • 总结

🎬 博主名称: 超级苦力怕

🔥 个人专栏: 《LeetCode 题解》

🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!


本篇文章讲解的是 LeetCode 第 1695 题------删除子数组的最大得分 和 第 2958 题------最多 K 个重复元素的最长子数组 。两道题看似目标不同------一个求最大元素和,一个求最长子数组长度------但本质上考察的都是 不定长滑动窗口 + HashMap 频率约束 的核心模式:右指针不断扩展窗口并更新哈希表,左指针在频率超限时收缩,动态维护窗口内每个元素的出现次数 ≤ 上限,从而在 O(n) 时间内得出答案。

1695 的约束是"窗口内所有元素唯一"(频率 ≤ 1),目标是最大化窗口元素之和;2958 将约束泛化为"每个元素频率 ≤ k",目标是最大化窗口长度。两道题共用同一套滑动窗口模板,仅约束条件和统计目标不同。

本文将使用 Java 进行讲解,从暴力枚举出发,逐步过渡到滑动窗口 + HashMap,帮助你掌握不定长滑窗中最常见的一类模式------频率约束下的最长/最优合法子数组

1695. 删除子数组的最大得分

1. 题目介绍

1695. 删除子数组的最大得分

直达链接:LeetCode 1695

给定一个正整数数组 nums,你需要删除一个 包含唯一元素 的子数组。删除子数组的得分就是子数组中各元素之

请你返回 只删除一个 子数组可获得的 最大得分 。换言之,你需要从 nums 中选出一个元素互不相同的连续子数组,使得其元素之和最大。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4

2. 题目示例

示例 1:

复制代码
输入:nums = [4,2,4,5,6]
输出:17
解释:最优子数组是 [2,4,5,6],元素之和为 2 + 4 + 5 + 6 = 17。

示例 2:

复制代码
输入:nums = [5,2,1,2,5,2,1,2,5]
输出:8
解释:最优子数组是 [5,2,1] 或 [1,2,5],元素之和为 8。

3. 算法思路

本题的核心约束是 窗口内元素必须互不相同。如果窗口里出现了重复元素,就说明当前窗口非法,需要收缩左边界,直到重复的那个元素被移出窗口为止。

暴力枚举法(超时)

枚举所有子数组的起止位置 [l, r],用哈希表判断是否包含重复元素,若是则更新最大和。时间复杂度 O(n²),在 n = 10^5 时不可行。

滑动窗口 + HashMap

滑动窗口是这道题的最优解法。用一个哈希表 freq 记录窗口内每个元素的出现次数,右指针 right 每步先加入新元素,若该元素出现次数 > 1(即窗口内出现重复),则不断右移左指针 left、将移出元素从 freq 中减掉,直到重复消失为止。窗口合法的每一个时刻,都尝试用当前窗口元素之和更新答案。

步骤拆解:

  1. 初始化 left = 0sum = 0maxSum = 0HashMap<Integer, Integer> freq
  2. right0 遍历到 n-1
    • sum += nums[right]freq[nums[right]]++
    • freq[nums[right]] > 1:循环执行 sum -= nums[left]freq[nums[left]]--left++,直到该元素频率降回 1
    • maxSum = max(maxSum, sum)
  3. 返回 maxSum
操作 时间复杂度
右指针遍历 O(n),每个元素入窗一次
左指针收缩 O(n),每个元素出窗一次
总体 O(n)

4. 核心代码

java 复制代码
class Solution {
    public int maximumUniqueSubarray(int[] nums) {
        int n = nums.length;
        int left = 0;
        int sum = 0;
        int maxSum = 0;
        HashMap<Integer, Integer> freq = new HashMap<>();

        for (int right = 0; right < n; right++) {
            // 1. 右指针入窗
            sum += nums[right];
            freq.put(nums[right], freq.getOrDefault(nums[right], 0) + 1);

            // 2. 若出现重复,收缩左边界直到重复消失
            while (freq.get(nums[right]) > 1) {
                sum -= nums[left];
                freq.put(nums[left], freq.get(nums[left]) - 1);
                left++;
            }

            // 3. 此时窗口内元素互不相同,更新最大得分
            maxSum = Math.max(maxSum, sum);
        }

        return maxSum;
    }
}

5. 示例测试(总代码)

java 复制代码
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Solution sol = new Solution();

        // 示例1测试
        int[] nums1 = {4, 2, 4, 5, 6};
        System.out.println("示例1输出:" + sol.maximumUniqueSubarray(nums1)); // 预期输出 17

        // 示例2测试
        int[] nums2 = {5, 2, 1, 2, 5, 2, 1, 2, 5};
        System.out.println("示例2输出:" + sol.maximumUniqueSubarray(nums2)); // 预期输出 8
    }
}

2958. 最多 K 个重复元素的最长子数组

1. 题目介绍

2958. 最多 K 个重复元素的最长子数组

直达链接:LeetCode 2958

给定一个整数数组 nums 和一个非负整数 k。一个子数组 合法 当且仅当其中 每个元素 的出现次数都 不超过 k

返回最长合法子数组的长度。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= nums.length

2. 题目示例

示例 1:

复制代码
输入:nums = [1,2,3,1,2,3,1,2], k = 2
输出:6
解释:最长合法子数组是 [1,2,3,1,2,3],其中 1、2、3 各出现恰好 2 次。

示例 2:

复制代码
输入:nums = [1,2,1,2,1,2,1,2], k = 1
输出:2
解释:k = 1 时合法子数组中所有元素必须互不相同,最长的是 [1,2](长度 2)。

示例 3:

复制代码
输入:nums = [5,5,5,5,5,5,5], k = 4
输出:4
解释:最长合法子数组是 [5,5,5,5](长度 4),其中 5 出现 4 次 ≤ k。

3. 算法思路

这道题是 1695 的泛化版本:1695 要求元素频率 ≤ 1,而本题将上限泛化为 k。核心模式完全一致------右扩左缩,哈希表维护窗口内频率

为什么不能预先固定窗口大小?

因为不同元素的出现位置是任意的,合法窗口的结束位置取决于各个元素的分布,无法提前预判窗口应该有多长。因此必须使用 不定长滑动窗口 :右指针每次扩展一步,一旦有新元素频率超过 k,就移动左指针直到该元素频率降回 k。窗口合法的每一步都更新最大长度。

步骤拆解:

  1. 初始化 left = 0maxLen = 0HashMap<Integer, Integer> freq
  2. right0 遍历到 n-1
    • freq[nums[right]]++
    • freq[nums[right]] > k:循环执行 freq[nums[left]]--left++,直到该元素频率降回 k
    • maxLen = max(maxLen, right - left + 1)
  3. 返回 maxLen

关键细节: 收缩条件只检查 nums[right] 对应的频率。因为只有新加入元素才可能突破频率上限------窗口在加入 nums[right] 之前是合法的,之前的元素频率最多恰好是 k,不会超过 k。这个性质保证了 while 循环的收缩目标单一且高效。

4. 核心代码

java 复制代码
class Solution {
    public int maxSubarrayLength(int[] nums, int k) {
        int n = nums.length;
        int left = 0;
        int maxLen = 0;
        HashMap<Integer, Integer> freq = new HashMap<>();

        for (int right = 0; right < n; right++) {
            // 1. 右指针入窗
            freq.put(nums[right], freq.getOrDefault(nums[right], 0) + 1);

            // 2. 若新元素频率超 k,收缩左边界直到合规
            while (freq.get(nums[right]) > k) {
                freq.put(nums[left], freq.get(nums[left]) - 1);
                left++;
            }

            // 3. 此时窗口内所有元素频率 ≤ k,更新最大长度
            maxLen = Math.max(maxLen, right - left + 1);
        }

        return maxLen;
    }
}

5. 示例测试(总代码)

java 复制代码
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Solution sol = new Solution();

        // 示例1测试
        int[] nums1 = {1, 2, 3, 1, 2, 3, 1, 2};
        System.out.println("示例1输出:" + sol.maxSubarrayLength(nums1, 2)); // 预期输出 6

        // 示例2测试
        int[] nums2 = {1, 2, 1, 2, 1, 2, 1, 2};
        System.out.println("示例2输出:" + sol.maxSubarrayLength(nums2, 1)); // 预期输出 2

        // 示例3测试
        int[] nums3 = {5, 5, 5, 5, 5, 5, 5};
        System.out.println("示例3输出:" + sol.maxSubarrayLength(nums3, 4)); // 预期输出 4
    }
}

总结

题号 题名 约束 优化目标 窗口收缩条件
1695 删除子数组的最大得分 元素频率 ≤ 1 最大元素和 freq[nums[right]] > 1
2958 最多 K 个重复元素的最长子数组 元素频率 ≤ k 最长长度 freq[nums[right]] > k

两道题的代码结构几乎完全相同,差异仅在于两点:

  1. 统计目标不同 :1695 累加 sum 并取 maxSum;2958 计算窗口长度 right - left + 1 并取 maxLen
  2. 约束阈值不同 :1695 是 2958 在 k = 1 时的特例

💡 核心结论: 不定长滑动窗口 + HashMap 频率约束是处理"元素出现次数有限制"类子数组问题的通用模板。右指针负责扩展并更新频次,左指针在频次超限时收缩。每个元素最多入窗一次、出窗一次,总时间复杂度 O(n)。

💡 扩展方向: 将这套模板记熟后,遇到同类变体------如"窗口内最多包含 k 种不同元素""恰好包含 k 种不同元素"------只需调整 freq 的检查条件和目标统计方式即可。

相关推荐
染指11101 小时前
3.AI大模型-token是什么-大模型底层运行机制
人工智能·算法·机器学习
谙弆悕博士1 小时前
快速学C语言——第19章:C语言常用开发库
c语言·开发语言·算法·业界资讯·常用函数
光影少年1 小时前
前端算法题
前端·javascript·算法
南宫萧幕2 小时前
基于 Simulink 与 Python 联合仿真的 eVTOL 强化学习全链路实战
开发语言·人工智能·python·算法·机器学习·控制
电魂泡哥2 小时前
CMS垃圾回收
java·jvm·算法
hkj88082 小时前
CRC-512算法输出64字节
算法
@我漫长的孤独流浪2 小时前
计算机系统核心概念与性能优化全解析
算法·计算机外设
如竟没有火炬2 小时前
接雨水22
数据结构·python·算法·leetcode·散列表
ʚ希希ɞ ྀ2 小时前
二叉树的锯齿层序遍历
数据结构·算法