LeetCode 2799.统计完全子数组的数目:滑动窗口(哈希表)

【LetMeFly】2799.统计完全子数组的数目:滑动窗口(哈希表)

力扣题目链接:https://leetcode.cn/problems/count-complete-subarrays-in-an-array/

给你一个由 整数组成的数组 nums

如果数组中的某个子数组满足下述条件,则称之为 完全子数组

  • 子数组中 不同 元素的数目等于整个数组不同元素的数目。

返回数组中 完全子数组 的数目。

子数组 是数组中的一个连续非空序列。

 

示例 1:

输入:nums = [1,3,1,2,2]
输出:4
解释:完全子数组有:[1,3,1,2]、[1,3,1,2,2]、[3,1,2] 和 [3,1,2,2] 。

示例 2:

输入:nums = [5,5,5,5]
输出:10
解释:数组仅由整数 5 组成,所以任意子数组都满足完全子数组的条件。子数组的总数为 10 。

 

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 2000

解题方法:滑动窗口

首先使用一个哈希表统计数组中出现了多少种的元素(记为allType)。

接着再使用一个哈希表,统计窗口中每个元素出现多少次。

数组中的元素依次加入窗口中,当窗口中元素种类数为allType并且窗口中第一个元素出现次数不为1时,左移窗口左指针。

这样,就保证了每次窗口右指针确定时,左指针指向位置为最后一个“窗口合法”的位置(或0)。

  • 时间复杂度 O ( l e n ( n u m s ) ) O(len(nums)) O(len(nums))
  • 空间复杂度 O ( l e n ( n u m s ) ) O(len(nums)) O(len(nums))

AC代码

C++
/*
 * @Author: LetMeFly
 * @Date: 2025-04-24 22:47:03
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-04-24 23:05:27
 * @Description: AC,36.08%,63.38%
 */
class Solution {
public:
    int countCompleteSubarrays(vector<int>& nums) {
        unordered_set<int> visited;
        for (int t : nums) {
            visited.insert(t);
        }
        int allType = visited.size();
        unordered_map<int, int> times;
        int ans = 0;
        for (int l = 0, r = 0; r < nums.size(); r++) {
            times[nums[r]]++;
            while (times.size() == allType && times[nums[l]] > 1) {
                times[nums[l++]]--;
            }
            if (times.size() == allType) {
                ans += l + 1;
            }
        }
        return ans;
    }
};

Python
'''
Author: LetMeFly
Date: 2025-04-24 22:47:44
LastEditors: LetMeFly.xyz
LastEditTime: 2025-04-24 23:10:14
Description: AC,60.65%,29.08%
'''
from typing import List
from collections import defaultdict

class Solution:
    def countCompleteSubarrays(self, nums: List[int]) -> int:
        allType = len(set(nums))
        times = defaultdict(int)
        l = ans = 0
        for r in range(len(nums)):
            times[nums[r]] += 1
            while len(times) == allType and times[nums[l]] > 1:
                times[nums[l]] -= 1
                l += 1
            if len(times) == allType:
                ans += l + 1
        return ans
Java
/*
 * @Author: LetMeFly
 * @Date: 2025-04-24 22:47:48
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-04-24 23:18:36
 * @Description: AC,65.83%,85.83%
 */
import java.util.Set;
import java.util.Map;
import java.util.HashSet;
import java.util.HashMap;

class Solution {
    public int countCompleteSubarrays(int[] nums) {
        Set<Integer> se = new HashSet<>();
        for (int t : nums) {
            se.add(t);
        }
        int allType = se.size();
        Map<Integer, Integer> times = new HashMap<>();
        int ans = 0;
        int l = 0;
        for (int t : nums) {
            times.merge(t, 1, Integer::sum);
            while (times.size() == allType && times.get(nums[l]) > 1) {
                times.merge(nums[l++], -1, Integer::sum);
            }
            if (times.size() == allType) {
                ans += l + 1;
            }
        }
        return ans;
    }
}
Go
/*
 * @Author: LetMeFly
 * @Date: 2025-04-24 22:47:30
 * @LastEditors: LetMeFly.xyz
 * @LastEditTime: 2025-04-24 23:23:21
 * @Description: AC,32.53%,40.96%
 */
