1. 程式人生 > 實用技巧 >劍指 Offer 48. 最長不含重複字元的子字串(動態規劃/雙指標/hash表)

劍指 Offer 48. 最長不含重複字元的子字串(動態規劃/雙指標/hash表)

  • 題目描述
請從字串中找出一個最長的不包含重複字元的子字串,計算該最長子字串的長度。



示例1:

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

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

輸入: "pwwkew"
輸出: 3
解釋: 因為無重複字元的最長子串是"wke",所以其長度為 3。
    請注意,你的答案必須是 子串 的長度,"pwke"是一個子序列,不是子串。
  • 解法一:hash表+動態規劃

首先可以用動態規劃求解此題。設動態規劃表dp,dp[j]是以s[j]為結尾的“最長不重複字串”的長度。dp[j-1]是第j-1個“最長不重複字串”的長度。假設s[j]上一次出現的字元位置為i,則s[i]和s[j]之間的距離d=j-1,此時,dp[j-1]存在兩種情況:

1.d>dp[j-1],想想這個距離大於dp[j-1],那麼說明s[i]不在以s[j-1]為結尾的“最長不重複字串”裡面,此時dp[j]=dp[j-1]+1

2.d<=dp[j-1],說明s[i]在在以s[j-1]為結尾的“最長不重複字串”裡面,此時dp[j] = j -i。

此外,可以用一個hash map儲存s[j]出現的位置, 如果重複出現,則更新為最近的位置,hash map的時間複雜度為O(1)。

再梳理一下動態規劃的轉移方程:

此時返回時max的最長不重複字串。

程式碼:

class Solution:
    '''
hash表+動態規劃 ''' def lengthOfLongestSubstring(self, s: str) -> int: dp = [] res = tmp = 0 dic = {} for j in range(len(s)): i = dic.get(s[j], -1) dic[s[j]] = j # tmp = tmp + 1 if tmp < j-i else j - i if j -i <= tmp: tmp
= j -i elif j -i > tmp: tmp += 1 res = max(res, tmp) return res
  • 解法二:hash表+雙指標

這道題,其實最容易想到的就是時間複雜度為O(N^2)的雙指標。類似滑動視窗的思想,用指標p1和p2指向字串頭,用一個滑動視窗始終維護當前不重複的最大長度的字串s[p1:p2+1],當s[p2]不在這個字串中,則從左向右移動p2,否則,從左向右移動p1,同時每次儲存最大s[p1:p2+1]的長度,儲存每次更新的最大值。

但是呢,這樣時間複雜度高啊,p1和p2最差的情況需要O(N^2)。

class Solution:
    '''
    滑動視窗+雙指標
    '''
    def lengthOfLongestSubstring(self, s: str) -> int:
        p1, p2= 0,0
        res = 0
        while p1 < len(s) and p2 < len(s):
            if s[p2] not in s[p1:p2]:
                res = max(res, p2+1 - p1)
                p2 += 1
            else:
                p1 += 1
        return res

因此如果我們在p2遇到重複的s[p2]時,直接將s[p1]指向前一個s[p2]出現的位置,(為什麼要這麼做呢?因此p1原來的位置到前一個s[p2]出現的位置這段位置已經包含了重複的s[p2],因此遍歷這部分是沒有意義的,最大字串一定是在沒有重複的)。這樣的話,我們可以用一個hash表儲存s[p2]的位置,如果下一次遇到s[p2]時,則將p1的指向s[p2]出現的前一個位置。

最大字串長度則為每次的p2-p1的最大值。

此時時間複雜度O(N),空間複雜度O(N).

程式碼中i表示p1,j表示p2.

class Solution:
    '''
    hash表+雙指標
    '''
    def lengthOfLongestSubstring(self, s: str) -> int:
        res, i , Map =0, -1, {}
        for j in range(len(s)):
            if s[j] in Map:
                i = max(Map[s[j]], i) #求i的新位置
            Map[s[j]] = j #雜湊表記錄s[j]的索引j
            res = max(res, j - i)
        return res

參考:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/solution/c-san-chong-jie-fa-by-yizhe-shi-2/