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问题就介绍这么几个很典型的题目了。

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