Fork me on GitHub

Longest Substring Without Repeating Characters

今天的题目是:

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

Examples:

Given "abcabcbb", the answer is "abc", which the length is 3.

Given "bbbbb", the answer is "b", with the length of 1.

Given "pwwkew", 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”的长度为3。字符串“bbbbb”,答案是“b”的长度为1。字符串“pwwkew”,答案是“wke”的长度为3。要注意的是答案必须是子字符串,“pwke”是子序列并不是一个子字符串。

  第一种解法:也是我能想到的最笨的方法,遍历。第一层从i = 0开始,第二层从 j = i + 1开始,每次判断从 i 到 j 的子字符串是否有重复字符,如果有,直接返回,如果没有,得出子字符串的长度,与之前的长度作比较取出最大值,作为最新的答案,循环结束后返回这个值。

package com.test;

import java.util.HashSet;
import java.util.Set;

public class Test {
    
    @org.junit.Test
    public void test(){
        String s = "pwwkew";//未考虑空字符串
        int length = 1;
        for(int i = 0;i < s.length()-1;i++){
            for(int j = i+1;j < s.length(); j++){
                if(isNotRepeating(s,i,j))
                    length = Math.max(j-i+1, length);
            }
        }
        System.out.println(s + "的最大子字符串长度为" + length);
    }
    
    public boolean isNotRepeating(String s,int start,int end){
        Set<Character> set = new HashSet<Character>();
        for(int i = start;i <= end;i++){
            Character ch = s.charAt(i);
            if(set.contains(ch)){
                return false;
            }else{
                set.add(ch);
            }
        }
        return true;
    }
}

结果:

时间复杂度:O(N^3)。为了验证下标为i,j的字符集是否无重复,我们需要遍历它们,因此,这就花费了O(j-i)时间。对每一个i,花费时间为​​

,因此花费的时间总和是

空间复杂度:O(min(n,m))。我们需要O(k)的空间大小来检查子字符串是否有重复字符,其中k的大小是Set的大小。k的上限是字符串的大小n或者是字符集的大小m(如果字符串所有的字符都是小写的英文字母,m就等于26)。

  第二种解法:这种解法我也想过,但是我考虑的是用队列,这肯定耗时间。在第一种解法中,我们需要重复检查子字符串中是否有重复的字符。但是这并不是必须的。对于从i到j-1的子字符串已经检查了无重复的字符,我们下一步仅需要检查s[j]是否已经在从i到j-1的子字符串中。回到我们的问题,我们使用HashSet来存储字符集[i,j-1),然后我们检查下标为j的字符是否已经在[i,j-1)中存在。如果未存在,我们就把s[j]加入到HashSet中,然后得到了当前无重复子字符串的最大长度,如果已存在,就把s[i]从HashSet中删除(从头开始删除,是不是像队列)。

package com.test;

import java.util.HashSet;
import java.util.Set;

public class Test {
    
    @org.junit.Test
    public void test(){
        String s = "pwwkew";//未考虑空字符串
        System.out.println(s + "的最大子字符串长度为" + getLongestSubStringLength(s));
    }
    
    public int getLongestSubStringLength(String s){
        int length = 1,m = 0,n = 0;//非空字符串的不重复子字符串长度最小为1
        int size = s.length();
        HashSet<Character> set = new HashSet<Character>();
        while(m < size && n < size){
            if(!set.contains(s.charAt(m))){
                set.add(s.charAt(m));
                length = Math.max(length, m-n+1);
                m++;
            }else{
                set.remove(s.charAt(n++));
            }
        }
        return length;
    }
}

结果:

时间复杂度:O(2n)= O(n)。

空间复杂度:O(min(n,m)),与第一种解法类似。

   第三种解法:这种解法是对第二种解法的优化,事实上,它可以优化到只需要n步。我们不再使用set,而是使用map,将字符与其下标关联起来,这样当我们碰到重复字符时可以立刻定位到重复字符的下标。例如我们在[i,j)找到了与s[j]相同字符的下标k,这是我们不需要一步步加i,即i++,此时我们可以直接跳过,使i = k。

package com.test;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Test {
    
    @org.junit.Test
    public void test(){
        String s = "pwwkew";//未考虑空字符串
        System.out.println(s + "的最大子字符串长度为" + getLongestSubStringLength(s));
    }
    
    public int getLongestSubStringLength(String s){
        int length = 1,n = 0;//非空字符串的不重复子字符串长度最小为1
        int size = s.length();
        Map<Character,Integer> map = new HashMap<Character,Integer>();
        for(int m = 0;m < size;m++){
            if(map.containsKey(s.charAt(m))){
                n = Math.max(n, map.get(s.charAt(m)));
                //map.remove(map.get(s.charAt(m)));//这个条件可以不加,加的话,下面的put相当于新添;不加的话下面的put是更新
            }
            length = Math.max(length, m-n);//计算下标为n到m的长度,但是不包括n,所以不用加1
            map.put(s.charAt(m), m);
        }
        return length;
    }
}

时间复杂度:O(n)。

空间复杂度:O(min(n,m)),与第一种解法类似。

  第四种解法:以上的解法对字符串的字符集没有任何限制,事实上如果预先知道了字符集,并且字符集很小,可以考虑用数组的形式代替Map。

  1,如果字符集是‘a’~'z'或者‘A’~‘Z’,可以考虑用int[26];

  2,如果字符集是ASCII,则是int[128];

  3,对于拓展的ASCII,则是int[256]。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }
}

此时的时间复杂度为O(n),空间复杂度为常量O(m),m为字符集的数量,例如上面的128。

 

 

posted @ 2018-03-21 14:59  爱跑步的星仔  阅读(159)  评论(0编辑  收藏  举报