这里我们考虑用 s1和 s2的某个前缀是否能形成 s3 的一个前缀。
这个方法的前提建立于:判断一个 s3的前缀(用下标 k表示),能否用 s1和 s2 的前缀(下标分别为 i和 j),仅仅依赖于 s1 前 i个字符和 s2 前 j个字符,而与后面的字符无关。
为了实现这个算法, 我们将使用一个 2D 的布尔数组 dp 。dp[i][j]表示用 s1的前 i和 s2的前 j个字符,总共 i+j个字符,是否交错构成 s3的前缀。为了求出 dp[i][j] ,我们需要考虑 两 种情况:
s1的第 i 个字符和 s2 的第 j 个字符都不能匹配 s3的第 k 个字符,其中 k=i + j 。这种情况下,s1和 s2的前缀无法交错形成 s3长度为 k 的前缀。因此,我们让 dp[i][j]为 False。
s1的第 i 个字符或者 s2的第 j 个字符可以匹配 s3的第 k个字符,其中 k=i+j 。假设匹配的字符是 x 且与 s1的第 i 个字符匹配,我们就需要把 x放在已经形成的交错字符串的最后一个位置。此时,为了我们必须确保 s1的前 (i-1)个字符和 s2的前 j 个字符能形成 s3的一个前缀。类似的,如果我们将 s2的第 j个字符与 s3的第 k 个字符匹配,我们需要确保 s1的前 i 个字符和 s2的前 (j-1) 个字符能形成 s3的一个前缀,我们就让 dp[i][j]为True 。
public class Solution {public bool IsInterleave(string s1, string s2, string s3){if((s1.Length + s2.Length) != s3.Length)return false;bool[,] dp = new bool[s1.Length + 1, s2.Length + 1];for(int i = 0; i <= s1.Length; i++){for(int j = 0; j <= s2.Length; j++){if(i == 0 && j == 0)dp[i, j] = true;else if(i == 0)dp[i, j] = dp[i, j - 1] && s2[j - 1] == s3[i+ j - 1];else if(j == 0)dp[i, j] = dp[i - 1, j] && s1[i - 1] == s3[i+ j - 1];elsedp[i, j] = (dp[i - 1, j] && s1[i - 1] == s3[i+ j - 1]) || dp[i, j - 1] && s2[j - 1] == s3[i+ j - 1];}}return dp[s1.Length, s2.Length];}
}
复杂度分析
时间复杂度:O(m \cdot n)O(m⋅n) 。计算 dpdp 数组需要 m*nm∗n 的时间。
空间复杂度:O(m \cdot n)O(m⋅n)。2 维的 dpdp 数组需要 (m+1)*(n+1)(m+1)∗(n+1) 的空间。 mm 和 nn 分别是 s1s1 和 s2s2 字符串的长度。
优化:使用一维动态规划
这种方法与前一种方法基本一致,除了我们仅使用一维 dp数组去储存前缀结果。我们利用 dp[i-1]的结果和 dp[i]之前的结果来计算 dp[i],即滚动数组。
public bool IsInterleave(string s1, string s2, string s3){if((s1.Length + s2.Length) != s3.Length)return false;bool[] dp = new bool[s2.Length + 1];for(int i = 0; i <= s1.Length; i++){for(int j = 0; j <= s2.Length; j++){if(i==0 && j == 0)dp[j] = true;else if(i == 0)dp[j] = dp[j - 1] && s2[j - 1] == s3[i+ j - 1];else if(j == 0)dp[j] = dp[j] && s1[i - 1] == s3[i+ j - 1];elsedp[j] = (dp[j] && s1[i - 1] == s3[i+ j - 1]) || dp[j - 1] && s2[j - 1] == s3[i+ j - 1];}}return dp[s2.Length];}
复杂度分析
时间复杂度:O(m⋅n);长度为 n 的 dp数组需要被填充 m 次。
空间复杂度:O(n);n是字符串 s1的长度