news 2026/8/10 10:01:39

动态规划解LeetCode 115:不同子序列计数问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
动态规划解LeetCode 115:不同子序列计数问题

1. 问题背景与理解

第一次看到LeetCode 115题"不同的子序列"时,我盯着题目描述足足看了五分钟。这道题在动态规划分类中属于中等难度,但它的解法思路却让很多初学者感到困惑。题目要求我们计算字符串s中有多少种不同的子序列等于字符串t,这里的子序列指的是在不改变字符顺序的情况下,通过删除某些字符得到的新字符串。

举个例子,如果s = "rabbbit",t = "rabbit",那么有3种方式可以从s中得到t:

  1. rabb b it (删除第二个b)
  2. ra b bbit (删除第三个b)
  3. rab b bit (删除第四个b)

这个例子生动展示了子序列问题的核心特征——顺序必须保持一致,但允许跳过中间字符。理解这一点对解题至关重要。

2. 暴力递归解法分析

2.1 基础递归思路

最直观的解法是使用递归。我们可以定义递归函数count(i,j),表示在s的前i个字符和t的前j个字符中,t的前j个字符作为子序列出现在s的前i个字符中的次数。

递归的终止条件有两种:

  1. 当j=0时,表示t已经匹配完成,返回1
  2. 当i=0但j>0时,表示s已经用完但t还未匹配完,返回0

递归关系也有两种情况:

  1. 如果s[i-1] == t[j-1],可以选择匹配这个字符,也可以选择不匹配
  2. 如果s[i-1] != t[j-1],只能选择不匹配这个字符

这种递归解法虽然直观,但时间复杂度高达O(2^n),在LeetCode上会超时。不过,理解这个基础解法对后续优化至关重要。

2.2 递归代码实现

def numDistinct(s: str, t: str) -> int: def helper(i, j): if j == 0: return 1 if i == 0: return 0 if s[i-1] == t[j-1]: return helper(i-1, j-1) + helper(i-1, j) else: return helper(i-1, j) return helper(len(s), len(t))

这段代码清晰地展现了递归思路,但在实际运行中,对于较长的字符串(比如s长度100+),性能会急剧下降。

3. 动态规划解法优化

3.1 DP状态定义

为了优化时间复杂度,我们引入动态规划。定义dp[i][j]表示s的前i个字符中t的前j个字符作为子序列出现的次数。这个定义与递归解法中的count(i,j)完全对应。

初始化条件:

  • dp[i][0] = 1 (空字符串是任何字符串的子序列)
  • dp[0][j] = 0 (j>0时,空字符串无法包含非空子序列)

状态转移方程:

  • 当s[i-1] == t[j-1]时:dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
  • 当s[i-1] != t[j-1]时:dp[i][j] = dp[i-1][j]

3.2 DP表格填充示例

以s="rabbbit",t="rabbit"为例:

初始化dp表格大小为(8,7)(包含空字符串情况)

填充过程:

  1. 第一行(除dp[0][0]外)全为0
  2. 第一列全为1
  3. 逐步填充其余单元格

最终dp[7][6] = 3,与示例结果一致。

3.3 DP代码实现

def numDistinct(s: str, t: str) -> int: m, n = len(s), len(t) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = 1 for i in range(1, m + 1): for j in range(1, n + 1): if s[i-1] == t[j-1]: dp[i][j] = dp[i-1][j-1] + dp[i-1][j] else: dp[i][j] = dp[i-1][j] return dp[m][n]

这个解法的时间复杂度为O(mn),空间复杂度也是O(mn),已经比递归解法高效很多。

4. 空间优化技巧

4.1 滚动数组优化

观察状态转移方程,我们发现dp[i][j]只依赖于上一行的数据。因此可以使用一维数组来优化空间复杂度:

def numDistinct(s: str, t: str) -> int: m, n = len(s), len(t) dp = [0] * (n + 1) dp[0] = 1 for i in range(1, m + 1): prev = dp.copy() for j in range(1, n + 1): if s[i-1] == t[j-1]: dp[j] = prev[j-1] + prev[j] else: dp[j] = prev[j] return dp[n]

4.2 反向遍历优化

更巧妙的是,我们可以反向遍历j,这样就不需要额外的prev数组:

def numDistinct(s: str, t: str) -> int: m, n = len(s), len(t) dp = [0] * (n + 1) dp[0] = 1 for i in range(1, m + 1): for j in range(n, 0, -1): if s[i-1] == t[j-1]: dp[j] += dp[j-1] return dp[n]

