1. 程式人生 > >Leetcode 3. Longest Substring Without Repeating Characters 無重複字元的最長子串

Leetcode 3. Longest Substring Without Repeating Characters 無重複字元的最長子串

Leetcode 3. Longest Substring Without Repeating Characters 無重複字元的最長子串

標籤: Leetcode


題目地址:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

題目描述

給定一個字串,請你找出其中不含有重複字元的最長子串的長度。
示例 1:

輸入: “abcabcbb”
輸出: 3
解釋: 因為無重複字元的最長子串是 “abc”,所以其長度為 3。
示例 2:

輸入: “bbbbb”
輸出: 1
解釋: 因為無重複字元的最長子串是 “b”,所以其長度為 1。
示例 3:

輸入: “pwwkew”
輸出: 3
解釋: 因為無重複字元的最長子串是 “wke”,所以其長度為 3。
請注意,你的答案必須是子串的長度,"pwke"是一個子序列,不是子串。

演算法思想

雜湊表法

做了這幾次題,發現雜湊表還真是個好東西,因為雜湊表的特殊儲存結構,所以使得其檢索起來比較快,所以在處理字串陣列的時候選擇雜湊表是不錯的選擇。

思想是:

1、遍歷字串如"pwwkew"

2、用雜湊表(python中可用字典)把字元和字元在的位置存起來如:{"p":0}

3、在存起來之前要先看這個字元是否出現過。
------3.1、如果沒有,就存起來。
------3.2、否則說明之前出現過,說明當前子串開始重複,所以記錄當前的長度與當前最大長度比較,並且把雜湊表置為空。並且把i回退到重複的位置。(比如pwabwuipq,如果i=4,那麼說明開始重複了,和1位置的w重複了,所以要回退到1位置的後面,因為1後面的並沒有重複)

舉個栗子

初始化:
str = “pwawkew”
dict ={}
maxLength,tempLength = 0

i dict tempLength maxLength
0 {“p”:0} 1 0
1 {“p”:0,“w”:1} 2 0
2 {“p”:0,“w”:1,“a”:2} 3 0
3->2(回退回去) {“p”:0,“w”:1,“a”:2} ->{} 0 3(因為w在dict中有,所以開始重複,此時把tem與max比較,把max置為大的那個)
2 {“a”:2} 0 3

因為有回退的操作,所以這個演算法的效率並不是很高,提交後雖然AC但是時間為700ms。

python程式碼

    # 784ms 
    class Solution(object):
        def lengthOfLongestSubstring(self, s):
            """
            :type s: str
            :rtype: int
            """
            dict = {}
            maxLength = 0
            temLength = 0
            i = 0
            while i<len(s):
                if s[i] in dict:
                    # 因為最後一行有i+=1所以這個地方不是回退到dict[s[i]]+1位置
                    i = dict[s[i]]
                    if temLength>maxLength:
                        maxLength = temLength
                    dict = {}
                    temLength = 0
                else:
                    dict[s[i]] = i
                    temLength+=1
                i+=1
            return (maxLength if(maxLength>temLength) else temLength)  

演算法改進

通過上面的執行可以看出非常耗時,並且也分析出來了原因,主要是因為在發現重複的時候進行了一步回退的操作,所以造成時間的浪費,那麼我們可以考慮一下不進行回退,由於我們已經儲存了之前重複的位置,所以重複元素之間的那段距離可以直接通過減法計算得到(temLength = i - dict[s[i]])

但是這樣又會造成一個問題,由於那段距離我們是通過計算得到的,所以我們不能進行一個向dict裡面放元素的操作,這樣的話意味著我們dict的元素不能置為{},還拿上面的那個例子str = "pwawkew" 來說,dict = {{"p":0,"w":1,"a":2}},這個時候w重複了,我們本來要把dict置為{},但是現在我們直接計算了waw這段距離,所以我們不能把dict置為{},因為不能保證後面是否還會有a,這樣的話我們p也會存在裡面,但是如果後面有p的話,其實並不算重複,因為這個p在1的位置的w之前,所以相當於已經刪除了(因為我們計算的waw這段距離裡面沒有算p),所以這個時候我們的判斷條件就要變了。

如果我們遇到了一個dict裡面有的元素,我們要看它是屬於以前的用過的,還是剛才新增進來的,這裡我們使用一個距離就能表示,因為如果這個重複的是之前的那麼它的位置+tempLength肯定小於當前位置。

1.jpg

所以這個時候如果在字典中有檢查到出現,那麼需要再多一步判斷,也就是3.2的操作進行多一步判斷,這個重複的是在當前串中的,還是以前刪掉的串中的。程式碼中就是if dict[s[i]] +temLength <i:這個判斷。

python程式碼

    # 60ms
    # ref  https://blog.csdn.net/littlebai07/article/details/79100081
    class Solution(object):
        def lengthOfLongestSubstring(self, s):
            """
            :type s: str
            :rtype: int
            """
            dict = {}
            maxLength = 0
            temLength = 0
            i = 0
            while i <len(s):
                # 不重複的了
                if (s[i] not in dict):
                    dict[s[i]] = i
                    temLength +=1
                else:
                    # 是和前面以前刪除的重複了
                    if dict[s[i]] +temLength <i:
                        temLength +=1
                    # 真正重複了
                    else:
                        maxLength =maxLength if(maxLength>temLength) else temLength
                        temLength = i - dict[s[i]]
                    dict[s[i]] = i
                i+=1 
            return (maxLength if(maxLength>temLength) else temLength)  

參考:

https://blog.csdn.net/littlebai07/article/details/79100081