最长连续不重复子序列
给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
所用方法和基本原理
- 滑动窗口原理:
- 代码采用滑动窗口的方法来解决此问题。滑动窗口是一种在数组或字符串上使用双指针技术的算法模式。
- 定义两个指针
i和j,i作为右指针,j作为左指针,它们之间的区间就是当前正在考虑的窗口。 - 使用数组
s来记录每个数字出现的次数。s[arr[i]]表示数字arr[i]在当前窗口内出现的次数。 - 右指针
i逐步向右移动,每移动一次,将arr[i]出现的次数加 1(即s[arr[i]]++)。如果发现arr[i]在当前窗口内出现的次数大于 1,说明窗口内有重复数字,此时通过移动左指针j来缩小窗口,同时将arr[j]出现的次数减 1(即s[arr[j]]--),直到窗口内不再有重复数字(即s[arr[i]] <= 1)。 - 在每次移动指针后,更新最长不重复子区间的长度
res,取当前窗口长度i - j + 1与res的较大值。
代码及注释
public class LongestSubstringWithoutRepeatingChars {
// 用于记录每个数字出现次数的数组,这里预留了一定的额外空间
public static int[] s = new int[100000 + 100];
// 计算最长不包含重复数字的子区间长度
public static int lengthOfLongestSubstring(int[] arr) {
// 序列的长度
int n = arr.length;
// 用于存储最长不重复子区间的长度
int res = 0;
// 双指针遍历数组,i 为右指针,j 为左指针
for (int i = 0, j = 0; i < n; i++) {
// 增加当前右指针指向数字的出现次数
s[arr[i]]++;
// 如果当前数字出现次数大于1,说明有重复,移动左指针缩小窗口
while (s[arr[i]] > 1) {
s[arr[j]]--;
j++;
}
// 更新最长不重复子区间的长度
res = Math.max(res, i - j + 1);
}
return res;
}
}
举例说明
假设给定整数序列 arr = [2, 3, 2, 4, 5]。
- 初始化:
n = 5,res = 0,i = 0,j = 0,s数组初始值全为 0。 - 第一轮循环:
i = 0,arr[0] = 2,s[2]++,此时s[2] = 1。- 因为
s[2] <= 1,不进入while循环。 res = Math.max(0, 0 - 0 + 1) = 1。
- 第二轮循环:
i = 1,arr[1] = 3,s[3]++,此时s[2] = 1,s[3] = 1。- 因为
s[3] <= 1,不进入while循环。 res = Math.max(1, 1 - 0 + 1) = 2。
- 第三轮循环:
i = 2,arr[2] = 2,s[2]++,此时s[2] = 2,s[3] = 1。- 因为
s[2] > 1,进入while循环:s[arr[j]]即s[2]--,s[2]变为 1。j++,j变为 1。
res = Math.max(2, 2 - 1 + 1) = 2。
- 第四轮循环:
i = 3,arr[3] = 4,s[4]++,此时s[2] = 1,s[3] = 1,s[4] = 1。- 因为
s[4] <= 1,不进入while循环。 res = Math.max(2, 3 - 1 + 1) = 3。
- 第五轮循环:
i = 4,arr[4] = 5,s[5]++,此时s[2] = 1,s[3] = 1,s[4] = 1,s[5] = 1。- 因为
s[5] <= 1,不进入while循环。 res = Math.max(3, 4 - 1 + 1) = 4。
所以,最长不包含重复数字的连续区间长度为 4。
方法的优劣
- 时间复杂度:
- 外层
for循环遍历数组一次,时间复杂度为 (O(n)),其中n是数组的长度。 - 内层
while循环在每次外层循环时,最多执行n次(极端情况下,如数组中所有数字都相同),但整体来看,每个元素最多进、出窗口各一次,所以内层while循环的总时间复杂度也是 (O(n))。 - 因此,总的时间复杂度为 (O(n))。
- 外层
- 空间复杂度:
- 使用了一个额外的数组
s来记录数字出现的次数,数组的大小取决于数据范围(这里是100000 + 100),空间复杂度为 (O(m)),其中m是数据范围。如果数据范围相对n较小,可以近似认为空间复杂度为 (O(n))。
- 使用了一个额外的数组
优点: - 时间复杂度低,能够高效地解决问题,适用于处理大规模数据。 - 实现相对简单,利用滑动窗口的思想,逻辑清晰易懂。
缺点:
- 空间复杂度取决于数据范围,如果数据范围很大,会占用较多的内存空间。例如,当数据范围远大于数组长度 n 时,空间消耗会比较大。
- 对于数据范围不确定且可能非常大的情况,使用数组来记录数字出现次数可能不太合适,需要考虑其他更优化的存储结构(如哈希表)来降低空间复杂度。