多语言详解从「暴搜」到「记忆化搜索」再到「线性 DP(及其进阶)」

题目描述

这是 LeetCode 上的 97. 交错字符串 ,难度为 中等

Tag : 「线性 DP」、「记忆化搜索」

给定三个字符串 s1s2s3,请你帮忙验证 s3 是否是由 s1s2 交错 组成的。

两个字符串 st 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

  • s = s1 + s2 + ... + sn
  • t = t1 + t2 + ... + tm
  • |n - m| <= 1

交错是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...

注意:a + b 意味着字符串 ab 连接。

示例 1:

ini 复制代码
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"

输出:true

示例 2:

ini 复制代码
输入:s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"

输出:false

示例 3:

ini 复制代码
输入:s1 = "", s2 = "", s3 = ""

输出:true

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 < = s 1. l e n g t h , s 2. l e n g t h < = 100 0 <= s1.length, s2.length <= 100 </math>0<=s1.length,s2.length<=100
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 < = s 3. l e n g t h < = 200 0 <= s3.length <= 200 </math>0<=s3.length<=200
  • s1s2、和 s3 都由小写英文字母组成

进阶:您能否仅使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( s 2. l e n g t h ) O(s2.length) </math>O(s2.length) 额外的内存空间来解决它?

记忆化搜索

数据范围相比于「暴搜」而言有点大,但将「暴搜」解法作为前置思考,总能为我们带来灵感。

s1s2s3 的长度分别记为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n、 <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l。

一个显然的情况是若 <math xmlns="http://www.w3.org/1998/Math/MathML"> n + m n + m </math>n+m 不为 <math xmlns="http://www.w3.org/1998/Math/MathML"> l l </math>l,必然不能用 s1s2 来凑成 s3,返回 false

定义暴搜函数为 boolean dfs(int i, int j),代表当前处理到 s1 的第 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i 个字符,s2 的第 <math xmlns="http://www.w3.org/1998/Math/MathML"> j j </math>j 个字符,能否凑成 s3 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i + j i + j </math>i+j 个字符。

最终答案为 dfs(0, 0)

根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i]、 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i + j] </math>s3[i+j] 的关系分情况讨论:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] = s 3 [ i + j ] s1[i] = s3[i + j] </math>s1[i]=s3[i+j],可使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i] 充当 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i + j] </math>s3[i+j] ,暴搜 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i] 已被使用的情况,决策下一位 dfs(i + 1, j)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] = s 3 [ i + j ] s2[j] = s3[i + j] </math>s2[j]=s3[i+j],可使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 充当 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i + j] </math>s3[i+j] ,暴搜 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 已被使用的情况,决策下一位dfs(i, j + 1)

当 <math xmlns="http://www.w3.org/1998/Math/MathML"> i + j = l i + j = l </math>i+j=l 时,代表我们成功用 s1s2 凑成了 s3,返回 true;而若在任一回合出现 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] ≠ s 3 [ i + j ] s1[i] \neq s3[i + j] </math>s1[i]=s3[i+j] 且 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] ≠ s 3 [ i + j ] s2[j] \neq s3[i + j] </math>s2[j]=s3[i+j],说明构造无法进行,返回 false

TLE Java 代码:

Java 复制代码
class Solution {
    char[] cs1, cs2, cs3;
    int n, m, l;
    public boolean isInterleave(String s1, String s2, String s3) {
        cs1 = s1.toCharArray(); cs2 = s2.toCharArray(); cs3 = s3.toCharArray();
        n = s1.length(); m = s2.length(); l = s3.length();
        if (n + m != l) return false;
        return dfs(0, 0);
    }
    boolean dfs(int i, int j) {
        if (i + j == l) return true;
        boolean ans = false;
        if (i < n && cs1[i] == cs3[i + j]) ans |= dfs(i + 1, j);
        if (j < m && cs2[j] == cs3[i + j]) ans |= dfs(i, j + 1);
        return ans;
    }
}

