最长无重复子数组
题目描述
给定一个长度为 n
的数组 arr
,返回 arr
的最长无重复元素子数组的长度。无重复指的是所有数字都不相同。
子数组定义:子数组是连续的,比如 [1,3,5,7,9]
的子数组有 [1,3]
、[3,5,7]
等,但 [1,3,7]
不是子数组。
数据范围:
- 数组长度 (0 \leq \text{arr.length} \leq 10^5)
- 数组元素 (0 < \text{arr}[i] \leq 10^5)
示例
示例 1
输入:
[2,3,4,5]
输出:
4
说明:
[2,3,4,5]
是最长子数组。
示例 2
输入:
[2,2,3,4,3]
输出:
3
说明:
[2,3,4]
是最长子数组。
示例 3
输入:
[9]
输出:
1
示例 4
输入:
[1,2,3,1,2,3,2,2]
输出:
3
说明:
最长子数组为 [1,2,3]
。
示例 5
输入:
[2,2,3,4,8,99,3]
输出:
5
说明:
最长子数组为 [2,3,4,8,99]
。
解题思路
滑动窗口 + 哈希表
-
滑动窗口:
- 使用两个指针
left
和right
表示当前窗口的左右边界。 right
指针向右移动,扩展窗口。- 如果遇到重复元素,
left
指针向右移动,缩小窗口。
- 使用两个指针
-
哈希表:
- 使用哈希表记录当前窗口中每个元素是否出现过。
- 如果
arr[right]
已经存在于哈希表中,说明出现了重复元素,需要移动left
指针。
-
更新最大长度:
- 每次窗口扩展时,更新最大无重复子数组的长度。
代码实现
#include <stdio.h>
#include <stdlib.h>
int maxLength(int* arr, int arrSize) {
if (arrSize == 0) return 0; // 如果数组为空,直接返回0
// 使用哈希表记录元素是否出现过
int hash[100001] = {0}; // 哈希表大小根据题目数据范围设置
int left = 0, right = 0; // 左右指针
int max_len = 0; // 记录最长无重复子数组的长度
while (right < arrSize) {
if (hash[arr[right]] == 0) {
// 如果当前元素没有出现过,加入哈希表
hash[arr[right]] = 1;
max_len = (right - left + 1 > max_len) ? (right - left + 1) : max_len;
right++;
} else {
// 如果当前元素已经出现过,移动左指针直到没有重复元素
while (left < right && hash[arr[right]] != 0) {
hash[arr[left]] = 0;
left++;
}
}
}
return max_len;
}
代码解析
1. 哈希表
- 使用数组
hash
作为哈希表,记录当前窗口中每个元素是否出现过。 - 数组大小为
100001
,因为题目中数组元素的最大值为 (10^5)。
2. 滑动窗口
left
指针:表示窗口的左边界。right
指针:表示窗口的右边界。- 窗口扩展:如果当前元素
arr[right]
没有出现过,扩展窗口。 - 窗口收缩:如果当前元素
arr[right]
已经出现过,移动left
指针直到没有重复元素。
3. 更新最大长度
- 每次窗口扩展时,计算当前窗口的长度
right - left + 1
,并更新max_len
。
为什么while (left < right && hash[arr[right]] != 0)left
不会一直增加到right
?
代码片段
while (left < right && hash[arr[right]] != 0) {
hash[arr[left]] = 0;
left++;
}
目的
这段代码的目的是:当右指针right
指向的元素已经在当前子数组中出现过时,移动左指针left
,直到当前子数组中不再包含重复的arr[right]
为止。
-
hash[arr[right]] != 0
的条件:- 这个条件确保了只有当
arr[right]
已经被包含在当前子数组中时,才会进入循环。 - 一旦
hash[arr[right]]
被置为0
(即arr[right]
被移出子数组),循环就会停止。
- 这个条件确保了只有当
-
循环的行为:
- 每次循环中,
left
向右移动一位,同时将hash[arr[left]]
置为0
,表示移除arr[left]
。 - 这个过程会一直进行,直到
hash[arr[right]]
变为0
,即arr[right]
被移出子数组。
- 每次循环中,
-
循环的终止条件:
- 当
hash[arr[right]]
变为0
时,循环停止,此时arr[right]
不再在子数组中。 - 因此,
left
不会一直增加到right
,而是会在移除重复元素后停止。
- 当
举例说明
假设数组为[2, 3, 4, 2]
,left = 0
,right = 3
:
- 初始时,
hash
数组记录了[2, 3, 4]
,hash[2] = 1
,hash[3] = 1
,hash[4] = 1
。 - 当
right = 3
时,arr[right] = 2
,而hash[2] = 1
,说明2
已经在子数组中。 - 进入循环:
left = 0
,hash[arr[left]] = hash[2] = 1
,将其置为0
,移除2
。left++
,此时left = 1
。- 再次检查
hash[arr[right]] = hash[2]
,此时hash[2]
已经被置为0
,循环停止。
总结
left
不会一直增加到right
,因为循环的终止条件是hash[arr[right]] != 0
。- 一旦
arr[right]
被移出子数组,hash[arr[right]]
会变为0
,循环就会停止。 - 这段代码的目的是确保在将
arr[right]
加入子数组之前,移除所有导致重复的元素。
复杂度分析
- 时间复杂度:(O(n)),其中 (n) 是数组的长度。每个元素最多被访问两次(一次由
right
指针,一次由left
指针)。 - 空间复杂度:(O(1)),哈希表的大小固定为 (10^5),与输入规模无关。
总结
方法 | 时间复杂度 | 空间复杂度 | 核心思想 |
---|---|---|---|
滑动窗口 + 哈希表 | (O(n)) | (O(1)) | 通过滑动窗口维护无重复子数组 |
标签:滑动窗口、哈希表、数组