这种优化将空间复杂度降到了O(n),是面试中最推荐的写法。

5. 边界条件与特殊测试用例

5.1 空字符串处理

  • s为空,t不为空:返回0
  • t为空:返回1(空字符串是任何字符串的子序列)
  • 两者都为空:返回1

5.2 大数溢出问题

当结果很大时(比如s和t都是相同的长字符串),结果可能超过普通整型范围。在Python中这不是问题,但在其他语言如C++中需要考虑使用长整型。

5.3 性能极限测试

对于s="a"*1000,t="a"*100的情况,即使使用DP解法也需要处理较大的计算量。在实际编码中,可以提前判断:

  • 如果len(t) > len(s),直接返回0
  • 如果t为空,直接返回1

6. 类似题目与举一反三

6.1 LeetCode 392. 判断子序列

这道简单题可以看作是本题的简化版,只需要判断是否存在子序列,而不需要计数。

6.2 LeetCode 72. 编辑距离

虽然题目不同,但状态定义和转移思路有相似之处,都是基于两个字符串的匹配。

6.3 LeetCode 1143. 最长公共子序列

LCS问题与子序列计数问题有异曲同工之妙,都是动态规划的经典应用。

7. 面试技巧与常见错误

7.1 面试官可能问的问题

  1. 为什么初始条件是dp[i][0]=1?
  2. 如何从递归解法推导出DP解法?
  3. 空间优化思路是什么?
  4. 如果字符串包含Unicode字符,解法需要修改吗?

7.2 常见错误点

  1. 混淆子序列和子串的概念
  2. 初始化条件设置错误
  3. 索引处理不当(字符串从0开始但dp表从1开始)
  4. 在大数情况下忘记考虑溢出

7.3 代码调试技巧

在实现DP解法时,可以:

  1. 先写出递归解法确保逻辑正确
  2. 打印出完整的DP表格验证中间结果
  3. 用小的测试用例手动计算核对

8. 实际应用场景

虽然这看起来是一道纯算法题,但子序列计数在实际中有重要应用:

  1. DNA序列比对:在生物信息学中,比较基因序列的相似性
  2. 版本控制系统:比较代码文件的变化
  3. 拼写检查:计算单词之间的相似度
  4. 自然语言处理:评估句子相似性

理解子序列问题的解法,可以帮助我们在这些领域设计更高效的算法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/10 10:01:20

UE5蓝图Tab切换:管理者模式实现UI状态管理与组件复用

1. 项目概述:UE5蓝图中的Tab切换交互在UE5项目开发中,尤其是制作工具类编辑器、游戏内菜单或者复杂的UI界面时,Tab页签(选项卡)是一个非常经典且高频使用的交互组件。它的核心逻辑就是通过点击不同的标签,来…

作者头像 李华
网站建设 2026/8/10 9:58:36

python hot 100——2 栈(自存)

20 有效的括号 1. 📖 题目要求输入:一个只包含 ()[]{} 的字符串,比如 "()"、"()[]{}"、"(]"输出:True 或 False规则:左括号必须和相同类型的右括号闭合,且顺序正确。输入结果…

作者头像 李华
网站建设 2026/8/10 9:56:14

工业物联网时序数据库选型与实践指南

1. 工业IoT场景下的时序数据挑战在工业物联网领域,设备产生的数据具有典型的时序特性——每台机床、传感器、PLC控制器都在以固定间隔生成带时间戳的状态数据。某汽车制造厂的实践显示,一条焊接产线每小时就能产生超过200万条数据点,包含电流…

作者头像 李华
网站建设 2026/8/10 9:52:06

贵阳专业网站建设公司如何打造高效转化网站的全攻略指南

在这个数字化浪潮席卷全球的今天,如果你还认为拥有一个网页就能代表企业在互联网上存在,那只能说是停留在上个世纪的想法了。尤其是在贵阳这样的西南重镇,随着数字经济的高速发展,各行各业的竞争早已从线下搬到了线上。很多老板在创业初期,往往会被各种眼花缭乱的营销话术…

作者头像 李华
网站建设 2026/8/10 9:52:15

具身智能TVA-World抽象概念学习与知识迁移机制

前沿技术探索:TVA智能体(简称TVA)TVA智能体(亦称“AI智能体视觉”或“TVA视觉智能体”)是依托Transformer架构与“因式智能体”理论构建的通用视觉技术框架。它融合深度强化学习(DRL)、卷积神经…

作者头像 李华