可分析上述 TLE 做法的时间复杂度上界为交错序列个数。

长度分别为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 形成的交错序列数量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> C ( n + m , n ) = ( n + m ) ! n ! × m ! C(n+m, n) = \frac{(n+m)!}{n! \times m!} </math>C(n+m,n)=n!×m!(n+m)! : 从 <math xmlns="http://www.w3.org/1998/Math/MathML"> n + m n + m </math>n+m 个位置中选 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 个位置按顺序放置 s1,剩下的 <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m 个位置唯一确定的放置 s2

实际上,不同交错序列之间有着相同的前缀(例如 s1 = aacs2 = bbd,具体交错方案中的 aabbcdaabbdc 之间有着相同的前缀 aabb__),可通过「记忆化搜索」来进行减少这些重复的构造。

直接根据 dfs 函数的入参构建缓存器 cache,起始 cache[i][j] = 0;若 cache[i][j] = 1 代表 truecache[i][j] = -1 代表 false

如此简单的改动,即可将朴素的 DFS 改成记忆化搜索。

Java 代码:

Java 复制代码
class Solution {
    char[] cs1, cs2, cs3;
    int n, m, l;
    int[][] cache;
    public boolean isInterleave(String s1, String s2, String s3) {
        cs1 = s1.toCharArray(); cs2 = s2.toCharArray(); cs3 = s3.toCharArray();
        n = s1.length(); m = s2.length(); l = s3.length();
        if (n + m != l) return false;
        cache = new int[n + 10][m + 10];
        return dfs(0, 0);
    }
    boolean dfs(int i, int j) {
        if (cache[i][j] != 0) return cache[i][j] == 1;
        if (i + j == l) return true;
        boolean ans = false;
        if (i < n && cs1[i] == cs3[i + j]) ans |= dfs(i + 1, j);
        if (j < m && cs2[j] == cs3[i + j]) ans |= dfs(i, j + 1);
        cache[i][j] = ans ? 1 : -1;
        return ans;
    }
}

C++ 代码:

C++ 复制代码
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n = s1.length(), m = s2.length(), l = s3.length();
        if (n + m != l) return false;
        vector<vector<int>> cache(n + 10, vector<int>(m + 10, 0));
        function<bool(int, int)> dfs = [&](int i, int j) {
            if (cache[i][j] != 0) return cache[i][j] == 1;
            if (i + j == l) return true;
            bool ans = false;
            if (i < n && s1[i] == s3[i + j]) ans |= dfs(i + 1, j);
            if (j < m && s2[j] == s3[i + j]) ans |= dfs(i, j + 1);
            cache[i][j] = ans ? 1 : -1;
            return ans;
        };
        return dfs(0, 0);
    }
};

Python 代码:

Python 复制代码
class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        n, m, l = len(s1), len(s2), len(s3)
        if n + m != l:
            return False
        @lru_cache
        def dfs(i, j):
            if i + j == l:
                return True
            ans = False
            if i < n and s1[i] == s3[i + j]:
                ans |= dfs(i + 1, j)
            if j < m and s2[j] == s3[i + j]:
                ans |= dfs(i, j + 1)
            return ans
        return dfs(0, 0)

TypeScript 代码:

TypeScript 复制代码
function isInterleave(s1: string, s2: string, s3: string): boolean {
    const n = s1.length, m = s2.length, l = s3.length;
    if (n + m !== l) return false;
    const cache = new Array(n + 10).fill(0).map(() => new Array(m + 10).fill(0));
    const dfs = (i: number, j: number): boolean => {
        if (cache[i][j] !== 0) return cache[i][j] === 1;
        if (i + j === l) return true;
        let ans = false;
        if (i < n && s1[i] === s3[i + j]) ans ||= dfs(i + 1, j);
        if (j < m && s2[j] === s3[i + j]) ans ||= dfs(i, j + 1);
        cache[i][j] = ans ? 1 : -1;
        return ans;
    };
    return dfs(0, 0);
};
  • 时间复杂度:共有 <math xmlns="http://www.w3.org/1998/Math/MathML"> n × m n \times m </math>n×m 个状态,复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n × m ) O(n \times m) </math>O(n×m)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n × m ) O(n \times m) </math>O(n×m)

