1. 程式人生 > >Leetcode 115:不同的子序列(最詳細的解法!!!)

Leetcode 115:不同的子序列(最詳細的解法!!!)

給定一個字串 S 和一個字串 T,計算在 S 的子序列中 T 出現的個數。

一個字串的一個子序列是指,通過刪除一些(也可以不刪除)字元且不干擾剩餘字元相對位置所組成的新字串。(例如,"ACE""ABCDE" 的一個子序列,而 "AEC" 不是)

示例 1:

輸入: S = "rabbbit", T = "rabbit"
輸出: 3
解釋:

如下圖所示, 有 3 種可以從 S 中得到 "rabbit" 的方案。
(上箭頭符號 ^ 表示選取的字母)

rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

示例 2:

輸入: S = "babgbag", T = "bag"
輸出: 5
解釋:

如下圖所示, 有 5 種可以從 S 中得到 "bag" 的方案。 
(上箭頭符號 ^ 表示選取的字母)

babgbag
^^ ^
babgbag
^^    ^
babgbag
^    ^^
babgbag
  ^  ^^
babgbag
    ^^^

解題思路

我們先進行一些準備工作。

s_len, t_len, result = len(s), len(t), 0
if s_len < t_len:
    return result

if s_len == t_len:
    if s == t:
        return 1
    return result

首先你一定可以想到這樣的暴力解法。從S中選出len(T)大小的全部組合,然後判斷這些組合中有多少個和T是一樣的。

for i in combinations(s, t_len):
    new_t = "".join(i)
    if new_t ==
t: result += 1

這種匹配問題,我們很容易想到通過動態規劃來做。但是如果我們直接思考遞迴過程,很難想出具體的遞推公式。對於兩個字串的比較,在我們沒有其他辦法的時候,我們不妨先建立一個二維的陣列,觀察一下兩個字串的匹配情況

  Ø r a b b b i t
Ø 1 1 1 1 1 1 1 1
r 0 1 1 1 1 1 1 1
a 0 0 1 1 1 1 1 1
b 0 0 0 1 2 3 3 3
b 0 0 0 0 1 3 3 3
i 0 0 0 0 0 0 3 3
t 0 0 0 0 0 0 0 3 

首先說明一下上面這個矩陣的含義,橫向表示S,縱向表示T,每個元素表示當前橫座標向左的S字串最多可以匹配幾次當前縱座標向上的T

字串。舉個例子,對於座標3,4,此時S=rabb,而T=rab,所以可以匹配兩次。觀察上面這個矩陣,我們可以的到這樣的規律,假設我們此時的座標是(i,j),如果S[j]==T[i],我們的矩陣mem[i+1][j+1]=mem[i][j]+mem[i+1][j],否則的話mem[i+1][j+1]=mem[i+1][j]

class Solution:
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        s_len, t_len = len(s), len(t)
        mem = [[0]*(s_len+1) for _ in range(t_len+1)]
        for i in range(s_len+1):
            mem[0][i] = 1

        for i in range(t_len):
            for j in range(s_len):
                if s[j] == t[i]:
                    mem[i+1][j+1] = mem[i][j] + mem[i+1][j]
                else:
                    mem[i+1][j+1] = mem[i+1][j]

        return mem[-1][-1]

如果我們從另外一個角度去看這個問題,我們按照遞迴的思路去考慮,每次遍歷s的一個元素,然後思考t的字首在s[:i]中出現的次數,我們會得到這樣的矩陣

r a b b i t 
0 0 0 0 0 0 Ø
1 0 0 0 0 0 r
1 1 0 0 0 0 a
1 1 1 0 0 0 b
1 1 2 1 0 0 b
1 1 3 3 0 0 b
1 1 3 3 3 0 i
1 1 3 3 3 3 t

我們不難發現這樣的規律,我們每次計算新的一行當中的元素,假設此時的座標是i,j,如果s[i]==t[j],我們此時的第j+1列的值,會等於原來j+1列的值再加上原來j列的值,也就是↘和↓相加。

class Solution:
    def numDistinct(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: int
        """
        t_len = len(t)
        mem = [1]+[0]*t_len
        for s_c in s:
            for i in range(t_len-1, -1, -1):
                if t[i] == s_c:
                    mem[i+1] += mem[i]

        return mem[-1]

是不是很酷!!!

我將該問題的其他語言版本新增到了我的GitHub Leetcode

如有問題,希望大家指出!!!