LeetCode 0003 Longest Substring Without Repeating Characters
1. 題目描述
2. Solution 1: Brute force
1、思路
如Q001 中提到的,最樸素的想法自然就是窮舉了。兩層迴圈,外層迴圈設定不重複字串起點start,內層迴圈設定終點end,提供輔助函式判斷s[start, end]是否重複。
2、程式碼實現
public class Solution1 { public int lengthOfLongestSubstring(String s) { int n = s.length(); int result = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j <= n; j++) { if (check(s, i, j)) result = Math.max(result, j - i); } } return result; } private boolean check(String s, int start, int end) { Set<Character> set = new HashSet<>(); for (int i = start; i < end; i++) { char c = s.charAt(i); if (set.contains(c)) return false; set.add(c); } return true; } }
time complexity: 遍歷字元兩層迴圈 O(n^2), check函式O(n), 綜合O(n^3)
space complexity: O(n)
3. Solution 2
1、思路: 雙指標 + Set做滑動視窗
用兩個變數start、end分別表示不重複子串的起點和終點,使用Set做滑動視窗暫存不重複的子串字元,res儲存全域性結果。
a) 初始態
int start = 0, end = 0, res = 0;
Set<Character> window = new HashSet<>();
b) 各變數更新
end逐個遍歷s中的字元,若s[end] not in window,window add s[end],end指向下一個字元,更新res取max{res, window.size}。若 s[end] in window,window中移除s[start],start指向下一個字元。
2、程式碼實現
public class Solution2 { // 使用Set做視窗 public static int lengthOfLongestSubstring(String s) { int n = s.length(); Set<Character> window = new HashSet<>(); int res = 0, start = 0, end = 0; while (start < n && end < n) { // try to extend the range [i, j] if (!window.contains(s.charAt(end))) { window.add(s.charAt(end++)); res = Math.max(res, window.size()); } else { window.remove(s.charAt(start++)); } } return res; } }
3. Solution 3
1、思路: 雙指標 + HashMap
用兩個變數start、end分別表示不重複子串的起點和終點,使用HashMap儲存遍歷過的字元,最近出現在s中的下標,res儲存全域性結果。
a) 初始態
int start = 0, end = 0, res = 0;
HashMap<Character, Integer> lastOccurred = new HashMap<>();
b) 各變數更新
end逐個遍歷s中的字元,若s[end] in HashMap, 更新start,start = max{start, lastOccurred.get(s.charAt(end) + 1},這裡+1
是因為lastOccurred儲存的是字元的最後一次出現的下標,而start的定義是不重複子串的起點,應該是上一個終點下一個字元。若s[end] not in HashMap,把s[end] 錄入HashMap,lastOccurred[s[end]] = end,更新res,res = max{res, end - start + 1},這裡+1
是因為我們需要統計點的個數,end - start只計算了間隔數,可以在數軸上畫畫看。
如圖,取start = 1, end = 4, end - start = 4 - 1 = 3,表示上面的藍色格子有3個,同時,若取點的個數有 {1, 2, 3, 4},點的個數為 end - start + 1 = 4 - 1 + 1 = 4。
2、程式碼實現
public class Solution3 {
/*
滑動視窗:在視窗中始終儲存不重複的字母,求視窗的長度並更新到結果中
start: 不重複子串中第1個字元在s中的下標
end: 不重複子串中最後一個字元在s中的下標
HashMap<Character, Integer> lastOccurred: 儲存字元 c 在s中最後一次出現的下標
難點,兩處`+1`:
- 第30行: lastOccurred中儲存的是字元c在s中最後一次出現的下標,所以start應該是c後面一個字元,該+1。
- 第33行: 手繪一個數軸,數軸上的兩個不同點a, b之間的間隔數為(b - a),可是,a,b間點的個數為(b - a + 1)。
以`pwwkew`為例
index: 0 1 2 3 4 5
char: p w w k e w
滿足要求的子串為`wke`,而程式碼計算出結果選取的是`kew`,此時start = index(k) = 3, end = 5, res = end - start + 1 = 3
*/
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length() == 0) return 0;
HashMap<Character, Integer> lastOccurred = new HashMap<>();
int res = 0;
for (int start = 0, end = 0; end < s.length(); end++) {
if (lastOccurred.containsKey(s.charAt(end))) {
start = Math.max(start, lastOccurred.get(s.charAt(end)) + 1);
}
lastOccurred.put(s.charAt(end), end);
res = Math.max(res, end - start + 1);
}
return res;
}
}
time complexity: O(n)
space complexity: O(n)
4. Solution 4
1、思路: 雙指標 + Array
繼續優化上一個解答,使用陣列做自定義雜湊表,字元c 轉換成 ASCII表中對應整數(int) c,並以此作為陣列下標,陣列中的值儲存字元最後出現的下標 。
2、程式碼實現
public class Solution4 {
/*
使用自定義雜湊表
*/
public static int lengthOfLongestSubstring(String s) {
int[] lastOccurred = new int[128];
int res = 0;
for (int start = 0, end = 0; end < s.length(); end++) {
if (lastOccurred[s.charAt(end)] >= start) {
start = lastOccurred[s.charAt(end)];
}
res = Math.max(res, end - start + 1);
lastOccurred[s.charAt(end)] = end + 1;
}
return res;
}
}
time complexity: O(n)
space complexity: O(n)