线性 DP

直接将「记忆化搜索」中的缓存器修改为我们的动态规划状态定义。

定义 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 为使用 s1 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i 个字符,使用 s2 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> j j </math>j 个字符,能否凑出 s3 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i + j i + j </math>i+j 个字符

为了方便,我们令所有字符串的下标均从 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1 开始。

不失一般性,考虑 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 如何转移,根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i]、 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i+j] </math>s3[i+j] 是否相等进行分析,即根据当前 s3 的最后一个字符 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i + j] </math>s3[i+j] 由 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 谁来提供进行讨论

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] = s 3 [ i + j ] s1[i] = s3[i + j] </math>s1[i]=s3[i+j]:说明当前 s3 的最后一个字符 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i + j] </math>s3[i+j] 可由 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 1 [ i ] s1[i] </math>s1[i] 提供,而 s3 此前的 <math xmlns="http://www.w3.org/1998/Math/MathML"> i + j − 1 i + j - 1 </math>i+j−1 个字符,可由 s1 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i − 1 i - 1 </math>i−1 字符和 s2 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> j j </math>j 个字符共同提供。此时若 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i − 1 ] [ j ] f[i - 1][j] </math>f[i−1][j] 为真,则有 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 为真。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] = s 3 [ i + j ] s2[j] = s3[i+j] </math>s2[j]=s3[i+j]:说明当前 s3 的最后一个字符串 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 3 [ i + j ] s3[i+j] </math>s3[i+j] 可由 <math xmlns="http://www.w3.org/1998/Math/MathML"> s 2 [ j ] s2[j] </math>s2[j] 提供,而 s3 此前的 <math xmlns="http://www.w3.org/1998/Math/MathML"> i + j − 1 i + j - 1 </math>i+j−1 个字符,可由 s1 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i 字符和 s2 的前 <math xmlns="http://www.w3.org/1998/Math/MathML"> j − 1 j - 1 </math>j−1 个字符共同提供。此时若 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j − 1 ] f[i][j - 1] </math>f[i][j−1] 为真,则有 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 为真。

综上,只有上述条件任一成立, <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 即为真。

一些细节:为了在转移过程中减少边界处理,我们先预处理出 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ 0 ] [ X ] f[0][X] </math>f[0][X] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ X ] [ 0 ] f[X][0] </math>f[X][0] 的状态值(即 s1s2s3 之间的公共前缀)。

Java 代码:

Java 复制代码
class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        char[] cs1 = s1.toCharArray(), cs2 = s2.toCharArray(), cs3 = s3.toCharArray();
        int n = s1.length(), m = s2.length(), l = s3.length();
        if (n + m != l) return false;
        boolean[][] f = new boolean[n + 10][m + 10];
        f[0][0] = true;
        for (int i = 1; i <= n && f[i - 1][0]; i++) f[i][0] = cs1[i - 1] == cs3[i - 1];
        for (int i = 1; i <= m && f[0][i - 1]; i++) f[0][i] = cs2[i - 1] == cs3[i - 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (cs1[i - 1] == cs3[i + j - 1]) f[i][j] |= f[i - 1][j];
                if (cs2[j - 1] == cs3[i + j - 1]) f[i][j] |= f[i][j - 1];
            }
        }
        return f[n][m];
    }
}

Python 代码:

Python 复制代码
class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        n, m, l = len(s1), len(s2), len(s3)
        if n + m != l:
            return False
        f = [[False] * (m + 10) for _ in range(n + 10)]
        f[0][0] = True
        for i in range(1, n + 1):
            f[i][0] = f[i - 1][0] and s1[i - 1] == s3[i - 1]
        for i in range(1, m + 1):
            f[0][i] = f[0][i - 1] and s2[i - 1] == s3[i - 1]
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                if s1[i - 1] == s3[i + j - 1]:
                    f[i][j] |= f[i - 1][j]
                if s2[j - 1] == s3[i + j - 1]:
                    f[i][j] |= f[i][j - 1]
        return f[n][m]

