Leecode 03 无重复字符的最长子串

230 阅读3分钟

Leecode 03 无重复字符的最长子串

题目

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

示例 1:

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

示例 2:

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

示例 3:

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

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters

暴力解法

从前向后进行遍历,这个是自己第一反应能想到的,但是时间复杂度明显很高O(n3)。优化解法是参考了别人的解法才想到的。

public int lengthOfLongestSubstring1(String s) {
        int result = 0;
        for (int i = 0; i < s.length(); i++) {
            // 遍历子串的终止字符
            for (int j = i + 1; j < s.length(); j++) {
                if (allUnique(s, i,j)) {
                    result = Math.max(j - i, result);
                }
            }
        }
        return result;
    }

    private boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();
        for (int i = start; i < end; i++) {
            char c = s.charAt(i);
            if (set.contains(c)) {
                return false;
            } else {
                set.add(c);
            }
        }
        return true;
    }

滑动窗口

根据题目中的关键字:重复字符 -> 出现1次

  • 涉及到子串,考虑滑动窗口

通过使用HashSet作为一个滑动窗口,检查一个字符是否已经存在于现有的子字符中只需要O(1). 滑动窗口经常作为一个抽象的概念来处理数组/字符串问题。窗口代表着一组数据/字符串元素,通过开头和结尾的索引来定义窗口。

image-20200705213645229
image-20200705213645229
image-20200705213713618
image-20200705213713618
public int lengthOfLongestSubstring2(String s) {
        int result = 0;
        if (null == s) {
            return result;
        }
        int start = 0, end = 0;
        int len = s.length();
        HashSet<Character> set = new HashSet<>();
        while (end < len){
            if (!set.contains(s.charAt(end))) {
                set.add(s.charAt(end++));
                result = Math.max(result, end - start);
            }else {
                set.remove(s.charAt(start++));
            }
        }

        return result;
    }

该方法是使用Hashset保存窗口中的元素,最坏的情况下需要O(2n)

优化的滑动窗口算法

上面的滑动窗口算法最多需要2n的步骤,但这其实是能被优化为只需要n步。我们可以使用HashMap定义字符到索引之间的映射,然后,当我们发现子字符串中的重复字符时,可以直接跳过遍历过的字符了。

public int lengthOfLongestSubstring3(String s) {
        int result = 0;
        if (null == s) {
            return result;
        }
        HashMap<Character, Integer> map = new HashMap<>();
        for (int start=0, end = 0; end < s.length(); end++) {
            if (map.containsKey(s.charAt(end))) {
                // 当发现重复的字符时,将字符的索引与窗口的左边进行对比,将窗口的左边直接跳到该重复字符的索引处
                start = Math.max(map.get(s.charAt(end)), start);
            } else {
                //记录子字符串的最大的长度
                result = Math.max(result, end - start + 1);
                //map记录第一次遍历到key时的索引位置,j+1,保证i跳到不包含重复字母的位置
                map.put(s.charAt(end), end + 1);
            }
        }
        return result;
    }

本文使用 mdnice 排版