• LeetCode Hot100之八: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 * 104

    思路

    如果考虑暴力枚举左边界和子串长度,那么我们发现:如果我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第 k个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为rk。那么当我们选择第 k+1 个字符作为起始位置时,首先从k+1 到 rk 的字符显然是不重复的,并且由于少了原本的第 k 个字符,我们可以尝试继续增大 rk,直到右侧出现了重复字符为止。这样一来,我们就可以使用滑动窗口来解决这个问题了。

    代码

    第一版

    来源于官方

    class Solution {
        public int lengthOfLongestSubstring(String s) {
            // 为什么滑动窗口可以枚举到最大长度
            //因为当其开始滑动的时候,说明此时滑动窗口所在的索引对应的情况的最大长度已经找到了。
            //而且滑动窗口一定会滑动到最右侧。其实就是暴力循环,循环长度+起点索引。但是滑动窗口会更形象点
    
            // 哈希集合,记录每个字符是否出现过
            Set<Character> occ = new HashSet<Character>();
            int n = s.length();
            // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
            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;
                }
                // 第 i 到 rk 个字符是一个极长的无重复字符子串
                ans = Math.max(ans, rk - i + 1);
            }
            return ans;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    效率分析

    6ms,击败54.56%使用 Java 的用户。很明显还需要优化。

    因为左指针每次只向右移动一格。但是实际上右指针下一位遍历到的和原有子串中重复的字符,可能不是出现在左指针的开头,可能是字串的中右边。那么左指针需要向右移动好多次才能遍历到这个重复的字符。也就是说我们需要另一个数据结构来记录重复字符在原有子串中的下标索引,然后直接进行跳转。

    第二版

    来源于高赞题解

    class Solution {
        public int lengthOfLongestSubstring(String s) {
            if (s.length()==0) return 0;
            HashMap<Character, Integer> map = new HashMap<Character, Integer>();
            int max = 0;
            int left = 0;
            for(int i = 0; i < s.length(); i ++){
                if(map.containsKey(s.charAt(i))){
                    left = Math.max(left,map.get(s.charAt(i)) + 1);
                    /* 之所以left需要用max是因为我们需要保证left存储的是遍历到的重复字符在原子串中最后一次出现的下标,也就是最右边的一个下标。如果遇上abba这样的子串。当遍历到第二个b时,map由{'a':0}变为{'a':0, 'b':1},left由0变为2(b的1+1)。子串由ab更新为第二个b本身。当遍历到第二个a时,如果单纯是left = map.get(s.charAt(i))+1,那么left = map.get('a')+1 = 1。反而倒退了。这是因为遇到的重复字符可能在上一次遇到的重复字符的左边。然而实际上我们的左边界遍历只需要保持递增就好了,不需要回退。那么left就需要使用max来进行比较现在应该跳转到的新边界是否在上次遍历的边界右边。
    */
                }
                map.put(s.charAt(i),i);
                max = Math.max(max,i-left+1);
            }
            return max;
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    效率分析

    4ms,击败89.97%使用 Java 的用户。没必要再继续优化了。

  • 相关阅读:
    AIE荧光分子杂化介孔二氧化硅杂化纳米微球/聚合诱导微米级多孔SiO2微球
    table的宽高适配方法
    一体化研发协作赋能平台:Apipost
    基于余弦相似度改进蝴蝶优化算法-附代码
    浅析前端单元测试
    锅炉智能制造工厂工业物联数字孪生平台,推进制造业数字化转型
    金色传说:SAP-ME-IDOC:使用基本类型LOIPRO01向ME发送生产订单,IDOC状态30问题解决
    【Godot】解决游戏中的孤立/孤儿节点及分析器性能问题的分析处理
    可root设备复制文件到system目录或者子目录下
    通过转义字符获取标签内的文字js通过转义字符获取字符串标签内的文字||获取字符串标签内的文字并加校验是否修改
  • 原文地址:https://blog.csdn.net/zhiaidaidai/article/details/134452670