leetcode-3-無重複字元的最長子串(longest substring without repeating characters)-java
題目及測試
package pid003; /*無重複字元的最長子串 給定一個字串,找出不含有重複字元的最長子串的長度。 示例 1: 輸入: "abcabcbb" 輸出: 3 解釋: 無重複字元的最長子串是 "abc",其長度為 3。 示例 2: 輸入: "bbbbb" 輸出: 1 解釋: 無重複字元的最長子串是 "b",其長度為 1。 示例 3: 輸入: "pwwkew" 輸出: 3 解釋: 無重複字元的最長子串是 "wke",其長度為 3。 請注意,答案必須是一個子串,"pwke" 是一個子序列 而不是子串。 */ public class main { public static void main(String[] args) { String[] testTable = {"abcabcbb","bbbbb","pwwkew"}; for (int i=0;i<testTable.length;i++) { test(testTable[i]); } } private static void test(String ito) { Solution solution = new Solution(); int rtn; long begin = System.currentTimeMillis(); System.out.println("ito="+ito); rtn = solution.lengthOfLongestSubstring(ito);//執行程式 long end = System.currentTimeMillis(); System.out.println("rtn="+rtn); System.out.println(); System.out.println("耗時:" + (end - begin) + "ms"); System.out.println("-------------------"); } }
解法1(成功,60ms,較快)
從頭到尾一次遍歷,設定一個佇列,先進先出,設定一個map,放置現在字串的char
i每增加一個,如果map中已有這個char,那麼從佇列不斷退出字元,直到等於現在的char,處理重複結束
然後佇列加入這個char,map設定這個char,nowlength++,如果現在長度大於max,那麼max=now
package pid003; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; public class Solution { public int lengthOfLongestSubstring(String s) { int length=s.length(); if(length==0||length==1){ return length; } HashMap<Character, Boolean> map=new HashMap<>(); Queue<Character> queue=new LinkedBlockingQueue<Character>(); int nowLength=0; int maxLength=0; for(int i=0;i<length;i++){ Character now=s.charAt(i); if(map.containsKey(now)){ while(true){ Character prev=queue.remove(); map.remove(prev); nowLength--; if(prev.equals(now)){ break; } } } map.put(now, true); nowLength++; queue.add(now); if(nowLength>maxLength){ maxLength=nowLength; } } return maxLength; } }
解法2(別人的)
這道求最長無重複子串的題和之前那道 Isomorphic Strings 很類似,屬於LeetCode的早期經典題目,博主認為是可以跟Two Sum媲美的一道題。給了我們一個字串,讓我們求最長的無重複字元的子串,注意這裡是子串,不是子序列,所以必須是連續的。我們先不考慮程式碼怎麼實現,如果給一個例子中的例子"abcabcbb",讓你手動找無重複字元的子串,該怎麼找。博主會一個字元一個字元的遍歷,比如a,b,c,然後又出現了一個a,那麼此時就應該去掉第一次出現的a,然後繼續往後,又出現了一個b,則應該去掉一次出現的b,以此類推,最終發現最長的長度為3。所以說,我們需要記錄之前出現過的字元,記錄的方式有很多,最常見的是統計字元出現的個數,但是這道題字元出現的位置很重要,所以我們可以使用HashMap來建立字元和其出現位置之間的對映。進一步考慮,由於字元會重複出現,到底是儲存所有出現的位置呢,還是隻記錄一個位置?我們之前手動推導的方法實際上是維護了一個滑動視窗,視窗內的都是沒有重複的字元,我們需要儘可能的擴大視窗的大小。由於視窗在不停向右滑動,所以我們只關心每個字元最後出現的位置,並建立對映。視窗的右邊界就是當前遍歷到的字元的位置,為了求出視窗的大小,我們需要一個變數left來指向滑動視窗的左邊界,這樣,如果當前遍歷到的字元從未出現過,那麼直接擴大右邊界,如果之前出現過,那麼就分兩種情況,在或不在滑動視窗內,如果不在滑動視窗內,那麼就沒事,當前字元可以加進來,如果在的話,就需要先在滑動視窗內去掉這個已經出現過的字元了,去掉的方法並不需要將左邊界left一位一位向右遍歷查詢,由於我們的HashMap已經儲存了該重複字元最後出現的位置,所以直接移動left指標就可以了。我們維護一個結果res,每次用出現過的視窗大小來更新結果res,就可以得到最終結果啦。
這裡我們可以建立一個256位大小的整型陣列來代替HashMap,這樣做的原因是ASCII表共能表示256個字元,所以可以記錄所有字元,然後我們需要定義兩個變數res和left,其中res用來記錄最長無重複子串的長度,left指向該無重複子串左邊的起始位置,然後我們遍歷整個字串,對於每一個遍歷到的字元,如果雜湊表中該字串對應的值為0,說明沒有遇到過該字元,則此時計算最長無重複子串,i - left +1,其中i是最長無重複子串最右邊的位置,left是最左邊的位置,還有一種情況也需要計算最長無重複子串,就是當雜湊表中的值小於left,這是由於此時出現過重複的字元,left的位置更新了,如果又遇到了新的字元,就要重新計算最長無重複子串。最後每次都要在雜湊表中將當前字元對應的值賦值為i+1。
這裡解釋下程式中那個if條件語句中為啥要有個m[s[i]] < left,我們用一個例子來說明,當輸入字串為"abbca"的時候,當i=4時,也就是即將要開始遍歷最後一個字母a時,此時雜湊表表中a對應1,b對應3,c對應4,left為2,即當前最長的子字串的左邊界為第二個b的位置,而第一個a已經不在當前最長的字串的範圍內了,那麼對於i=4這個新進來的a,應該要加入結果中,而此時未被更新的雜湊表中a為1,不是0,如果不判斷它和left的關係的話,就無法更新結果,那麼答案就會少一位,所以需要加m[s[i]] < left。
public class Solution {
public int lengthOfLongestSubstring(String s) {
int[] m = new int[256];
Arrays.fill(m, -1);
int res = 0, left = -1;
for (int i = 0; i < s.length(); ++i) {
left = Math.max(left, m[s.charAt(i)]);
m[s.charAt(i)] = i;
res = Math.max(res, i - left);
}
return res;
}
}
解法3(方法1的改進)
可以不用queue,hashmap的value改為字元的index,最後一次出現的位置,再加上left,只要新加的字元原來map中沒有或者index<left即可加入,否則left為過去的index。