LeetCode · 无重复字符的最长子串

91 阅读1分钟

这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

题目描述

英文版描述

Given a string s, find the length of the longest substring without repeating characters.

中文版描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、数字、符号和空格组成

解题思路

由于不确定排在前面的字符串是不是最长的,需要定义一个变量用于存储当前遍历到的符合题目要求的最长的字符串长度,每遇到一次重复的字符,就把当前遍历到的字符串长度与之前长度的最大值做比较,小于则不做操作继续遍历,大于则替换后再继续遍历,直到所有字符遍历结束,返回该值即可。

解题方法

俺这版

第一版用的Map,个人觉得逻辑还是很清楚的,BUT超时了(╥﹏╥),于是把数据结构改为了队列,通过(。・ω・。)ノ

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int sum = 0;
        int count = 0;
        Queue<Character> queue = new LinkedList<>();
        for (int i = 0; i < s.length(); i++) {
            char x = s.charAt(i);
            if (!queue.contains(x)) {
                count++;
                queue.add(x);
            } else {
                if (count > sum) {
                    sum = count;
                }
                while (queue.contains(x)) {
                    queue.remove();
                }
                count = queue.size();
                i = i - 1;
            }
        }
        if (count > sum) {
            sum = count;
        }
        return sum;
    }
    
}

复杂度分析

  • 时间复杂度:O(n),会遍历字符串一遍
  • 空间复杂度:O(n+2) = O(n),n为字符串长度

官方版

方法一:滑动窗口

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Set<Character> occ = new HashSet<Character>();
        int n = s.length();
        int rk = -1, ans = 0;
        for (int i = 0; i < n; ++i) {
            if (i != 0) {
                occ.remove(s.charAt(i - 1));
            }
            while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) {
                // 不断地移动右指针
                occ.add(s.charAt(rk + 1));
                ++rk;
            }
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }
}
复杂度分析
  • 时间复杂度:O(N),其中 N 是字符串的长度。左指针和右指针分别会遍历整个字符串一次。
  • 空间复杂度:O(∣Σ∣),其中 Σ表示字符集(即字符串中可以出现的字符),∣Σ∣表示字符集的大小。在本题中没有明确说明字符集,因此可以默认为所有 ASCII 码在 [0,128)内的字符,即 ∣Σ∣=128。我们需要用到哈希集合来存储出现过的字符,而字符最多有 ∣Σ∣个,因此空间复杂度为 O(∣Σ∣)。

总结

虽然思路基本一致,但是采用哈希集合结合指针的方法的执行速度跟内存占用都小于直接使用队列。讨论区有一个名字是coderfan的同学提供了一个人觉得很棒方法,他的解释是while循环发现的重复字符不一定就是Set最早添加那个,还要好多次循环才能到达,这些都是无效循环,不如直接用map记下每个字符的索引,直接进行跳转,执行结果比官方的还赞,记录学习下:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character, Integer> map = new HashMap<>();
        int max = 0, start = 0;
        for (int end = 0; end < s.length(); end++) {
            char ch = s.charAt(end);
            if (map.containsKey(ch)){
                start = Math.max(map.get(ch)+1,start);
            }
            max = Math.max(max,end - start + 1);
            map.put(ch,end);
        }
        return max;
    }
}

我在一开始也是使用的Map,但是执行超时叻=[,,_,,]:3,我当时只想到了如何把不需要计算的元素移出去,并没有考虑到可以只计算数量而不做实际上的元素移除,再总结下解题步骤:

  1. 确定算法;
  2. 确定数据结构;
  3. 是否有重复过程可以被优化