Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

https://oj.leetcode.com/problems/longest-substring-without-repeating-characters/

Given a string, find the length of the longest substring without repeating characters. For example, the longest substring without repeating letters for "abcabcbb" is "abc", which the length is 3. For "bbbbb" the longest substring is "b", with the length of 1.

解题思路:

这种题目用暴力遍历显然不可。因为往后遍历的最长长度,取决于前面的情况,考虑用动态规划的方法求解。动态规划问题的关键是建立每个状态的模型,建立一个数学中的递推公式。

与maxlength subarray的问题类似,这里考虑以i - 1位置结尾的没有重复字符的最长字符为S[i-1],长度为f(i - 1),i处的字符为C[i],我们来讨论f(i)的大小。

如果S[i - 1]里没有i处的字符,显然f(i) = f(i - 1) + 1。如果S[i - 1]里有i处的字符,那么必然只有一个,所以f(i) = 从S[i-1]内i字符后面开始算起,算到i为止。代码如下。仅仅遍历一遍,在O(n)的时间内就可以解决问题了。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int maxLength = 0;
        int start = 0;
        for(int i = 0; i < s.length(); i++){
           // length = i - start;
            if(s.substring(start, i).indexOf(s.charAt(i)) == -1){
                maxLength = Math.max(maxLength, i - start + 1);    
            //}else if(s.substring(start, i).indexOf(s.charAt(i)) == 0){
                //maxLength不变
              //  start++;
            }else {
                start = start + s.substring(start, i).indexOf(s.charAt(i)) + 1;
                //start = i;
            }
        }
        return maxLength;
    }
}

事实上,S[i - 1]内有i处字符的情况也可以计算长度。代码如下。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int maxLength = 0;
        int start = 0;
        for(int i = 0; i < s.length(); i++){
           // length = i - start;
            if(s.substring(start, i).indexOf(s.charAt(i)) == -1){
                //start不变
                maxLength = Math.max(maxLength, i - start + 1);    
            //}else if(s.substring(start, i).indexOf(s.charAt(i)) == 0){
                //maxLength不变
              //  start++;
            }else {
                start = start + s.substring(start, i).indexOf(s.charAt(i)) + 1;
                maxLength = Math.max(maxLength, i - start + 1); 
                //start = i;
            }
        }
        return maxLength;
    }
}

事实上,上述两个if分支可以合并,start的坐标计算不管i有没有在S[i - 1]内出现,都能得到计算。于是,代码进一步精简如下。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int maxLength = 0;
        int start = 0;
        for(int i = 0; i < s.length(); i++){
            start = start + s.substring(start, i).indexOf(s.charAt(i)) + 1;
            maxLength = Math.max(maxLength, i - start + 1); 
        }
        return maxLength;
    }
}

至此,该dp算法可以表述为,dp[i]等于S[i - 1]里面,从与C[i]相同的后一个字符算起,到C[i]的长度。C[i]为i处的字符。如果S[i - 1]内没有C[i],显然应该+1的。事实上indexOf(C[i]) = -1,所以后一个字符就是从0开始算,即+1。最后再计算所有dp[i]的最大值,即为maxLength。

还要注意的是,这里不可取不同情况下的max。即,不能出现一个在S[i - 1]内已经有的C[i],就将整个S[i - 1]丢弃,从C[i]开始,不可!因为这与选硬币之类的dp题不同,不是构造一个最优解(平行的选择),这里是给定一个数列,当前情况下的dp[i]是固定的,不可取max。

这里还可以用一个HashMap记录所有字符出现的位置,然后再用map.contains判断是不是有这个字符,其实和indexof是一样的。

update 2015/03/17

更新了一个所谓sliding window的方法。这个方法在很多题目中都会用到,虽然内核和上面的解法差不多,但是方法更为规范和固定。而且,只要O(n)的时间,上题看似也只要O(n),但是indexOf的方法还是会循环,花费O(n)的时间,这样总的时间复杂度就是O(n^2)了。

我这里将上面说的用HashMap记录每个char的下标的方法实现,同时可参考了他人关于sliding window的概念,这样就可以免去多余的indexOf时间,同时也能演示一下这种重要的解法。

如上图所示,黄色的是遍历的指针,蓝色的是开始指针,他们共同组成一个所谓的sliding window,代表当前的Longest Substring Without Repeating Characters。

黄色指针从头到尾遍历数组,如果未遇到重复字符,就继续往后。如果遇到了,记录前面的窗口长度,并和历史最大的比较,做更新。

同时,蓝色指针更新到重复字符的后一个位置,黄色从原位置继续往后。

直至黄色指针将数组遍历结束。

画出图来就很容易理解,窗口内始终保持是没有重复字符的,这样遇到了一个重复字符,只要将窗口左侧缩小到这个重复字符后开始,就可以保证再次没有重复字符了。如果缩小到这个位置之前,这个重复字符还是在窗口里;如果缩小到这个位置之后,浪费了窗口的大小。所以,必须是这个位置。

下面是代码。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int longestLength = 0;
        int currentLength = 0;
        int start = 0;
        Map<Character, Integer> charMap = new HashMap<Character, Integer>();
        for(int i = 0; i < s.length(); i++){
            if(!charMap.containsKey(s.charAt(i)) || charMap.get(s.charAt(i)) < start){
                charMap.put(s.charAt(i), i);
                currentLength++;
            }else{
                start = charMap.get(s.charAt(i)) + 1;
                charMap.put(s.charAt(i), i);
                longestLength = Math.max(longestLength, currentLength);
                currentLength = i - start + 1;
            }
        }
        longestLength = Math.max(longestLength, currentLength); //不能忘记,可能到了结尾也没重复字符,但是要去主动更新最长长度
        return longestLength;
    }
}

 

参考网站:

http://fisherlei.blogspot.jp/2012/12/leetcode-longest-substring-without.html

http://blog.csdn.net/linhuanmars/article/details/19949159

 

//20180628

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int start = 0, end = 0;
        int max = 0;
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        while (end < s.length()) {
            if (map.containsKey(s.charAt(end)) && map.get(s.charAt(end)) >= start) {
                start = map.get(s.charAt(end)) + 1;
            }
            map.put(s.charAt(end), end);
            max = Math.max(max, end - start + 1);
            end++;
        }
        return max;
    }
}

 

posted on 2015-01-18 22:51  NickyYe  阅读(956)  评论(0编辑  收藏  举报