題目描述
給定一個字符串,請你找出其中不含重複字符的最長子串的長度。
示例1:
輸入: s = “abcabcbb”
輸出: 3
解釋: 因爲無重複字串的最長子串是“abc”,所以其長度爲 3.
示例2:
輸入: s = “bbbbb”
輸出: 1
解釋: 因爲無重複字符的最長子串是“b”,所以其長度爲1.
示例3:
輸入: s = “pwwkew”
輸出: 3
解釋: 因爲無重複字符的最長子串是“wke”,所以其長度爲3.
說明:
請注意,你的答案必須是子串的長度,示例3中,"pwke"使用一個子序列,而不是子串
思路分析
我們通過題意可以理解是尋找無重複字符的最長子串,返回其長度。這裏我們講問題進行拆解:
- 無重複字符
- 最長子串
對於問題1 無重複字符,需要無重複字符,這裏我們爲尋求較好的時間複雜度不採取對每一個字符c
在某範圍內都進行搜索判重的方法。這裏我們藉助輔助容器來達到記錄歷史的目的,進而當遍歷到後面的字符時,直接在輔助容器中查詢是否包含即可。
這一題讓我直接聯想到得是計算機網絡中TCP的滑動窗口,我們通過界定窗口的左邊界和右邊界來判斷需要的數據區域,這裏即爲包括left
不包括right
的字符串均無重複字符。這裏我們通過圖示來說明:
圖1
我們通過兩個下標來確定滑動窗口的範圍left, right
,且爲左閉右開;我們藉助集合set
來存儲遍歷歷史,可知我們每個元素均爲唯一。
- 當
s.charAt(right)
沒有在set
中時,先添加進set
,且right++
,即滑動窗口右擴展,重複步驟1; - 若
set
已經包括了s.charAt(right)
,則此時一定是left
下標的字符爲重複字符,滑動窗口左收縮,且更新left
爲left++
;
接下來再看問題2 最長子串,我們這裏需要返回的是這個最長子串的長度,即一個整型數據,我們這裏可採用直接對比的方式,每次都更新爲比較值得最大值即可:
maxSubStrLen = Math.max(maxSubStrLen , currLen)
。
此時可以得知解題步驟爲:
核心操作
- 當
s.charAt(right)
沒有在set
中時,先添加進set
,且right++
,即滑動窗口右擴展,重複步驟1, 更新maxSubStrLen
; - 若
set
已經包括了s.charAt(right)
,則此時一定是left
下標的字符爲重複字符,滑動窗口左收縮,且更新left
爲left++
;
解題代碼1
public static int solution1(String s){
if(s == null) return 0;
int len = s.length();
if(len == 1) return 1;
/* Step 1: Init. integer and container.
*/
int maxSubStrLen = 0, left = 0, right = 0;
Set<Character> set = new HashSet<>();
/* Step 2: go-through string
and
check whether repeated in container to re-fine sliding window
*/
while(left < len && right < len){
if(!set.contains(s.charAt(right))){ // enlarge window on the right
set.add(s.charAt(right++));
maxSubStrLen = Math.max(maxSubStrLen, right-left);
}else{ // shrink window from left
set.remove(s.charAt(left++));
}
}
return maxSubStrLen;
}
複雜度分析
時間複雜度: 最壞情況下,需要對每個字符都訪問兩邊,2N
,故時間複雜度爲O(N)
;
空間複雜度: 我們藉助了輔助容器,且考慮字符集大小M
的情況下,空間複雜度爲O(min(M, N))
.
進階思考
在solution1 中最壞情況(均爲同一個字符),我們對每個字符都需要訪問兩遍,能否存在一種方法使得訪問字符的次數減少。我們已知字符的可取範圍是有限的,這裏假定爲M
,則不妨在right
界定右邊界的情況下,判斷並更新滑動窗口內的有且唯一的字符的下標,更新maxSubStrLen
。
在這裏我們依然需要藉助輔助容器,且還需要記錄字符的下標,則可選取輔助容器爲哈希表HashMap<char, index>
, key
爲瀏覽過的字符,value
爲滑動窗口內(此時包含right
在內)的相應字符的最新下標。
則可總結爲下:
核心操作
- 當
map
中包含s.charAt(right)
時,更新重複字符left
的最新下標;否則直接進入步驟2; - 更新
maxSubStrLen
,同時將s.charAt(right)
添加入map
;
解題代碼2
public static int solution2(String s){
if(s == null) return 0;
int len = s.length();
if(len == 1) return 1;
/* Step 1: Init. integer and container.
*/
int maxSubStrLen = 0;
// <char, index>
Map<Character, Integer> map = new HashMap<>();
/* Step 2: go-through string
and
update existed index if checked.
*/
for(int left=0, right=0; right < len; right++){
if(map.containsKey(s.charAt(right))){
left = Math.max(map.get(s.charAt(right)), left);
}
maxSubStrLen = Math.max(maxSubStrLen, right - left + 1);
map.put(s.charAt(right), right + 1);
}
return maxSubStrLen;
}
複雜度分析
時間複雜度: 這裏我們對數據進行了一次遍歷,故時間複雜度爲O(N)
;
空間複雜度: 我們藉助了輔助容器,同solution1一樣,爲O(min(M, N))
;
Github源碼
完整可運行文件請訪問GitHub。