10.5.2 (python) 動態規劃字符串類LeetCode題目 —— Interleaving String & Distinct Subsequences

下面我們看兩道字符串子序列的問題 subsequence,首先明白子序列是不必連續的。

97. Interleaving String

Given s1s2s3, find whether s3 is formed by the interleaving of s1 and s2.

Example 1:

Input: s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
Output: true

題目解析:

先處理一些特殊情況。

解法參考https://leetcode.com/problems/interleaving-string/discuss/544593/Python-faster-than-97.40

首先理解dp變量的含義,在for循環中dp代表主串s3在當前位置i處s3[:i]的解決方案,所用的s1和s2的位置,比如3: {(1, 1), (2, 0)},當aadb時,有兩種方案,或者是 aa + db ,或者是 aab + d。

狀態轉移來說,要根據dp[i-1]的解決方案和當前字符,來生成dp[i]的解決方案,看代碼即可。

下面的代碼做了簡化,dp是一維的,dp中的元素,代表S1和S2的解決方案的位置,做成字符串,方便dp集合做哈希處理。

最後結果只要dp中有一種解決方案即可。

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        la, lb = len(s1), len(s2)
        lc = len(s3)
        
        if la + lb != lc:
            return False
        if la == 0:
            return s2 == s3
        if lb == 0:
            return s1 == s3
        
        dp = set()
        dp.add("0 0")
        for i in range(lc):
            char = s3[i]
            dp_ = set()            
            for s in dp:
                x, y = map(int, s.split(" "))
                if x < la and s1[x] == char:
                    dp_.add("%d %d" % (x+1, y))
                if y < lb and s2[y] == char:
                    dp_.add("%d %d" % (x, y+1))
            dp = dp_
        return len(dp) > 0

 

115. Distinct Subsequences

Given a string S and a string T, count the number of distinct subsequences of S which equals T.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE" is a subsequence of "ABCDE" while "AEC" is not).

題目解析:

也是子序列的問題,鄙人寫的代碼如下,比較慢,DP的思想比較弱,屬於常規思維。dp的第一維代表主串S的每個位置的信息,信息即爲S串對應T串的解決方案,思路與上題類似。最終dp[i][-1]代表T串完成一個解決方案,累加即可。

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        ls = len(s)
        lt = len(t)        
        if ls < lt:
            return 0
        if ls == lt:
            return 1 if s == t else 0
        
        dp = [[] for _ in range(ls)]
        max_num = 0
        t_dict = dict()
        for ix, char in enumerate(t):
            t_dict[char] = t_dict.get(char, [])
            lst = t_dict[char]
            lst.append(ix)

        for i in range(ls):
            char = s[i]
            dp[i] = [0 for _ in range(lt)]
            idx_lst = t_dict.get(char, [])
            if len(idx_lst) == 0:
                continue
            for j in range(i):
                lst = dp[j]                
                # lst代表s串每個位置的信息
                for ix in idx_lst:
                    num = lst[ix-1]
                    # info代表與t串對應的位置索引和次數
                    if num == 0:
                        continue
                    dp[i][ix] += num
            if char == t[0]:
                dp[i][0] = 1
            max_num += dp[i][-1]

        return max_num  

上面的方法太慢,800+ms。下面纔是DP的思想。首先,建立T串的字典,字符及其索引的map;dp的長度爲T的長度,然後我們要遍歷S,看當前字符的索引位置,有的話則索引idx代表的子串t[:idx]的解決方案就等於t[:idx-1]的解決方案的次數,首字母直接+1。自己需要理解一下。另外注意索引的遍歷要從大到小,原因可以rabbit爲例看一下。

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        ls = len(s)
        lt = len(t)        
        if ls < lt:
            return 0
        if ls == lt:
            return 1 if s == t else 0
        
        t_dict = dict()
        for ix, char in enumerate(t):
            t_dict[char] = t_dict.get(char, [])
            lst = t_dict[char]
            lst.append(ix)
        dp = [0 for _ in range(lt)]
        for char in s:
            idx_lst = t_dict.get(char, [])
            if len(idx_lst) == 0:
                continue
            for idx in idx_lst[::-1]:
                if idx == 0:
                    dp[0] += 1
                else:
                    dp[idx] += dp[idx-1]

        return dp[-1]

字符串的DP問題就介紹這麼幾個很典型的題目了。

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