置頂

我的 VPython 教學文件 (HackMD 版本)

VPython 教學文件目錄 安裝及測試 基本語法 等速度直線運動 自由落下 終端速度 水平抛射 使用For迴圈計算水平抛射資料 斜向抛射 圓周運動 簡諧運動 單擺 木塊彈簧系統分離 重力及簡諧 行星運動 相疊木塊 雙重簡諧運動 一維彈性碰撞 ...

熱門文章

2026年9月6日 星期日

LeetCode 解題筆記:115. Distinct Subsequences

作者:王一哲
日期:2026年9月6日


LeetCode 題目連結:115. Distinct Subsequences

解題想法


困難題。題目給兩個字串 $s$、$t$,要計算 $s$ 的子字串之中有幾個等於 $t$,字串長度最長為 $1000$,要用動態規畫解題。

假設 $s$ 的長度為 $m$,$t$ 的長度為 $n$,開一個長度為 $(m+1) \times (n+1)$ 的二維陣列 $dp$,初始值先設為 0,$dp[i][0] = 1$,$dp[i][j]$ 代表檢查到 $s[i-1]$ 及 $t[j-1]$ 時,$s[0]$ 到 $s[i-1]$ 共有幾個子字串等於 $t[0:j]$。用兩層 for 迴圈更新 $dp$,外層跑 $i = 1$ 到 $i = m$,內層跑 $j = 1$ 到 $i = n$,如果 $s[i-1] == t[j-1]$,$dp[i][j] = dp[i-1][j-1] + dp[i-1][j]$;反之,$dp[i][j] = dp[i-1][j]$。由於更新時只需要用到前一次的狀態,可以用滾動陣列節省記憶體。由於答案很大,如果用 C 或 C++ 解題,$dp$ 的格式要用 unsigned long 才不會溢位。

Python 程式碼


二維陣列。Runtime: 419 ms, beats 49.82%. Memory: 75.56 MB, beats 56.33%.
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # 動態規畫,dp[i][j] 代表 s[0:i] 範圍內可以組合出等於 t[0:j] 的子字串數量
        m, n = len(s),  len(t)
        dp = [[1] + [0]*n for _ in range(m+1)]
        for i in range(1, m+1):
            for j in range(1, n+1):
                if s[i-1] == t[j-1]:
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
                else:
                    dp[i][j] = dp[i-1][j]
        return dp[-1][-1]


滾動陣列。Runtime: 213 ms, beats 85.86%. Memory: 19.53 MB, beats 82.38%.
class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        # 動態規畫,prev[j] 代表 s 之中等於 t[0:j] 的子字串數量
        m, n = len(s),  len(t)
        prev = [1] + [0]*n  # 前一個狀態,prev[0] = 1,空字串
        for c in s:  # 由 s 依序取出字母
            curr = [1] + [0]*n # 現在的狀態 curr[0] = 1,空字串
            for j in range(1, n+1):  # 掃過 t 的每個字母
                if t[j-1] == c:  # 如果 t[j-1] 等於 c
                    curr[j] = prev[j-1] + prev[j]  # 長度為前一個狀態索引值 j-1, j 相加
                else:  # 反之,繼承 prev[j]
                    curr[j] = prev[j]
            prev, curr = curr, prev  # 交換資料
        return prev[-1]  # 答案在 prev 最後一項


C++ 程式碼


二維陣列。Runtime: 30 ms, beats 58.87%. Memory: 44.40 MB, beats 20.60%.
class Solution {
public:
    int numDistinct(string s, string t) {
        // 動態規畫,dp[i][j] 代表 s[0:i] 範圍內可以組合出等於 t[0:j] 的子字串數量
        int m = (int)s.size(), n = (int)t.size();
        vector<vector<unsigned long>> dp (m+1, vector<unsigned long> (n+1, 0));
        for(int i = 0; i <= m; i++) {
            dp[i][0] = 1;
        }
        // 掃過 s 及 t,更新 dp,答案在 dp[m][n]
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if (s[i-1] == t[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                } else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[m][n];
    }
};


滾動陣列。Runtime: 23 ms, beats 77.66%. Memory: 43.63 MB, beats 41.76%.
class Solution {
public:
    int numDistinct(string s, string t) {
        // 動態規畫,prev[j] 代表前一個狀態中以組合出等於 t[0:j] 的子字串數量
        int m = (int)s.size(), n = (int)t.size();
        vector<unsigned long> prev (n+1, 0);
        prev[0] = 1;
        // 掃過 s 及 t,更新 dp,答案在 prev[n]
        for(int i = 1; i <= m; i++) {
            vector<unsigned long> curr (n+1, 0);
            curr[0] = 1;
            for(int j = 1; j <= n; j++) {
                if (s[i-1] == t[j-1]) {
                    curr[j] = prev[j-1] + prev[j];
                } else {
                    curr[j] = prev[j];
                }
            }
            swap(prev, curr);
        }
        return prev[n];
    }
};


C 語言程式碼


Runtime: 36 ms, beats 6.32%. Memory: 16.64 MB, beats 30.86%.
int numDistinct(char* s, char* t) {
    int m = strlen(s), n = strlen(t);
    unsigned long dp[1001][1001];
    memset(dp, 0, sizeof(dp));
    for(int i = 0; i <= m; i++) {
        dp[i][0] = 1;
    }
    
    for(int i = 1; i <= m; i++) {
        for(int j = 1; j <= n; j++) {
            if (s[i-1] == t[j-1]) {
                dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
            } else {
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    return dp[m][n];
}


沒有留言:

張貼留言