51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

实现一个函数,计算两个字符串的最长公共子序列(LCS),并分析算法的时间复杂度和空间复杂度。请说明动态规划的状态转移方程以及如何优化空间复杂度。

微软Software Engineer Intern难度:中等

答案

1) 【一句话结论】计算两个字符串的最长公共子序列(LCS)问题,可通过动态规划解决,核心是定义前缀字符串的LCS长度状态,状态转移方程为匹配则加1、不匹配取最大值,时间复杂度O(mn),空间可优化为O(min(m,n))。

2) 【原理/概念讲解】最长公共子序列(LCS)是指两个序列中共同出现的字符序列(顺序不变,不必连续)。动态规划的核心是状态定义:设字符串s1长度为m,s2长度为n,定义dp[i][j]为s1前i个字符(i从1到m)和s2前j个字符(j从1到n)的LCS长度。
状态转移逻辑:

  • 若s1[i-1] == s2[j-1](当前字符匹配),则当前字符可加入LCS,dp[i][j] = dp[i-1][j-1] + 1;
  • 否则(不匹配),需舍弃一个字符,取不包含当前字符的子问题的最大值,即dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
    类比:类似拼图,每个子问题(前缀字符串的LCS)解决后,大问题由子问题递推组合,确保每个子问题只计算一次,避免重复。

3) 【对比与适用场景】

方法时间复杂度空间复杂度适用场景注意点
标准动态规划O(mn)O(mn)m、n均较小(如≤1000)需m*n内存,可能超出限制
空间优化动态规划O(mn)O(min(m,n))m或n很大(如m=10^5,n=10^3)交换字符串使较短为列,遍历时用prev保存上一行值

4) 【示例】
示例:s1 = "ABCBDAB",s2 = "BDCABA",求LCS。

  • 初始化dp数组(m=7,n=7),dp[0][]=0,dp[][0]=0。
  • 遍历i从1到m,j从1到n:
    • i=5('B'),j=4('B'):B==B,匹配,dp[5][4]=dp[4][3]+1=2(前缀"ABCB"与"BDCB"的LCS为"BC",长度2)。
    • 最终dp[7][7]=4,对应LCS为"BCBA"。
      (伪代码优化后):
def lcs(s1, s2):
    m, n = len(s1), len(s2)
    if m < n: s1, s2, m, n = s2, s1, n, m  # 交换使s1更长,用s2作为列
    dp = [0] * (n+1)
    for i in range(1, m+1):
        prev = 0
        for j in range(1, n+1):
            temp = dp[j]  # 保存dp[i-1][j]
            if s1[i-1] == s2[j-1]:
                dp[j] = prev + 1
            else:
                dp[j] = max(dp[j], dp[j-1])
            prev = temp
    return dp[n]

5) 【面试口播版答案】
“面试官您好,计算两个字符串的最长公共子序列(LCS),我会用动态规划来解决。首先定义状态:设字符串s1长度为m,s2长度为n,dp[i][j]表示s1前i个字符和s2前j个字符的LCS长度。状态转移方程是:如果s1[i-1]等于s2[j-1],那么dp[i][j]等于dp[i-1][j-1]加1;否则取dp[i-1][j]和dp[i][j-1]的最大值。这样通过递推计算所有子问题的解,最终dp[m][n]就是结果。时间复杂度是O(mn),因为需要遍历所有m*n个状态。空间复杂度标准解法是O(mn),但可以优化,比如用一维数组,因为当前状态只依赖上一行和当前列,所以只需要O(min(m,n))的空间。优化方法是交换字符串使较短的那个作为列,遍历时用prev变量保存上一行的值,这样空间复杂度降到O(min(m,n))。比如对于s1='ABCBDAB'和s2='BDCABA',计算后LCS是'BCBA',长度4。”

6) 【追问清单】

  • 问题1:如果其中一个字符串为空,LCS应该怎么处理?
    回答要点:当字符串为空时,LCS长度为0,因为空字符串与任何字符串的公共子序列都是空,此时状态转移中初始化dp[i][0]或dp[0][j]为0即可,结果正确。
  • 问题2:空间优化时,为什么需要交换字符串使较短的那个作为列?
    回答要点:因为标准解法中,dp[i][j]依赖于dp[i-1][j]和dp[i][j-1],优化后用一维数组时,遍历顺序需保证dp[j](当前列)的值在更新时,dp[j-1](左方)和prev(上一行当前列,即dp[i-1][j])都已计算,交换较短字符串作为列可减少空间占用。
  • 问题3:如何从dp数组中回溯得到具体的LCS序列?
    回答要点:回溯时从dp[m][n]开始,若s1[i-1]==s2[j-1],则字符加入结果,向左上角移动(i-1,j-1);否则向值较大的方向移动(若dp[i-1][j] > dp[i][j-1],则i-1;否则j-1),直到i=0或j=0。
  • 问题4:状态转移方程中,为什么匹配时加1,不匹配时取最大值?
    回答要点:匹配时,当前字符可加入LCS,故长度为前缀的LCS长度加1;不匹配时,必须舍弃一个字符,此时LCS由不包含当前字符的子问题决定,取两者最大值确保长度最大。
  • 问题5:如果字符串长度很大(如100万字符),动态规划是否可行?
    回答要点:对于超长字符串,动态规划的时间复杂度O(mn)可能无法承受,可考虑更高效的算法(如Hirschberg分治法,时间O(mn),空间O(min(m,n))),但面试中优化后的动态规划已足够。

7) 【常见坑/雷区】

  • 坑1:状态定义索引错误(如0-based导致转移方程错误)。
    反问:若用0-based索引,转移方程会变成dp[i][j] = (s1[i]==s2[j] ? dp[i-1][j-1]+1 : max(dp[i-1][j], dp[i][j-1])),但边界条件需调整,易出错。
  • 坑2:空间优化时遍历顺序错误(如内层循环从左往右导致prev更新错误)。
    反问:若内层循环从左往右,prev会变成当前列的旧值,导致dp[j]计算错误。
  • 坑3:时间复杂度与空间复杂度混淆(认为空间优化会降低时间复杂度)。
    反问:空间优化仅减少内存,计算状态数量仍为m*n,时间复杂度不变。
  • 坑4:边界条件处理不当(如空字符串初始化错误)。
    反问:当s1为空,s2非空时,若初始化dp[0][j]为1,会导致结果错误。
  • 坑5:回溯路径错误(无法正确还原LCS序列)。
    反问:若从dp[m][n]回溯时判断条件错误,会跳过匹配字符,结果不正确。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1