3. 沒有重複字母的最長子串 [leetcode 3: Longest Substring Without Repeating Characters]

3. 沒有重複字母的最長子串 [leetcode 3: Longest Substring Without Repeating Characters]

原題鏈接

https://leetcode.com/problems/longest-substring-without-repeating-characters

老王的解法鏈接

https://github.com/simplemain/leetcode/blob/master/3/analysis.md

難度

★★☆☆☆

標籤

字符串 / 判重

題目描述

輸入一個字符串, 求他的一個子串的長度: 在這個子串裏, 沒有重複的字母.

說明: 這裏是要求子串(要求連續), 而不是子序列(不要求連續)

輸入樣例

第一組: abcabcbb
第二組: bbbbb
第三組: pwwkew

輸出樣例

第一組: 3 (因爲最長無重複的子串是: abc)
第二組: 1 (最長就只有一個: b)
第三組: 3 (最長的是: wke)

解法分析

  • 解法1 : 三重循環

這道題剛一看到的時候, 就覺得有點懵圈. 感覺完全無頭緒. 但是鎮靜30秒以後, 其實還是有思路, 最簡單的無非就是我用兩個指針分別指向字符串的兩個位置, 看看這兩個位置之間的子串有沒有重複的字符.

  1. 如果沒有, 就記錄一下他的長度;

  2. 如果有, 就不符合我們要求, 跳出循環.

比如:

abcdefgcd
  ^    ^
  p    q

我們用指針p和q分別指向字符串的兩個位置, 然後再判斷p和q之間是否有重複字符. 他們之間明顯沒有重複的, 所以他們的長度就可以記錄下來, 看看是否是最大長度.

如果q指針再往後走一個, 他們之間就有重複的字母c了, 所以就不符合條件了.

最後, 將長度最大的那一個輸出就是了.

for (int p = 0; p < len; p++)
{
	for (int q = p; q < len; q++)
	{
		final boolean isOK = judge(cs, p, q);
		if (!isOK) break;
		final int curLen = q - p + 1;
		if (curLen > maxLen)
		{
			maxLen = curLen;
		}
	}
}

代碼看起來非常簡潔, 不過呢, 就是執行效率不高. 大家看到基本上就是三重循環, 所以時間複雜度O(n^3).

完整代碼請點擊這裏: 完整代碼


  • 解法2 : 二重循環 + hash判重

我們既然有了第一種解法大的思路, 那麼我們有沒有優化的可能呢?

其實是可以的. 就是在解法1的第三重循環那裏, 我們的目的是要判斷是否有重複. 那既然是要判重複, 就可以不用循環, 可以直接用hash的方法. 這不就可以直接把性能提升了嗎?

時間複雜度就降到: O(n^2) ~ O(n^2 * lgn) [爲什麼Hash是這個複雜度, 請參見老王在第一題中的分析哈]

完整代碼請點擊這裏: 完整代碼


  • 解法3: 二重循環 + 數組判重

因爲是英文的網站, 所以要處理的字符串是ascii字符. 這裏字符取值的範圍就會落在[0, 255]之間. 那麼, 我們就可以用一個char[256]數組, 取代HashSet來判斷重複. 這樣, 一定就可以保證時間複雜度控制在O(n^2).

這裏其實是一個偷巧的做法. 不過這種做法應用場景也比較多, 只要範圍是固定的範圍的最大和最小值差值不大, 我們就可以用這種方法.

即使這題含有中文字符, 用unicode編碼, 我們也可以用一個char[65536]的數組來處理, 因爲他滿足我們上面說的兩個條件.

完整代碼請點擊這裏: 完整代碼


  • 解法4: 滑動窗口

上述三種方法, 其實都是一種思路的衍生品. 那我們有沒有更好的方法呢?

其實是有的(這句是廢話 o).

我們最先用一個1 * 1的框, 將第一個字符框住.

[a]bcdefgcd

大家可以看到, 字符a已經被中括號代表的框給框起來了.

好, 接下來我們把框試着往右邊擴大一個, 看看有沒有重複.

如果沒有, 我們就把右邊的字符框進來.

[ab]cdefgcd

以此類推, 我們可以一直把框擴大到字符g.

[abcdefg]cd

不過, 當我們繼續擴張的時候, 發現字符c重複了, 他之前就已經出現在框裏了. 這個時候我們怎麼辦呢?

既然他已經出現在框裏了, 就說明從框起始的地方開始, 已經不能繼續往下延伸了, 對吧.

那我們就需要重新調整框起始的位置, 一直往右邊挪. 挪到什麼時候才停止呢? 直到新加入的那個元素c之前沒在這個框裏出現過. 也就是第一個c後面的位置.

abc[defgc]d

這樣, 我們就能很快的找到沒有重複字母出現的子串, 對不對?

不過新的子串有可能不是符合條件最長的, 沒關係, 我們重複上面的動作, 讓他的右邊繼續擴張, 直到抵達字符串的最後一個字符.

這樣, 我們就通過一遍掃描, 完成了算法的要求.

完整代碼請點擊這裏: 完整代碼


  • 解法5: 滑動窗口 + 位置記錄

這個算法就是解法4的優化算法. 我們在找窗口裏出現過的字符的時候, 需要將左括號[一個個的挪動, 看他和新的元素是不是相等, 這樣效率不是很高.

我們可以將窗口裏每個元素的位置記錄一下, 到時挪動的時候, 只需要從記錄表裏面查一下, 就知道應該將左括號[挪動到這個元素的下一個位置. 對吧~

完整代碼請點擊這裏: 完整代碼


好了, 這一題就分析到這裏. 如果覺得老王的講解有意思或有幫助, 可以給老王點個贊或者打個賞啥的, 老王就很開心啦~

咱下一題繼續~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章