leetcode - 467. Unique Substrings in Wraparound String

算法系列博客之Dynamic Programming

本篇博客將運用動態規劃的思想來解決leetcode上467號問題


問題描述:

Consider the string s to be the infinite wraparound string of “abcdefghijklmnopqrstuvwxyz”, so s will look like this: “…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd….”.

Now we have another string p. Your job is to find out how many unique non-empty substrings of p are present in s. In particular, your input is the string p and you need to output the number of different non-empty substrings of p in the string s.

Note: p consists of only lowercase English letters and the size of p might be over 10000.

這裏先對題目的關鍵要求簡要說明如下:
      ·   目的是計算字符串p中滿足特定需求的子串的個數
      ·   要求是能夠在字符串s中找到相同的子串與之字面值完全相同
      ·   相同子串不做重複計算,且字面值相同的子串均視爲相同子串

首先,觀察字符串s,假定 ‘z’和 ‘a’視作連續,那麼字符串s的任意子串均是字典序連續的,也即是說p中滿足需求的子串也一定是連續的,這是一個非常關鍵的入手點。
其次,對於字符串增加一個字符,那麼它所增加的子串,均是以該字符結尾向前延續的
因而如果我們做出假設
  res[i]表示從0到i的p的子串所對應的滿足需求的子串數目
  continue[i]表示從i向前最長連續的個數
那麼res[i] = res[i-1] + continue[i]
回顧之前的要求,發現它只滿足了前兩點,並不滿足第三點

考慮一個子串可能的情況,前面的推理過程就表達出了一種區分子串的方法:
    根據結尾字符和長度來對子串進行區分
而且一旦結尾字符爲c長度爲l的子串出現,那麼結尾爲c長度爲1-l的子串均已經出現並進行計算了,因而可以得到下面一種方法來防止重複計算
數組f[c]表示以c結尾目前已經出現的最長的連續子串的長度
至此,再對前面的狀態轉移方程進行修改
    res[i] = res[i-1] + (continue[i] > f[p[i]] ? continue[i] - f[p[i]] : 0 )
同時需要對f進行更新
    f[p[i]] = max(continue[i], f[p[i]])

最後考慮其初始狀態,如果p不爲空串以0作爲起始位數,i = 0時:
    res[0] = 1; f[p[0]] = 1
    數組f長度26, 其餘元素均爲0

此時,可以着手開始用代碼實現:

class Solution(object):
    def findSubstringInWraproundString(self, p):
        if not p: return 0
        res = [0] * len(p)
        res[0] = 1
        continue_num = [0] * len(p)
        f = [0] * 26
        f[ord(p[0]) - ord('a')] = 1
        for i in range(1, len(p)):
            if ord(p[i]) == ord(p[i-1]) + 1 or (p[i] == 'a' and p[i-1] == 'z'):
                continue_num[i] = continue_num[i-1] + 1
            else: continue_num[i] = 1

            if continue_num[i] > f[ord(p[i]) - ord('a')]:
                res[i] = res[i-1] + continue_num[i] - f[ord(p[i]) - ord('a')]
                f[ord(p[i]) - ord('a')] = continue_num[i]
            else: res[i] = res[i-1]
        return res[len(p)-1]

經過測試,運算結果正確無誤;但再仔細回顧整個推理過程,發現res和continue僅僅只是依賴於上一次的計算結果,因而完全可以簡化這裏的空間複雜度,得到如下的實現方式

class Solution(object):
    def findSubstringInWraproundString(self, p):
        if not p: return 0
        res = continue_num = 1
        f = [0] * 26
        f[ord(p[0]) - ord('a')] = 1
        for i in range(1, len(p)):
            if ord(p[i]) == ord(p[i-1]) + 1 or (p[i] == 'a' and p[i-1] == 'z'):
                continue_num += 1
            else: continue_num = 1

            if continue_num > f[ord(p[i]) - ord('a')]:
                res += continue_num - f[ord(p[i]) - ord('a')]
                f[ord(p[i]) - ord('a')] = continue_num
        return res

比較起來,代空間複雜度降低的同時還精簡了代碼,使得邏輯更加清晰可讀

複雜度分析,假設p的長度爲n
時間上,僅僅只有一層循環,循環內無嵌套,常數指令數,因而O(n)
空間上,所用空間爲常數,不依賴於任何變量,因而O(1)

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