C++ 代码:

C++ 复制代码
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n = s1.length(), m = s2.length(), l = s3.length();
        if (n + m != l) return false;
        vector<vector<bool>> f(n + 10, vector<bool>(m + 10, false));
        f[0][0] = true;
        for (int i = 1; i <= n && f[i - 1][0]; i++) f[i][0] = (s1[i - 1] == s3[i - 1]);
        for (int i = 1; i <= m && f[0][i - 1]; i++) f[0][i] = (s2[i - 1] == s3[i - 1]);
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (s1[i - 1] == s3[i + j - 1]) f[i][j] = f[i][j] | f[i - 1][j];
                if (s2[j - 1] == s3[i + j - 1]) f[i][j] = f[i][j] | f[i][j - 1];
            }
        }
        return f[n][m];
    }
};

TypeScript 代码:

TypeScript 复制代码
function isInterleave(s1: string, s2: string, s3: string): boolean {
    const n = s1.length, m = s2.length, l = s3.length;
    if (n + m !== l) return false;
    const f = new Array(n + 10).fill(false).map(() => new Array(m + 10).fill(false));
    f[0][0] = true;
    for (let i = 1; i <= n && f[i - 1][0]; i++) f[i][0] = s1[i - 1] === s3[i - 1];
    for (let i = 1; i <= m && f[0][i - 1]; i++) f[0][i] = s2[i - 1] === s3[i - 1];
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= m; j++) {
            if (s1[i - 1] === s3[i + j - 1]) f[i][j] ||= f[i - 1][j];
            if (s2[j - 1] === s3[i + j - 1]) f[i][j] ||= f[i][j - 1];
        }
    }
    return f[n][m];
};
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n × m ) O(n \times m) </math>O(n×m)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n × m ) O(n \times m) </math>O(n×m)

进阶

根据状态转移方程可知 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j ] f[i][j] </math>f[i][j] 仅依赖于 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i − 1 ] [ j ] f[i - 1][j] </math>f[i−1][j] 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ i ] [ j − 1 ] f[i][j - 1] </math>f[i][j−1],因此可通过「滚动数组」的方式进行优化。

状态定义不变,将动规数组开成 <math xmlns="http://www.w3.org/1998/Math/MathML"> f [ 2 ] [ m + 10 ] f[2][m + 10] </math>f[2][m+10] 大小(另外一维的 +10 操作仅为个人习惯)。

随后,当我们需要更新 f[i][X] 时,需要写成 f[i & 1][X];需要访问 f[i - 1][X] 时,则写成 f[(i - 1) & 1][X] 即可。

最后,同样是为了转移过程中减少边界处理,我们需要使用两个变量 ab 记住 s1s2s3 的公共前缀,并特判掉 s1s2 为空的情况。

Java 代码:

Java 复制代码
class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        char[] cs1 = s1.toCharArray(), cs2 = s2.toCharArray(), cs3 = s3.toCharArray();
        int n = s1.length(), m = s2.length(), l = s3.length();
        if (n + m != l) return false;
        int a = 0, b = 0;
        while (a < n && cs1[a] == cs3[a]) a++;
        while (b < m && cs2[b] == cs3[b]) b++;
        if (m == 0) return n <= a;
        if (n == 0) return m <= b;
        boolean[][] f = new boolean[2][m + 10];
        f[0][0] = true;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                boolean cur = false;
                if (cs1[i - 1] == cs3[i + j - 1]) {
                    if (i == 1) cur |= j <= b;
                    else cur |= f[(i - 1) & 1][j];
                }
                if (cs2[j - 1] == cs3[i + j - 1]) {
                    if (j == 1) cur |= i <= a;
                    else cur |= f[i & 1][j - 1];
                }
                f[i & 1][j] = cur;
            }
        }
        return f[n & 1][m];
    }
}