package main

func countCompleteSubarrays(nums []int) (ans int) {
	visited := map[int]bool{}
	for _, t := range nums {
		visited[t] = true
	}
	allType := len(visited)
	times := map[int]int{}
	l := 0
	for _, t := range nums {
		times[t]++
		for len(times) == allType && times[nums[l]] > 1 {
			times[nums[l]]--
			l++
		}
		if len(times) == allType {
			ans += l + 1
		}
	}
	return
}

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

### 滑动窗口与哈希算法的结合工作原理 滑动窗口是一种常用的优化技术,用于解决涉及连续子数组或子串的问题。它通过动态调整窗口大小来减少冗余计算,从而提高效率。当滑动窗口哈希表相结合时,通常能够高效处理一些需要快速查找、去重或统计频率的任务。 #### 1. **基本概念** 滑动窗口的核心在于维护一个动态区间 `[start, end]`,并通过移动两个指针 `start` 和 `end` 来遍历整个数据结构。而哈希表的作用则是辅助存储某些状态信息,比如元素的计数、是否存在某个特定值等。这种组合特别适合于以下场景: - 需要在一定范围内频繁查询某种属性(如唯一性、重复次数等)。 - 数据范围较大但可以通过局部特性推导全局最优解。 #### 2. **具体实现方式** 以下是基于滑动窗口哈希表的经典实现模式: ##### (1)初始化变量 定义必要的变量,包括但不限于窗口边界 (`left`, `right`)哈希表以及目标条件下的最佳结果记录器(如最大长度)。 例如,在求解水果成篮问题时[^3],我们需要跟踪当前窗口内的两种不同类型的水果数量及其分布情况。 ```java // 初始化部分 int[] record = new int[n + 1]; int start = 0; int countDistinctTypes = 0; // 当前种类数目 int maxLengthOfValidWindow = 0; for (int i = 0; i < n; ++i) { updateRecord(record, fruits[i]); // 更新哈希表 if (conditionMet()) { // 判断是否满足约束 adjustLeftBoundary(); // 调整左侧边界以保持合法性 } calculateResult(); // 根据当前窗口更新最终答案 } ``` ##### (2)扩展右端点 逐步增加右侧索引 `right` 并将其对应的数值加入到哈希表中。每次操作后都需要重新评估当前窗口的状态是否仍然有效。 ##### (3)收缩左端点 一旦发现当前窗口违反了题目设定的规则(如超过允许的不同字符类型),就需要逐渐向右平移左侧索引 `left` 直至恢复合法状态为止。 ##### (4)持续监控并保存最优解 在整个过程中不断比较各个阶段所形成的候选方案,并保留其中最理想的那一个作为最后的结果输出。 #### 3. **案例分析** 下面选取几个典型例子进一步阐述上述方法的应用过程: ###### A. LeetCode第1题两数之和[^2] 此问题是利用单次遍历配合哈希映射完成的一道经典入门级练习。其核心思路是在迭代每一个新读取的数据项之前先检查补全所需的另一半是否已经存在于之前的集合当中;如果是的话立即返回匹配位置即可结束程序运行。 ###### B. 最大连续'1's数量限制反转K个零位[^4] 这里采用双层循环形式模拟固定宽度变化趋势的同时兼顾内部细节控制逻辑设计。外层负责整体框架搭建,内嵌环节专注于局部修正动作执行直至达成预期效果为止。 --- ### 总结说明 综上所述,借助恰当的方式融合这两种强大工具不仅可以显著提升解决问题的速度而且还能增强代码可读性和简洁度。不过需要注意的是实际应用时应充分考虑各自适用前提条件以免造成资源浪费甚至错误结论得出等问题发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tisfy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值