3. 无重复字符的最长子串

给定一个字符串 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 由英文字母、数字、符号和空格组成

# V1-基本方法

## 思路

容易想到的就是两层遍历。

i 从 0 开始,j 从 i 及后面的位置开始,判断 s[i,j] 对应的子串 s1 不存在重复字符的最大长度。

是否存在重复字符的方法,可以通过 Set 集合。

## java 实现

```java
    /**
     * 获取最长字符的长度
     * @date 2020-6-9 16:08:57
     * @param s 字符串
     * @return 结果
     */
    public int lengthOfLongestSubstring(String s) {
        int maxLength = 0;

        // 元素开始的下标
        for(int i = 0; i < s.length(); i++) {

            // 循环
            Set<Character> characters = new HashSet<>();
            for(int j = i; j < s.length(); j++) {
                char currentChar = s.charAt(j);

                // 如果元素重复
                if(characters.contains(currentChar)) {
                    maxLength = Math.max(characters.size(), maxLength);
                    // 跳过当前循环
                    break;
                }

                // 正常添加元素
                characters.add(s.charAt(j));

                // 如果已经走到了最后一位
                // 说明是最长的一位
                if(j == (s.length()-1)) {
                    return Math.max(maxLength, characters.size());
                }
            }
        }

        return maxLength;
    }

效果

效果如下

Runtime: 121 ms, faster than 15.97% of Java online submissions for Longest Substring Without Repeating Characters.
Memory Usage: 40.3 MB, less than 100% of Java online submissions for Longest Substring Without Repeating Characters.

V2-滑动窗口

思路

优化思想:最外层 i 的自增是可以优化的。

i 在遍历的时候,直接走到最新的位置。并且保存上一个最大的距离。

abcdee
abcdaefgi
[i, j]
j-i+1 获取最长字串的长度。
i 初始位置遍历
j 当前 char 位置

java 实现

我们引入 indexMap 记录一个字符和其对应的 char 位置 + 1。

如果发现 indexMap 已经包含了当前的 char,那么直接让 i = Math.max(i, indexMap.get(currentChar));。这样才能保证在 [i, j] 中没有重复的字符。

那么最大的长度就是:maxLength = Math.max(maxLength, j-i+1);

    public int lengthOfLongestSubstring(String s) {
        int maxLength = 0;

        final int length = s.length();
        Map<Character, Integer> indexMap = new HashMap<>(length);
        for(int i = 0,j = 0; j < length; j++) {
            char currentChar = s.charAt(j);
            if(indexMap.containsKey(currentChar)) {
                // 重复,直接更新 i 的位置到最大的 max(i, j+1)
                // 这里的 indexMap.get(currentChar) 获取到的是上一次出现该元素的位置+1
                // 这里到底快了多少呢?
                i = Math.max(i, indexMap.get(currentChar));
            }

            maxLength = Math.max(maxLength, j-i+1);
            // 对应的元素位置,是下一个
            indexMap.put(currentChar, j+1);
        }

        return maxLength;
    }

效果

时间复杂度直接从 O(N^2) 优化到了 O(N)。

效果如下

Runtime: 5 ms, faster than 92.58% of Java online submissions for Longest Substring Without Repeating Characters.
Memory Usage: 39.3 MB, less than 100% of Java online submissions for Longest Substring Without Repeating Characters.

那么,还可以进一步优化吗?

V3-内存优化

思路

在字符类的题目中,我们使用 hashmap 保存字符和位置的关系。

因为 hashmap 的操作都是 O(1) 的复杂度。

那么,有更加轻量的方式,让代码看起来更加优雅吗?

答案就是数组,直接使用 int 数组,让 char 转换为 int,设置对应的 value 即可。

这个在后续的算法题中,会经常见到。

java 实现

    /**
     * 假设字符是固定的
     *
     * 这里的原理就是把 char 当做数组的下标,访问时为 O(1)
     * 优点:快速 && 节约内存
     *
     * 其他的和上面的方法一样。
     *
     * 
     * @param s 字符串
     * @return 结果
     * @since v3
     */
    public int lengthOfLongestSubstring(String s) {
        int[] chars = new int[128];
        int maxLength = 0;

        for(int i = 0,j = 0; j < s.length(); j++) {
            char currentChar = s.charAt(j);

            // 有值的情况
            i = Math.max(chars[currentChar], i);

            // 这里临时值的计算优化基本为 0
            int temp = j+1;
            maxLength = Math.max(maxLength, temp-i);
            chars[currentChar] = temp;
        }

        return maxLength;
    }

效果

效果如下

Runtime: 2 ms, faster than 99.95% of Java online submissions for Longest Substring Without Repeating Characters.
Memory Usage: 39.4 MB, less than 100% of Java online submissions for Longest Substring Without Repeating Characters.

小结

我们日常工作中,基本是采用第一种方式居多。

不过要善于思考,体验一下第二种方式和第三种的妙处。

进一寸有进一寸的欢喜。

开源地址

为了便于大家学习,所有实现均已开源。欢迎 fork + star~

https://github.com/houbb/leetcode

参考资料

https://leetcode.cn/problems/add-two-numbers/