算法修炼之路—【字符串】Leetcode 3 无重复字符的最长子串

题目描述

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

示例1:

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

示例2:

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

示例3:

输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是“wke”,所以其长度为3.

说明:
请注意,你的答案必须是子串的长度,示例3中,"pwke"使用一个子序列,而不是子串

思路分析

我们通过题意可以理解是寻找无重复字符的最长子串,返回其长度。这里我们讲问题进行拆解:

  1. 无重复字符
  2. 最长子串

对于问题1 无重复字符,需要无重复字符,这里我们为寻求较好的时间复杂度不采取对每一个字符c在某范围内都进行搜索判重的方法。这里我们借助辅助容器来达到记录历史的目的,进而当遍历到后面的字符时,直接在辅助容器中查询是否包含即可。
这一题让我直接联想到得是计算机网络中TCP的滑动窗口,我们通过界定窗口的左边界和右边界来判断需要的数据区域,这里即为包括left不包括right的字符串均无重复字符。这里我们通过图示来说明:
在这里插入图片描述
图1

我们通过两个下标来确定滑动窗口的范围left, right,且为左闭右开;我们借助集合set来存储遍历历史,可知我们每个元素均为唯一。

  1. s.charAt(right)没有在set中时,先添加进set,且right++,即滑动窗口右扩展,重复步骤1
  2. set已经包括了s.charAt(right),则此时一定是left下标的字符为重复字符,滑动窗口左收缩,且更新leftleft++

接下来再看问题2 最长子串,我们这里需要返回的是这个最长子串的长度,即一个整型数据,我们这里可采用直接对比的方式,每次都更新为比较值得最大值即可:
maxSubStrLen = Math.max(maxSubStrLen , currLen)
此时可以得知解题步骤为:

核心操作

  1. s.charAt(right)没有在set中时,先添加进set,且right++,即滑动窗口右扩展,重复步骤1更新maxSubStrLen
  2. set已经包括了s.charAt(right),则此时一定是left下标的字符为重复字符,滑动窗口左收缩,且更新leftleft++

解题代码1

    public static int solution1(String s){
        if(s == null) return 0;
        
        int len = s.length();
        if(len == 1) return 1;
        
        /* Step 1: Init. integer and container.
        */
        int maxSubStrLen = 0, left = 0, right = 0;        
        Set<Character> set = new HashSet<>();
        
        /* Step 2: go-through string
        and
        check whether repeated in container to re-fine sliding window
        */
        while(left < len && right < len){
            
            if(!set.contains(s.charAt(right))){ // enlarge window on the right
                set.add(s.charAt(right++));
                maxSubStrLen = Math.max(maxSubStrLen, right-left);
            }else{ // shrink window from left
                set.remove(s.charAt(left++));
            }
        }
        
        
        return maxSubStrLen;
    }

复杂度分析

时间复杂度: 最坏情况下,需要对每个字符都访问两边,2N,故时间复杂度为O(N);
空间复杂度: 我们借助了辅助容器,且考虑字符集大小M的情况下,空间复杂度为O(min(M, N)).

进阶思考

solution1 中最坏情况(均为同一个字符),我们对每个字符都需要访问两遍,能否存在一种方法使得访问字符的次数减少。我们已知字符的可取范围是有限的,这里假定为M,则不妨在right界定右边界的情况下,判断并更新滑动窗口内的有且唯一的字符的下标,更新maxSubStrLen
在这里我们依然需要借助辅助容器,且还需要记录字符的下标,则可选取辅助容器为哈希表HashMap<char, index>, key为浏览过的字符,value为滑动窗口内(此时包含right在内)的相应字符的最新下标
则可总结为下:

核心操作

  1. map中包含s.charAt(right)时,更新重复字符left的最新下标;否则直接进入步骤2
  2. 更新maxSubStrLen,同时将s.charAt(right)添加入map;

解题代码2

    public static int solution2(String s){
        if(s == null) return 0;
        
        int len = s.length();
        if(len == 1) return 1;
        
        /* Step 1: Init. integer and container.
        */
        int maxSubStrLen = 0;        
        // <char, index>
        Map<Character, Integer> map = new HashMap<>();
        
        /* Step 2: go-through string
        and
        update existed index if checked.
        */
        for(int left=0, right=0; right < len; right++){
            if(map.containsKey(s.charAt(right))){
               left = Math.max(map.get(s.charAt(right)), left);
            }
            maxSubStrLen = Math.max(maxSubStrLen, right - left + 1);
            map.put(s.charAt(right), right + 1);
        }
        
        
        return maxSubStrLen;
    }

复杂度分析

时间复杂度: 这里我们对数据进行了一次遍历,故时间复杂度为O(N);
空间复杂度: 我们借助了辅助容器,同solution1一样,为O(min(M, N))

Github源码

完整可运行文件请访问GitHub

发布了53 篇原创文章 · 获赞 59 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u011106767/article/details/105651957