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. 最长公共子串
全部评论

相关推荐

Flutter&nbsp;快速上手、开发体验、路由和导航https://www.nowcoder.com/issue/tutorial?zhuanlanId=j572L2&amp;amp;uuid=daaad000ae7e4348a2f815e87e8880cc一、Flutter&nbsp;快速上手安装Flutter&nbsp;SDK:首先,需要从Flutter官网下载并安装Flutter&nbsp;SDK。安装完成后,配置环境变量,以便在任何地方都可以使用Flutter命令。创建Flutter项目:使用Flutter&nbsp;CLI(命令行界面)可以轻松创建一个新的Flutter项目。例如,通过运行flutter&nbsp;create&nbsp;myapp来创建一个名为“myapp”的新项目。编写代码:使用Dart语言编写Flutter应用。Dart语言的语法简洁明了,易于上手。开发者可以编辑项目中的Dart文件,实现应用的功能和界面。运行和调试:使用Flutter&nbsp;CLI或集成开发环境(IDE)如Android&nbsp;Studio或Visual&nbsp;Studio&nbsp;Code来运行和调试应用。Flutter支持热重载功能,可以在应用运行时实时查看代码更改的效果,这大大提高了开发效率。二、Flutter&nbsp;开发体验高效的开发流程:Flutter的热重载功能使得开发者能够实时看到代码更改的效果,从而快速迭代和调试应用。此外,Flutter还提供了丰富的组件库和布局选项,使得构建用户界面变得简单而高效。跨平台兼容性:Flutter是一个跨平台的移动UI框架,可以在iOS和Android上构建高质量的原生用户界面。这意味着开发者只需要编写一份代码就可以在多个平台上运行,大大降低了开发成本。社区支持:Flutter拥有一个活跃且庞大的社区,为开发者提供了大量的资源、插件和解决方案。这有助于开发者解决问题并加速开发进程。三、Flutter&nbsp;路由和导航路由管理:在Flutter中,路由管理主要是通过维护一个路由栈来实现的。路由入栈(push)操作对应打开一个新页面,而路由出栈(pop)操作对应页面关闭操作。这类似于原生开发中的导航管理。Navigator&nbsp;Widget:Flutter中的Navigator是一个管理路由的widget,它通过一个栈来管理路由widget集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator提供了一系列方法来管理路由栈,如push和pop操作来进行页面的入栈和出栈。MaterialPageRoute:这是Flutter中常用的一个路由widget,它继承自PageRoute类。MaterialPageRoute提供了与平台页面切换动画风格一致的路由切换动画,使得页面切换更加自然和流畅。综上所述,Flutter提供了高效的开发流程、跨平台兼容性和强大的社区支持,使得开发者能够快速上手并构建出高质量的移动应用程序。同时,Flutter的路由和导航机制也非常灵活和强大,能够满足各种复杂的页面跳转需求。
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务