LeetCode | 3. Longest Substring Without Repeating Characters

题目

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

Example 1:

Input: "abcabcbb"
Output: 3
Explanation: The answer is "abc", with the length of 3.

Example 2:

Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.

Example 3:

Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.

Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

 

 

题目大意呢就是说,给定一个字符串,求最大子串长度,该子串满足:没有重复的字符。

 

例如,第一个例子中,字符串“abcabcbb”,其最长无重复字符子串为“abc”、“bca”“cab”......  它们的长度为均为 3,所以结果为 3。

第二个例子“bbbbb”,很显然,结果应该是 1。

第三个例子“pwwkew”,最长无重复字符子串为“wke”、“kew”,长度为 3。

 

题解

(* 使用 Math.max 函数比使用条件表达式快,因为一开始我都是用的条件表达式取较大值,后来经测试,Math.max 确实要更快一点 *)

1. 暴力法

首先最容易想到的就是暴力求解(菜鸡我就是),也没什么思路好讲的。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = 0; // 最终的结果

        HashMap<Character, Integer> map = new HashMap<>(); // 用以判断某一个字符是否出现过
        
        for(int i = 0 ; i < s.length() ; i++){
            int l = 0; // l记录当前循环的长度,每一次外部循环都是一次新的求解,需要将 l 设为0
            map.clear(); // 将 map 清空
          //  System.out.println(map.size());
            for(int j = i ; j < s.length() ; j++){
                if(map.containsKey(s.charAt(j))){
                    break;
                }else {
                    map.put(s.charAt(j), 1);
                    l++;
                }
            }
            Math.max(l, len);
        }

        return len;
    }
}

暴力求解始终是最好理解的,但是性能嘛......

 

还有一个不使用 HashMap 的解法,但是性能似乎更差了......

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = 0; // 最终的结果

        for(int i = 0 ; i < s.length() ; i++){
            int l = 1; // l记录当前循环的长度,每一次外部循环都是一次新的求解,需要将 l 设为0

            for(int j = i+1 ; j < s.length() ; j++){
                if(s.substring(i,j).contains(String.valueOf(s.charAt(j)))){
                    break;
                }else {
                    l++;
                }
            }
            Math.max(l, len);
        }

        return len;
    }
}

 

 

2. 滑动窗口(Sliding Window)

滑动窗口的思路是这样的:

确定一个左边界(left)和一个右边界(right),分别作为窗口的左右边界,给一个 HashSet 用来存放当前 window 中的字符。初始状态时,left 和 right 都设为0,然后试着将 right 逐步增加,当 set 不包含当前(right)字符时,则将当前字符放进 set,即是说将滑动窗口向右扩展一格,如果 set 中已经包含了当前(right)字符,则该字符只可能与窗口中最左边的字符相同,因此只需将最左边的字符(left)移除,left++ 即可。每循环一次,就对 len 做一次更新。直到 left 或者 right 等于 s.length() 退出循环。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = 0;

        int left = 0 ,right = 0;
        HashSet<Character> set = new HashSet<>(); // 用以记录当前 window 中所包含的字符

        while(left < s.length() && right < s.length()){
            if (!set.contains(s.charAt(right))){
                set.add(s.charAt(right++)); // 将 window 右边界向右扩展一格
            }else {
                set.remove(s.charAt(left++)); // 将 window 中最左边的一格剔除
            }

            len = Math.max(len, right - left);
        }

        return len;
    }
}

 

3. 优化滑动窗口(Sliding Window Optimized)

上一个方法的时间复杂度为O(2n),实际上,还可以将其优化到O(n)。

优化滑动窗口法的思路是这样的:

可以定义一个字符到其索引的映射,而不是使用一个 Set 来判断字符是否存在。然后我们可以在找到重复的字符时立即跳过一定个数的字符。因为如果s[j]在s[i, j)中有一个重复的字符s[j'](也就是说 s[j] == s[j']),我们就不需要一个一个地增加 i 的值,而是可以一次跳过 s[i, j'] 并令 i = j' + 1。

为了实现上述目标,需要一个 HashMap<Character, Integer>,该 map 用于存放的是某一字符在字符串中的位置,key 为字符,value 为其出现的位置,注意,这里所说的字符位置是指:位于当前字符之前的字符串中某一字符最后一次出现的位置,emmmmm 我说的好像有点绕,举个例子吧:假设字符串为 “abcabcbc”,此时处理到了倒数第二个字符(b),此时 map.get('c') 的值应该是 5,而不是 2,更不可能是 7 啦!

我们只需要 O(n) 的时间就可以解决这个问题啦,也就是一次循环!在循环中,我们首先判断 map 中是否已经存在了当前字符,如果存在,就更新 left(上面说到的 i),注意 left 的更新式子,一开始我犯了个错,直接用 left = map.get(s.charAt(right)) + 1 对 left更新,这样做是不对的,因为 left 只可能越来越大,而上面那个错误的式子可能会使 left 变小,例如:当s = "abba" ,当 right = 3 时,首先判断 map 是否包含 key 'a',肯定是包含的呀,并且 map.get('a') 是为 0 的,而用上面的式子更新 left 后,left 变为 map.map.get('a') + 1 = 1,而在上一循环 right 为 2 时,已经将 left 更新为 2,left 居然还回退了?!这显然是不合理的,所以正确的更新式子是:

left = Math.max(left, map.get(s.charAt(right)) + 1);

class Solution {
    int lengthOfLongestSubstring(String s) {
        int len = 0;

        HashMap<Character, Integer> map = new HashMap<>(); // 存放指定字符的位置

        int left = 0;
        for (int right = 0 ; right < s.length() ; right++){
            if (map.containsKey(s.charAt(right))){
                left = Math.max(map.get(s.charAt(right)) + 1, left); // left 只能变大,不能变小(可能会出现 (map.get(s.charAt(right)) + 1) 更小的情况)
            }
            map.put(s.charAt(right), right);
            len = Math.max(len, right - left + 1);
        }

        return len;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值