1. 程式人生 > >leetcode-3-無重複字元的最長子串(longest substring without repeating characters)-java

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。