LeetCode03 - 無重復字符的最長子串(Java 實現)
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
題目描述
給定一個字符串,請你找出其中不含有重復字符的 最長子串 的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。
示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。
示例 3:
輸入: "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是 "wke",所以其長度為 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
相關知識點補充
Java 數組的下標可以是:
- 整型常量
- 整型變量
- 整型字符常量(字符的 ASCII 碼)
- 整型表達式
例如:
int a[300],i;
// 下標是整型常量,整數 5
a[5] = 5;
// 下標是整型變量, i 現在數值 5, 就是數組元素 a[5]。
i = 5;
a[i] = 5;
// 下標是整型字符常量,等於 F 的 ASCII 碼值,就是數組元素 a[70]。
a['F'] = 5;
// 下標是整型表達式,表達式運算結果是 5,就是數組元素 a[5]。
a['F' - 'A'] = 5;
怎么查看 ASCII 碼的值?
// 比如查看 a 的 ASCII 值
System.out.println((int)'a');
// 結果為 97
System.out.println((int)'z');
// 結果為 122
Java 實現與實現思路
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 03:給定一個字符串,請你找出其中不含有重復字符的最長子串的長度。
*
* @author XiaoPengwei
* @since 2019-07-14
*/
public class LC03LongestSubstring {
public static void main(String[] args) {
// 該代表性示例,最長為 4,即 abcd
// String str = "abcabcdbcs";
String str = "abba";
int lengthByMethod1 = lengthOfLongestSubstringMethod1(str);
System.out.println("lengthByMethod1-->" + lengthByMethod1);
int lengthByMethod2 = lengthOfLongestSubstringMethod2(str);
System.out.println("lengthByMethod2-->" + lengthByMethod2);
}
/**
* 方法一:
* 求不含有重復字符的最長子串的長度
* 思路:創建一個 pre 數組表示長度,從左到右遍歷字符串數組,查看
*
* @param s 字符串參數
* @return int 長度
*/
public static int lengthOfLongestSubstringMethod1(String s) {
// 數組沒有賦值的時,所有元素會初始化為 0
// 字符為下標時,會將 ASCII 碼作為下標
int[] pre = new int[128];
int max = 0, t = 0;
// i 表示當前處理的第 i 個字符
for (int i = 0; i < s.length(); i++) {
// c 為依次取出的單個字符
char c = s.charAt(i);
/* 如果 pre[c] 不等於 0 表示數組中該位置被修改過,也就代表前面有重復字符
*/
if (pre[c] != 0 && pre[c] > t) {
// 更新 max 最大值
// i - t 重復元素下標 - 上一次沒重復的下標
max = Math.max(max, i - t);
// t 是為求下一個子串長度做准備,因為要求出的是最長的子串長度
// 更新 t,上一次沒重復的下標
t = pre[c];
}
// 如果 pre[c] 為 0,或者 pre[c] <= t
pre[c] = i + 1;
}
return Math.max(max, s.length() - t);
}
/**
* 方法二:
* 定義一個 map 數據結構存儲 (k, v),其中 key 值為字符,value 值為字符位置 +1,加 1 表示從字符位置后一個才開始不重復
* 我們定義不重復子串的開始位置為 start,結束位置為 end; [start, end] 可理解為滑動窗口
* 隨着 end 不斷遍歷向后,會遇到與 [start, end] 區間內字符相同的情況,此時將字符作為 key 值,獲取其 value 值,並更新 start,此時 [start, end] 區間內不存在重復字符
* 無論是否更新 start,都會更新其 map 數據結構和結果 max。
* 時間復雜度:O(n)
*
* @param s 字符串參數
* @return int 長度
* @author guanpengchn
* @link https://leetcode-cn.com/problems/two-sum/solution/hua-jie-suan-fa-3-wu-zhong-fu-zi-fu-de-zui-chang-z/
*/
public static int lengthOfLongestSubstringMethod2(String s) {
// max 表示所求的最大長度
int max = 0;
/* Map 中 key 值為字符,value 值為字符位置 +1,加 1 表示從字符位置后一個才開始不重復
* 同一個字符 key,在 map 中只存在一個。當重復時更新它的值。
*/
Map<Character, Integer> map = new HashMap<Character, Integer>();
/* start 指向不重復子串的第一個字符的下標。
* 當存在重復字符時 start 指向后面一個重復字符,指向下一個不重復子串的第一個字符的下標
*/
for (int start = 0, end = 0; end < s.length(); end++) {
char c = s.charAt(end);
if (map.containsKey(c)) {
/* 如果含有重復字符串,將滑動窗戶的開始位置更新,后移
* map.get(c) 的值表示該出現重復的字符,上一次出現時下標+1
* 什么時候會出現 start<map.get(c)?
* 答:map.get(c) 該重復字符出現的位置不一定,如果重復字符出現的順序和之前一樣,
* 比如:abcabcd,先重 a,再重 b
* 什么時候會出現 start>map.get(c)?
* 答:第二次出現重復時,如果重復的字符在第一次出現重復的字符前面
* 比如 abba,先重 b,再重 a。出現 a 重復時,start 為 2 > map.get('a') 為 1
*/
start = Math.max(map.get(c), start);
}
/* 不管是否重復這里都會執行
* 每次執行判斷一次滑動窗口長度是否超過當前最大長度,是則更新
* end - start + 1 表示滑動窗口長度,子串長度,不重復子串最短也為 1
* 為什么要 + 1?
* 當 start 和 end 指向一個是元素時,下標一樣,end-start 為 0,此時存在一個不重復子串為 1 個元素
* 當 end 指向 start 后面相鄰一個,end-start 為 1,此時不重復子串為 2 個元素
*/
max = Math.max(max, end - start + 1);
/* 不管是否重復這里都會執行
* 如果不重復,會新添加一個 key,值為:位置下標+1
* 如果重復,會更新這個 key 對應的值為:后面又重復出現的該字符的位置下標+1
*/
map.put(c, end + 1);
}
return max;
}
}