2020-01-02 Longest Substring Without Repeating Characters

给你一个字符串,要求你找出这个字符串中长度最长的子串,返回其长度。要求这个子串不能有重复的字符出现。
Note: 这里是子串,不是子序列,所以必须是连续的

Think about SET when we trying to identify if sth has a repeated characters or sth like this.

Solution 1: Brute force --- Time Limited Exceeded

要求找最长子串,首先想到如何找到所有的子串。---- Two Nested Loop

然后在所有子串中去找allUnique的那一个。找到了,取其长度。继续遍历直到结束,其中一旦遇到更长的那就继续更新max。这里我们用一个helperfunction来帮助寻找最长的substring。So,考虑需要什么参数?---> str, start, end

如何判断是否unique? ----- Set不允许有重复值出现。
利用 set的 contains方法,包含则true,返回上面false.反之亦然。

还有一个值得考虑的问题: 假设我们找到了最长的那个substring,我们需要每次把他取出来对其取length()吗? 其实不需要。我们可以直接使用index作差求其长度。这也是我们引入一个helper function的原因之一。

Note: 关于出现的三个for loop 变量范围问题

这里的 n = s.length()的,而我们在取index时永远最多只能取到 n - 1。
1.Two nested loop: 要遍历产生所有子串,我们固定一个i,遍历其后所有的j。
显然,0=< i < n. i 取不到n是因为要留给 j 取n。 i + 1 =< j <= n。

  1. helper function 里,i是不可能取到end的。因为j取到了n,而我们最多取到n-1。但j必须要取n,因为他要给你传下来。j虽然取了n但是下面并不操作并不影响。
这里参数取值范围要多思考。
// tc = o(n^3)  sc = o(Math.min(m,n))       m is the size of charset/alphabet
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int ans = 0;
        int n = s.length();
        for(int i = 0; i < n; ++i){
            for(int j = i + 1; j <= n; ++j){
                if(allUnique(s, i, j))  ans = Math.max(ans, j - i);
            }
        }
        return ans;  
    }
     boolean allUnique(String s, int start, int end){
         Set set = new HashSet<>();
         for(int i = start; i < end; ++i){
             char ch = s.charAt(i);
             if(set.contains(ch))    return false;
             else set.add(ch);
        }
        return true;
    }
}

对于solution 1, 我们是找出每一个substring,都进行是否包含有重复元素的操作。但其实并不需要这样,这样太费时间了。

来,看问题:如果你要每一个找出来再进行判断,给你一个string ---> pwwkew.
p, pw, pww, pwwk, pwwke....
其实你判断pww已经包含有重复元素了,后面就不需要再判断了。
给出一种我最开始判断的方法,但是开始并不知道该如何来实现。看了blog之后知道这种方法叫做sliding window.

Solution 2: Sliding Window

image.png

错误做法:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int left = -1, right = 0;
        int ans = right - left;
        Set set = new HashSet<>();
        set.add(s.charAt(right));
        while(right < n){
            right++;
            if(set.contains(s.charAt(right))){
                left = right - 1;     //这里不应该给他直接减一,你应该准确适用HashMap去记录他的被重复元素的位置。
                                    //减一的情况是针对于kww这种,对于wkew就不对了。
            }
            ans = Math.max(ans,right - left);    
        }
        
        return ans;
    }
}

我们先固定一个left, assume left = 0。不断扩大right border,right border最多为string.length()。每次我们都需要同步去更新max值,也就是最后的ans。一旦发现这中间有重复元素,then we update left border to the current right border.直到我们的right border达到string最右边界。
那么如何判断有没有重复元素? HashSet.

Revise Solution:

  1. 本来上面想用hashSet结果发现需要使用sliding window里面被重复元素的position,显然我们需要使用映射记录这个key-value(element-position)的映射。

  2. right < n - 1。起初IndexOutOfBoundary,是因为right++在一开始。永远在满足条件时,他操作的是下一个值,那我们的范围也就应该限制在前一个值。如果我们给right < n,那么他就睡操作n,然而n已经越界了。拿实际数字举例子。

  3. rePos位置的确定。

  4. 在分析sc时,需要考虑不同字符集m的情况,比如字母集,ASCII集等。所以sc = Math.min(m,n)。

对code中rePos取法的说明
/**
 tc = O(n)  cause rigth will iterate from 0 to n - 1
 sc = O(Math.min(m,n))  空间上我们只使用了map。
 那么他最多存储26个字母或者另一个字符集的长度,或者就是我们字符串的长度。
 取最小值:如果是double 26个字母的一个字符串,那么我们肯定是取m;
         如果是abdcba,那么我们肯定是取n.当n足够小时,其实就是常数空间。
*/
class Solution {
    public int lengthOfLongestSubstring(String s) {
        //special case: 这是一个特殊的test case,开始怎么可以不过
        if(s.length() == 0) return 0;
        int n = s.length();
        int left = -1, right = 0;
        int ans = right - left;
        Map map = new HashMap<>();
        map.put(s.charAt(right), 0);
        while(right < n - 1){
            right++;
            if(map.containsKey(s.charAt(right))){
            /**
            每次当你遇到重复元素时,你更新left,你的left永远都是指向被重复元素。被重复元素可以通过s.charAt(right)来寻找,因为此时还没有put新的right entry,所以不影响。你这里get到的就是被重复元素的index。而原left应该是永远都在被重复元素的position之前,所以取max。
                e.g: pwwkew----- wkew
                     pwwkew----- pww
            left永远都是新的substring的前一个index,所以初始值取了-1.
            为什么不需要remove? 比如给出的第二个例子,不用担心取到第一个w的index吗?
            -----> 不需要。因为map每次put会覆盖。取到的永远都是最近的一个重复值,也就是在sliding window里面。
            */     
                int rePos = Math.max(map.get(s.charAt(right)), left);
                left = rePos;           
            }
            map.put(s.charAt(right), right);
            ans = Math.max(ans,right - left);    
        }
        return ans;
    }
}

你可能感兴趣的:(2020-01-02 Longest Substring Without Repeating Characters)