?LeetCode刷題實(shí)戰(zhàn)159:至多包含兩個(gè)不同字符的最長(zhǎng)子串
Given a string, find the length of the longest substring T that contains at most 2 distinct characters.
題意
例 1:
輸入: “eceba”
輸出: 3
解釋: t 是 “ece”,長(zhǎng)度為3。
例 2:
輸入: “ccaabbb”
輸出: 5
解釋: t 是 “aabbb”,長(zhǎng)度為5。
解題
解題思路
一. 理解題意
要求是子串,不是子序列
子串最多由兩個(gè)不同字符 按任意數(shù)量 排列組合而成
不要求記錄子串,只需要記錄長(zhǎng)度
二. 選擇數(shù)據(jù)結(jié)構(gòu)與算法
雙指針 + 哈希表 解法
數(shù)據(jù)結(jié)構(gòu):指針、哈希表
算法思維:遍歷、雙指針、哈希(散列)
解題思路
聲明一個(gè)哈希表和一個(gè)哈希函數(shù)
? 哈希函數(shù)用于生成字符索引
? 哈希表存放該字符在子串中出現(xiàn)的個(gè)數(shù)聲明一個(gè)整形變量存放“不同字符”的個(gè)數(shù),按照題目要求,此個(gè)數(shù)應(yīng)當(dāng) <= 2
用左右兩個(gè)指針遍歷整個(gè)字符串
? 判斷右指針字符是否是“新字符”
? ? 如果是新字符,不同字符數(shù) +1
? ? 無論是否為新字符,右指針在哈希表中相應(yīng)位置的數(shù)據(jù)都要 +1,表示右指針字符在子串中出現(xiàn)的次數(shù) +1
? ? 不斷判斷不同字符數(shù)是否 > 2
? ? ? 如果大于2,左指針在哈希表中相應(yīng)位置的數(shù)據(jù) -1,表示左指針字符在子串中出現(xiàn)的次數(shù) -1
? ? ? 判斷左指針在哈希表中相應(yīng)位置的數(shù)據(jù)是否 == 0
? ? ? ? 如果不為 0,說明當(dāng)前刪除的左指針字符不是子串中的“最后一個(gè)”,右移左指針
? ? ? ? 如果為 0,說明當(dāng)前刪除的左指針字符是子串中的“最后一個(gè)”,不同字符數(shù) -1不管任何情況,右指針都照常右移
每次右指針右移時(shí),計(jì)算最長(zhǎng)子串的長(zhǎng)度
邊界問題
右指針(b)領(lǐng)先左指針(a),因此邊界條件為:
b < s.length()
細(xì)節(jié)問題
子串長(zhǎng)度的計(jì)算公式:
如果先計(jì)算長(zhǎng)度,右指針再右移,則subLen = b - a +1
如果右指針先右移,再計(jì)算長(zhǎng)度,則subLen = b - a
class?Solution?{
????public?int?lengthOfLongestSubstringTwoDistinct(String s)?{
????????int?len = s.length();
????????//定義哈希表
????????int[] hashtable = new?int[128];
????????for?(int?i = 0; i < 128; i++){
????????????hashtable[i] = 0;
????????}
????????int?a = 0; //左指針
????????int?b = 0; //右指針
????????int?count = 0; //不同字符的個(gè)數(shù)
????????int?subLen = 0; //最長(zhǎng)子串的長(zhǎng)度
????????
????????//遍歷字符串
????????while?(b < len) {
????????????char?cb = s.charAt(b);
????????????//如果 b 處字符不存在于哈希表中
????????????if?(hashtable[hash(cb)] == 0){
????????????????count++; //不同字符個(gè)數(shù) +1
????????????}
????????????//哈希表對(duì)應(yīng)位置 +1
????????????hashtable[hash(cb)]++;
????????????
????????????//直到不同字符個(gè)數(shù) <= 2
????????????while?(count > 2){
????????????????char?ca = s.charAt(a);
????????????????hashtable[hash(ca)]--; //哈希表中 a 處字符對(duì)應(yīng)位置 -1
????????????????//如果哈希表中 a 處字符對(duì)應(yīng)位置不為 0
????????????????if?(hashtable[hash(ca)] == 0){
????????????????????count--; //不同字符個(gè)數(shù) -1
????????????????}
????????????????a++; //a指針右移
????????????}
????????????
????????????//計(jì)算子字符串長(zhǎng)度
????????????subLen = Math.max(subLen,b-a+1);
????????????b++;
????????}
????????return?subLen;
????}
????//定義哈希函數(shù)
????private?int?hash(char?c){
????????return?c;
????}
}
內(nèi)存消耗:36.6 MB,擊敗了 99.63% 的Java用戶
時(shí)間復(fù)雜度:O(n) -- 兩次鏈表的遍歷 O(n),查詢哈希表 O(1)
空間復(fù)雜度:O(1) -- 一個(gè)哈希表 O(1),兩個(gè)指針 O(1),兩個(gè)整數(shù)變量 O(1)
使用“滑動(dòng)窗口”的算法思維解決問題
優(yōu)化解法
/**
?????* 解法一:采用滑動(dòng)窗口的算法思想,選取 left-right 子串進(jìn)行驗(yàn)證
?????* 如果字符數(shù)量沒有超過2,則記錄此時(shí)的最大長(zhǎng)度,并且 right++
?????* 如果字符數(shù)量超過2,則 left++
?????* 使用 map 存放出現(xiàn)過的字符及次數(shù)
?????*
?????* (優(yōu)化算法,使用數(shù)組替代 map)
?????*/
????public?int?lengthOfLongestSubstringTwoDistinct1(String s)?{
????????char[] chars = s.toCharArray();
????????int?len = chars.length;
????????Mapmap?= new?HashMap<>();
????????int?left = 0, right = 0;
????????int?count = 0;//不同的字符數(shù)量
????????int?maxLength = 0;
????????while?(right < len) {
????????????//在map中獲取該字符出現(xiàn)的次數(shù)(不存在則為0),次數(shù)+1
????????????int?rightNumber = map.getOrDefault(chars[right], 0) + 1;
????????????map.put(chars[right], rightNumber);//更新map
????????????if?(rightNumber == 1) {//第一次出現(xiàn)
????????????????count++;
????????????}
????????????right++;
????????????if?(count <= 2) {
????????????????maxLength = Math.max(maxLength, right - left);
????????????}
????????????while?(count > 2) {
????????????????//左側(cè)移除一個(gè)字符,在map中獲取該字符出現(xiàn)的次數(shù)-1
????????????????int?leftNumber = map.get(chars[left]) - 1;
????????????????map.put(chars[left], leftNumber);//更新map
????????????????if?(leftNumber == 0) {
????????????????????count--;
????????????????}
????????????????left++;
????????????}
????????}
????????return?maxLength;
????}
最優(yōu)解
/**
?????* 最優(yōu)解
?????* 改進(jìn)1:考慮 ASCII 碼表中的128個(gè)字符,可以使用數(shù)組代替 map,存放每個(gè)字符出現(xiàn)的次數(shù)
?????* 改進(jìn)2:滑動(dòng)窗口 只會(huì)擴(kuò)大或者平移(我們要取的就是最大窗口長(zhǎng)度)
?????*
?????* @param s
?????* @return
?????*/
????public?int?lengthOfLongestSubstringTwoDistinct(String s)?{
????????final int?length = s.length();
????????final int[] map?= new?int[128];
????????int?right = 0, left = 0;
????????//count 為不同字符的數(shù)量
????????for?(int?count = 0; right < length; ) {
????????????//右側(cè)新字符進(jìn)入窗口
????????????if?(map[s.charAt(right++)]++ == 0) {
????????????????count++;
????????????}
????????????//如果新字符進(jìn)入窗口后使得 不同字符數(shù)量大于2,則左側(cè)窗口也向右滑動(dòng)一個(gè)(窗口平移)
????????????if?(count > 2) {
????????????????if?(--map[s.charAt(left++)] == 0) {
????????????????????count--;
????????????????}
????????????}
????????}
????????return?right - left;
????}
內(nèi)存消耗:36.8 MB,擊敗了 96.08% 的Java用戶
時(shí)間復(fù)雜度:O(n) -- 字符串的遍歷 O(n),哈希表的查詢 O(1)
空間復(fù)雜度:O(1) -- 定長(zhǎng)的哈希表 O(1),雙指針 O(1)
