leetcode
leetcode 1001 ~ 1050
最短公共超序列

最短公共超序列

难度:

标签:

题目描述

代码结果

运行时间: 324 ms, 内存: 391.9 MB


/*
 * 思路: 这个解法与上一个类似,但是我们利用Java的Stream API来处理字符串的拼接部分。
 * 同样地,我们先用动态规划求出最短公共超序列的长度表,然后反向构建结果字符串。
 */
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ShortestCommonSupersequenceStream {
    public String shortestCommonSupersequence(String str1, String str2) {
        int m = str1.length();
        int n = str2.length();
        int[][] dp = new int[m + 1][n + 1];

        // 初始化 dp 数组
        for (int i = 0; i <= m; i++) {
            for (int j = 0; j <= n; j++) {
                if (i == 0) {
                    dp[i][j] = j;
                } else if (j == 0) {
                    dp[i][j] = i;
                } else if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                }
            }
        }

        // 使用 Stream 反向构建结果字符串
        String result = IntStream.iterate(m + n - 1, i -> i >= 0, i -> i - 1)
                .mapToObj(i -> new Object() {
                    int index = i;
                    char ch = (index >= m) ? str2.charAt(index - m) : str1.charAt(index);
                })
                .filter(obj -> {
                    if (obj.index >= m || obj.index >= n) return true;
                    if (str1.charAt(obj.index) == str2.charAt(obj.index)) return true;
                    return dp[m - obj.index - 1][n - obj.index - 1] + 1 == dp[m - obj.index][n - obj.index];
                })
                .map(obj -> String.valueOf(obj.ch))
                .collect(Collectors.joining());

        return result;
    }
}

解释

方法:

此题解采用动态规划先找出两个字符串的最长公共子序列(LCS),然后构建最短公共超序列(SCS)。首先,使用一个二维数组dp,其中dp[i][j]存储s1的前i个字符和s2的前j个字符的LCS。遍历两个字符串,填充dp数组。接着,根据dp数组的内容,从头到尾构建出SCS。在构建SCS的过程中,如果当前字符属于LCS,则添加这个字符到结果中,同时移动两个字符串的指针;如果不是,则分别添加两个字符串的当前字符,直到处理完所有字符。

时间复杂度:

O(l1 * l2)

空间复杂度:

O(l1 * l2)

代码细节讲解

🦆
在动态规划过程中,dp数组的维度是如何确定的,为什么选择使用二维数组?
dp数组的维度是根据两个输入字符串s1和s2的长度来确定的。具体来说,如果s1长度为l1,s2长度为l2,则dp数组的维度为(l1+1)x(l2+1)。这样的维度选择可以保证dp[i][j]能够表示s1的前i个字符和s2的前j个字符的最长公共子序列。使用二维数组是因为这样可以方便地通过下标来访问和更新两个字符串的任意位置的LCS长度,同时便于实现二维动态规划算法。
🦆
题解中提到构建SCS时,如果当前字符不属于LCS,则同时添加两个字符串的当前字符。这种做法是否可能导致结果不是最短的公共超序列?
根据题解的方法,当当前字符不属于LCS时,确实会同时添加两个字符串的当前字符。这种做法可能会导致结果不是最短的公共超序列,因为它可能同时添加两个不同的字符,而不是选择其中一个字符来保持序列尽可能短。为了保证得到真正的最短公共超序列,应当在添加字符时更加谨慎地考虑哪个字符能够最有效地推进序列构建,而不是简单地同时添加两者。
🦆
在计算LCS时,为何选择从后向前遍历字符串进行动态规划填表,而不是从前向后?
在计算LCS时,从后向前遍历字符串可以更方便地利用后面已经计算好的结果来更新当前的dp值。这是因为每个dp[i][j]的值依赖于dp[i+1][j]、dp[i][j+1]和dp[i+1][j+1]的值。如果从后向前遍历,这些依赖的值在更新当前dp[i][j]时已经计算完毕,无需额外的存储空间来保存中间结果。这种方式可以减少计算中的冗余并优化空间使用。
🦆
在构建最短公共超序列的过程中,如何处理两个字符串中的剩余字符,以确保最终结果符合题目要求?
在构建最短公共超序列的过程中,如果在完成LCS的字符添加后,某个或两个字符串还有剩余字符,则需要将这些剩余字符直接添加到结果字符串的末尾。这是因为这些剩余字符并没有在另一个字符串中找到对应的匹配,但它们是构成超序列的必要部分。添加剩余字符的顺序应当与原字符串中的顺序保持一致,以确保结果是正确的超序列。

相关问题