3.Longest Substring Without Repeating Characters (medium)
3.1.png
Brute solution
- 因為Time Limit Exceeded不被AC
- 維護兩個index找出所有的window,O(n2),每次都檢查window內的元素是否有重復,最終導致O(n3)
-
time complexity:
3.2.png
public class Solution{
public int lengthOfLongestSubstring(String s) {
int longest = 0;
for(int i=0; i<s.length()-1; i++){
for(int j=i+1;j<=s.length();j++){
if(isn_repeated(s,i,j)) longest = Math.max(longest,j-i);
else break;
}
}
return longest;
}
public boolean isn_repeated(String s,int start,int end){
Set<Character> container = new HashSet<>();
for(int i=start;i<end;i++){
char ch = s.charAt(i);
if( ((HashSet) container).contains(ch)) return false;
container.add(ch);
}
return true;
}
}
Sliding window version1
- 采用滑動窗口(sliding window)
By using HashSet as a sliding window, checking if a character in the current can be done in O(1).使用HashSet用空間換取時間 - HashMap: map.containsKey(a),map.get(a),map.put(a,i)
HashSet: set.contains(a),set.remove(a) - complexity analysis:
- time complexity:O(2n)=O(n).最壞的情況:每個元素都被i,j訪問,這時便是O(2n)
- space complexity:O(min(n,m)),空間復雜度取決于窗口的長度,窗口長度的上界是字符串長度n或者charset/alphabet表m
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
Set<Character> set = new HashSet<>();
int res = 0, i = 0, j = 0;
while (i < n && j < n){
//如果set中不包含第j個字母,那么就把這個字母插入set
if(!set.contains(s.charAt(j))){
set.add(s.charAt(j++));
//記錄substring的長度
res = Math.max(res,j - i);
}
//如果set中包含第j個字母,則刪掉set中第一個元素,然后繼續循環
else{
set.remove(s.charAt(i++));
}
}
return res;
}
}
- 窗口狀態變化圖
3.3.png
其中,步驟2~5太多余,直接把i挪到set中重復元素的右邊即可!,通過HashMap實現,下面說明
Sliding window version2
- version1最多需要2n次循環,建立元素與索引的映射后需要n次循環即可解決
- 具體來說就是對n個元素都進行元素值與索引的映射,如果有元素重復出現,則會更新映射
- 測量長度的起點用索引i表示,終點用索引j表示,出現重復元素時,如果其索引大于等于i,則更新i;如果其索引小于i,則不需要更新i
- complexity analysis:
- time complexity:O(n)
- space complexity:O(min(x,m))
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
//1. 建立映射:元素的值與元素的索引
//在這一過程中就能找到最長的substring
Map<Character, Integer> map = new HashMap<>();
int res = 0;
//2. i是測量長度的起點,j是測量長度的終點
for (int j = 0, i = 0; j < n; j++){
if(map.containsKey(s.charAt(j)))
//如果跟i左邊的元素重復了,則不更新測量起點
i = Math.max(map.get(s.charAt(j)) ,i);
//3. 這一步說明了,總共進行n次映射,如果有元素重復出現,則會更新映射
//3.1 每個元素的位置為當前索引的下一個,更新時會讓i在重復元素的右邊
map.put(s.charAt(j),j + 1);
//4. 跟之前的res比較,取最大的
res = Math.max(j - i + 1,res);
}
return res;
}
}
假設字符集為ASCII
- 這種情況下不需要字符和索引之間的映射了,直接用一個大小為128個int數組即可
- int數組,默認初始值為0,利用這一點!HashMap沒有這一特點
- complexity analysis:
- time complexity:O(n)
- space comeplexity:O(min(n,m))
public class Solution {
public int lengthOfLongestSubstring(String s) {
int n =s.length(),res = 0;
//int數組,默認初始值為0,利用這一點!HashMap沒有這一特點
int[] index = new int[128];
//遍歷每個元素,測量起點i,測量終點j
for (int j = 0, i = 0; j< n - 1; i++){
//如果當前元素已經存在于數組中,則要判斷是否更新測量起點i
//通過index[j]和i的大小判斷是否有重復,數組中沒有該
//如果當前元素沒出現過,那么index[s.charAt(j)] == 0
//如果當前元素出現過,那么index[s.charAt(j)]->0,問題來了:如果index[s.charAt(j)]<i,說明當前元素不在窗口內;如果index[s.charAt(j)]>i,說明當前元素在窗口內;邊界情況:index[s.charAt(j)]==i,重復元素不在窗口內,i的值保持不變
i = Math.max(index[s.charAt(j)],i)
res = Math.max(j - i + 1,res);
// 將當前元素的值設置為其索引的右邊,這個索引值是遞增的
index[s.charAt(j)] = j + 1;
}
return res;
}
}