最长连续不重复子序列

115 阅读4分钟

最长连续不重复子序列

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

所用方法和基本原理

  1. 滑动窗口原理
    • 代码采用滑动窗口的方法来解决此问题。滑动窗口是一种在数组或字符串上使用双指针技术的算法模式。
    • 定义两个指针 iji 作为右指针,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 + 1res 的较大值。

代码及注释

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]

  1. 初始化n = 5res = 0i = 0j = 0s 数组初始值全为 0。
  2. 第一轮循环
    • i = 0arr[0] = 2s[2]++,此时 s[2] = 1
    • 因为 s[2] <= 1,不进入 while 循环。
    • res = Math.max(0, 0 - 0 + 1) = 1
  3. 第二轮循环
    • i = 1arr[1] = 3s[3]++,此时 s[2] = 1s[3] = 1
    • 因为 s[3] <= 1,不进入 while 循环。
    • res = Math.max(1, 1 - 0 + 1) = 2
  4. 第三轮循环
    • i = 2arr[2] = 2s[2]++,此时 s[2] = 2s[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
  5. 第四轮循环
    • i = 3arr[3] = 4s[4]++,此时 s[2] = 1s[3] = 1s[4] = 1
    • 因为 s[4] <= 1,不进入 while 循环。
    • res = Math.max(2, 3 - 1 + 1) = 3
  6. 第五轮循环
    • i = 4arr[4] = 5s[5]++,此时 s[2] = 1s[3] = 1s[4] = 1s[5] = 1
    • 因为 s[5] <= 1,不进入 while 循环。
    • res = Math.max(3, 4 - 1 + 1) = 4

所以,最长不包含重复数字的连续区间长度为 4。

方法的优劣

  1. 时间复杂度
    • 外层 for 循环遍历数组一次,时间复杂度为 (O(n)),其中 n 是数组的长度。
    • 内层 while 循环在每次外层循环时,最多执行 n 次(极端情况下,如数组中所有数字都相同),但整体来看,每个元素最多进、出窗口各一次,所以内层 while 循环的总时间复杂度也是 (O(n))。
    • 因此,总的时间复杂度为 (O(n))。
  2. 空间复杂度
    • 使用了一个额外的数组 s 来记录数字出现的次数,数组的大小取决于数据范围(这里是 100000 + 100),空间复杂度为 (O(m)),其中 m 是数据范围。如果数据范围相对 n 较小,可以近似认为空间复杂度为 (O(n))。

优点: - 时间复杂度低,能够高效地解决问题,适用于处理大规模数据。 - 实现相对简单,利用滑动窗口的思想,逻辑清晰易懂。

缺点: - 空间复杂度取决于数据范围,如果数据范围很大,会占用较多的内存空间。例如,当数据范围远大于数组长度 n 时,空间消耗会比较大。 - 对于数据范围不确定且可能非常大的情况,使用数组来记录数字出现次数可能不太合适,需要考虑其他更优化的存储结构(如哈希表)来降低空间复杂度。