題目
給定一個字符串,請找出其中無重復字符的最長子字符串。
樣例
例如,在"abcabcbb"中,其無重復字符的最長子字符串是"abc",其長度為 3。
對于,"bbbbb",其無重復字符的最長子字符串為"b",長度為1。
分析
思路1:
暴力求解(超時)
最自然的想法就是我們考慮所有子串的情況,然后判斷當前的子串包不包含重復的字符,并且更新最大長度。
所以我們在此主要實現兩個部分
- 求出所有子串
- 判斷字符串有沒有重復元素
下面我們就來具體實現:
- 求出所有子串,思路很簡單,兩根指針,i從0到n,j從i+1到n就可以列舉出所有子串
- 判斷子串有沒有重復字符,顯然用一個set可以實現
代碼:
package TwoPointer;
import java.util.HashSet;
import java.util.Set;
public class LongestSubstringWithoutRepeatingCharacters {
//brute
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int res = 0;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
if(noDupicate(s,i,j))
res = Math.max(res, j-i+1);
return res;
}
private boolean noDupicate(String s, int left, int right) {
Set<Character> set = new HashSet<>();
for(int i=left;i<=right;i++) {
if(set.contains(s.charAt(i)))
return false;
else {
set.add(s.charAt(i));
}
}
return true;
}
}
思路二:
滑動窗口法
滑動窗口方法是一個抽象的概念,廣泛應用于數組和String的問題上,一個窗口就是包含了一系列的元素,然后移動窗口兩邊的元素。所以叫滑動窗口法。
簡單的直接暴力求解非常容易理解,但是效率太低,我們需要想出更好的解法
在暴力求解中,我們做了很多重復判斷是否有子串的工作,這些都是沒有必要的。
比如,如果我們已經知道i到j-1是不含重復子串的,那么當我們要判斷,i到j是否含重復子串時,只需要判斷j是不是在i到j-1中就可以了。這里我們就可以用一個hashset來進行判斷。
我們用hashset來存儲當前窗口所包括的字符,然后我們向右滑動,如果當前字符不再hashset中,我們就可以繼續滑動并且更新最大長度,如果在窗口中,我們就將窗口的左邊界右移,繼續循環判斷,這樣只要遍歷一次就可以了。
代碼:
//slide windows
public int lengthOfLongestSubstring2(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int j = 0;
int res = 0;
for(int i=0;i<n;i++) {
while(!set.contains(s.charAt(j)) && j<n) {
set.add(s.charAt(j));
res = Math.max(res, j-i+1);
j++;
}
set.remove(s.charAt(i));
}
return res;
}
思路三:
滑動窗口的優化:
上一中方方法中,當我們發現重復元素時,我們是移動i也就是左窗口的邊界,一步步移動,其實這樣是可以優化的,我們實際只需要從出現重復元素的位置的后一個開始移動,因為前面那些元素是小于之前的長度的。
舉個例子,假設(i,j)我們發現j元素和j‘元素重復,那么i不需要一步一步右移,而是可以直接移動到j'+1的位置,所以我們需要記錄重復元素的下標,這時候我們可以使用hashmap。
代碼
public int lengthOfLongestSubstring(String s) {
if (s.length()==0) return 0;
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
int max=0;
for (int i=0, j=0; i<s.length(); ++i){
if (map.containsKey(s.charAt(i))){
j = Math.max(j,map.get(s.charAt(i))+1);
}
map.put(s.charAt(i),i);
max = Math.max(max,i-j+1);
}
return max;
}
進一步優化
我們可以用數組進一步優化,用一個int數組記錄出現的元素
- int[26] for Letters 'a' - 'z' or 'A' - 'Z'
- int[128] for ASCII
- int[256] for Extended ASCII
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;
}