算法系列博客之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)