04.06_双序列问题

双序列问题

问题描述

双序列动态规划是指在两个序列上进行状态转移的问题。
常见的有最长公共子序列(LCS)、编辑距离等。
通常需要定义状态表示两个序列某个位置的最优解。

最长公共子序列(LCS)

算法思想

  1. 定义dp[i][j]表示s1的前i个字符和s2的前j个字符的LCS长度
  2. 如果s1[i] == s2[j],则可以选择该字符作为LCS的一部分
  3. 状态转移方程:
    • 当s1[i] == s2[j]时:dp[i][j] = dp[i-1][j-1] + 1
    • 否则:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  4. 最终答案为dp[n][m]

代码实现

int longestCommonSubsequence(string s1, string s2) {
    int n = s1.length(), m = s2.length();
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s1[i-1] == s2[j-1]) {
                dp[i][j] = dp[i-1][j-1] + 1;
            } else {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            }
        }
    }
    
    return dp[n][m];
}
public int longestCommonSubsequence(String s1, String s2) {
    int n = s1.length(), m = s2.length();
    int[][] dp = new int[n + 1][m + 1];
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s1.charAt(i-1) == s2.charAt(j-1)) {
                dp[i][j] = dp[i-1][j-1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
            }
        }
    }
    
    return dp[n][m];
}
def longestCommonSubsequence(s1: str, s2: str) -> int:
    n, m = len(s1), len(s2)
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    
    return dp[n][m]

编辑距离

算法思想

  1. 定义dp[i][j]表示将s1的前i个字符转换为s2的前j个字符所需的最小操作数
  2. 每次可以进行插入、删除或替换操作
  3. 状态转移方程:
    • 当s1[i] == s2[j]时:dp[i][j] = dp[i-1][j-1]
    • 否则:dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
  4. 最终答案为dp[n][m]

代码实现

int minDistance(string s1, string s2) {
    int n = s1.length(), m = s2.length();
    vector<vector<int>> dp(n + 1, vector<int>(m + 1));
    
    // 初始化边界
    for (int i = 0; i <= n; i++) dp[i][0] = i;
    for (int j = 0; j <= m; j++) dp[0][j] = j;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s1[i-1] == s2[j-1]) {
                dp[i][j] = dp[i-1][j-1];
            } else {
                dp[i][j] = min({dp[i-1][j-1], dp[i-1][j], dp[i][j-1]}) + 1;
            }
        }
    }
    
    return dp[n][m];
}
public int minDistance(String s1, String s2) {
    int n = s1.length(), m = s2.length();
    int[][] dp = new int[n + 1][m + 1];
    
    // 初始化边界
    for (int i = 0; i <= n; i++) dp[i][0] = i;
    for (int j = 0; j <= m; j++) dp[0][j] = j;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s1.charAt(i-1) == s2.charAt(j-1)) {
                dp[i][j] = dp[i-1][j-1];
            } else {
                dp[i][j] = Math.min(Math.min(dp[i-1][j-1], dp[i-1][j]), 
                                  dp[i][j-1]) + 1;
            }
        }
    }
    
    return dp[n][m];
}
def minDistance(s1: str, s2: str) -> int:
    n, m = len(s1), len(s2)
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    
    # 初始化边界
    for i in range(n + 1):
        dp[i][0] = i
    for j in range(m + 1):
        dp[0][j] = j
    
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
    
    return dp[n][m]

时间复杂度分析

算法 时间复杂度 空间复杂度
最长公共子序列
编辑距离

应用场景

  1. 序列比较
  2. 文本相似度
  3. DNA序列分析
  4. 拼写检查
  5. 版本控制差异

注意事项

  1. 状态定义的选择
  2. 边界条件的处理
  3. 空间优化的可能性
  4. 路径还原的需求
  5. 初始化的正确性

经典例题

  1. 最长公共子序列
  2. 编辑距离
  3. 最长公共子串
全部评论

相关推荐

评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务