C++ 代码:

C++ 复制代码
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n = s1.length(), m = s2.length(), l = s3.length();
        if (n + m != l) return false;
        int a = 0, b = 0;
        while (a < n && s1[a] == s3[a]) a++;
        while (b < m && s2[b] == s3[b]) b++;
        if (n == 0) return m <= b;
        if (m == 0) return n <= a;
        vector<vector<bool>> f(2, vector<bool>(m + 1, false));
        f[0][0] = true;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                bool cur = false;
                if (s1[i - 1] == s3[i + j - 1]) {
                    if (i == 1) cur |= j <= b;    
                    else cur |= f[(i - 1) & 1][j];
                }
                if (s2[j - 1] == s3[i + j - 1]) {
                    if (j == 1) cur |= i <= a;
                    else cur |= f[i & 1][j - 1]; 
                }
                f[i & 1][j] = cur;
            }
        }
        return f[n & 1][m];
    }
};

Python 代码:

Python 复制代码
class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        n, m, l = len(s1), len(s2), len(s3)
        if n + m != l:
            return False
        a, b = 0, 0
        while a < n and s1[a] == s3[a]:
            a += 1
        while b < m and s2[b] == s3[b]:
            b += 1
        if m == 0:
            return n <= a
        if n == 0:
            return m <= b
        f = [[False for _ in range(m + 1)] for _ in range(2)]
        f[0][0] = True
        for i in range(1, n+1):
            for j in range(1, m+1):
                cur = False
                if s1[i - 1] == s3[i + j - 1]:
                    if i == 1:
                        cur |= j <= b
                    else:
                        cur |= f[(i - 1) & 1][j]
                if s2[j - 1] == s3[i + j - 1]:
                    if j == 1:
                        cur |= i <= a
                    else:
                        cur |= f[i & 1][j - 1]
                f[i & 1][j] = cur
        return f[n & 1][m]

TypeScript 代码:

TypeScript 复制代码
function isInterleave(s1: string, s2: string, s3: string): boolean {
    const n = s1.length, m = s2.length, l = s3.length;
    if (n + m !== l) return false;
    let a = 0, b = 0;
    while (a < n && s1[a] === s3[a]) a++;
    while (b < m && s2[b] === s3[b]) b++;
    if (m == 0) return n <= a;
    if (n == 0) return m <= b;
    const f = new Array(2).fill(null).map(() => new Array(m + 1).fill(false));
    f[0][0] = true;
    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= m; j++) {
            let cur = false;
            if (s1[i - 1] === s3[i + j - 1]) {
                if (i === 1) cur ||= j <= b;    
                else cur ||= f[(i - 1) & 1][j];
            }
            if (s2[j - 1] === s3[i + j - 1]) {
                if (j === 1) cur ||= i <= a;    
                else cur ||= f[i & 1][j - 1];
            }
            f[i & 1][j] = cur;
        }
    }
    return f[n & 1][m];
};
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n × m ) O(n \times m) </math>O(n×m)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( m ) O(m) </math>O(m)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.97 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour...

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
骑士雄师6 分钟前
Java 泛型中级面试题及答案
java·开发语言·面试
Victor3568 分钟前
Redis(61)Redis的连接数上限是多少?
后端
Victor35611 分钟前
Redis(60) Redis的复制延迟如何优化?
后端
.格子衫.6 小时前
Spring Boot 原理篇
java·spring boot·后端
多云几多6 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
Swift社区7 小时前
LeetCode 394. 字符串解码(Decode String)
算法·leetcode·职场和发展
tt5555555555558 小时前
LeetCode进阶算法题解详解
算法·leetcode·职场和发展
让我们一起加油好吗8 小时前
【基础算法】DFS中的剪枝与优化
算法·深度优先·剪枝
Jabes.yang8 小时前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
聪明的笨猪猪8 小时前
Java Redis “高可用 — 主从复制”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试