【LeetCode】动态规划—115. 不同的子序列(附完整Python/C++代码)

news2024/12/24 20:15:44

动态规划—115. 不同的子序列

  • 前言
  • 题目描述
  • 基本思路
    • 1. 问题定义
    • 2. 理解问题和递推关系
    • 3. 解决方法
      • 3.1 动态规划方法
      • 3.2 空间优化的动态规划
    • 4. 进一步优化
    • 5. 小总结
  • 代码实现
    • Python
      • Python3代码实现
      • Python 代码解释
    • C++
      • C++代码实现
      • C++ 代码解释
        • 1. 变量定义:
        • 2. 初始化:
        • 3. 动态规划状态转移:
        • 4. 返回结果:
  • 总结:

前言

在字符串处理的领域,不同子序列问题是一个经典的挑战,涉及到如何计算一个字符串的所有不同子序列以匹配另一个字符串。通过动态规划方法,我们能够有效地找出字符串之间的匹配数量,为更复杂的字符串问题提供解决方案。本文将详细介绍这一问题的思路、解决方法以及相应的 Python 和 C++ 实现代码。

题目描述

在这里插入图片描述

基本思路

1. 问题定义

不同子序列问题要求我们计算字符串 s s s 中有多少个不同的子序列等于字符串 t t t 。子序列是通过删除字符串中的某些字符(可以不删除任何字符)而形成的序列。

2. 理解问题和递推关系

  • 我们可以定义一个二维数组 d p [ i ] [ j ] d p[i][j] dp[i][j] ,表示字符串 s s s 的前 i i i 个字符与字符串 t t t 的前 j j j 个字符形成的不同子序列的数量。
  • 递推关系如下:
    • 如果 s [ i − 1 ] = = t [ j − 1 ] s[i-1]==t[j-1] s[i1]==t[j1] ,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] d p[i][j]=d p[i-1][j-1]+d p[i-1][j] dp[i][j]=dp[i1][j1]+dp[i1][j] 。这表示我们可以选择将 s [ i − 1 ] s[i-1] s[i1] 作为 t [ j − 1 ] t[j-1] t[j1] 的一部分,或者不选择它。
    • 如果 s [ i − 1 ] ! = t [ j − 1 ] s[i-1]!=t[j-1] s[i1]!=t[j1], 则 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] d p[i][j]=d p[i-1][j] dp[i][j]=dp[i1][j] 。这表示我们只能忽略 s [ i − 1 ] s[i-1] s[i1] 来继续匹配 t [ j − 1 ] t[j-1] t[j1]

3. 解决方法

3.1 动态规划方法

  1. 创建一个二维数组 d p d p dp ,大小为 ( m + 1 ) × ( n + 1 ) (m+1) \times(n+1) (m+1)×(n+1) ,其中 m m m 是字符串 s s s 的长度, n n n 是字符串 t t t 的长度。
  2. 初始化边界条件:
    • d p [ 0 ] [ 0 ] = 1 \mathrm{dp}[0][0]=1 dp[0][0]=1 :两个空字符串是一个有效的子序列。
    • d p [ i ] [ θ ] = 1 d p[i][\theta]=1 dp[i][θ]=1 :任何字符串 s s s 的前 i i i 个字符与空字符串匹配的方式只有一种,即删除所有字符。
    • d p [ 0 ] [ j ] = 0 d p[0][j]=0 dp[0][j]=0 :空字符串无法匹配非空字符串。
  3. 使用双重矿环填充 dp 数组,依赖于前面的状态。
  4. 最终结果为 d p [ m ] [ n ] d p[m][n] dp[m][n]

3.2 空间优化的动态规划

  • 可以使用一维数组来优化空间复杂度,将 dp 数组从二维变为一维。

4. 进一步优化

  • 使用一维数组可以减少内存使用,同时时间复杂度保持在 O ( m ∗ n ) O(m * n) O(mn) ,适合处理中等规模的字符串。

5. 小总结

  • 不同子序列问题通过动态规划有效计算出字符串之间的匹配数量。
  • 理解该问题的解决方案有助于掌握动态规划的设计理念,并能够应用于类似的字符串匹配问题。

以上就是不同的子序列问题的基本思路。

代码实现

Python

Python3代码实现

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        m, n = len(s), len(t)
        # 创建dp数组
        dp = [[0] * (n + 1) for _ in range(m + 1)]

        # 初始化边界条件
        for i in range(m + 1):
            dp[i][0] = 1  # 空字符串t的匹配方法

        # 填充dp数组
        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]

Python 代码解释

  • 初始化:创建 dp 数组并设置边界条件,处理空字符串的匹配。
  • 动态规划填充:通过双重循环遍历 st 的每个字符,依据字符相同与否来更新 dp 数组。
  • 返回结果:最终返回 dp[m][n],即字符串 s 中与 t 匹配的不同子序列数量。

C++

C++代码实现

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        // 使用unsigned long long类型来防止溢出
        vector<vector<unsigned long long>> dp(m + 1, vector<unsigned long long>(n + 1, 0));

        // 初始化边界条件
        for (int i = 0; i <= m; i++) {
            dp[i][0] = 1;  // 空字符串t的匹配方法
        }

        // 填充dp数组
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                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];  // 字符不同
                }
            }
        }

        // 返回不同子序列的数量,确保返回的结果是int类型
        return static_cast<int>(dp[m][n]);
    }
};

C++ 代码解释

  • 初始化:创建 dp 数组并设置边界条件,处理空字符串的匹配情况。
  • 动态规划填充:通过双重循环更新 dp 数组,依赖于当前字符的匹配状态。
  • 返回结果:返回 dp[m][n],即不同子序列的数量。
1. 变量定义:
  • m : s m: s ms 字符串的长度。
  • n : t n: t nt 字符串的长度。
  • d p : d p: dp一个二维动态规划数组,用于存储从 s s s 的前 i i i 个字符中选择与 t t t 的前 j j j 个字符匹配的子序列个数。 d p [ i ] [ j ] d p[i][j] dp[i][j] 的值表示从 s s s 的前 i i i 个字符中有多少个不同子序列可以匹配 t t t 的前 j j j 个字符。
2. 初始化:
  • d p [ i ] [ θ ] = 1 d p[i][\theta]=1 dp[i][θ]=1 :当 t t t 是空字符串时,不管 s s s 是什么,只有一种方法可以匹配空字符串,即删除所有字符。因此, d p [ i ] [ 0 ] = 1 d p[i][0]=1 dp[i][0]=1
  • d p [ 0 ] [ j ] = 0 d p[0][j]=0 dp[0][j]=0 :当 s s s 是空字符串而 t t t 不是空字符串时,不可能通过任何方式匹配,因此 d p [ 0 ] [ j ] = 0 \mathrm{dp}[0][j]=0 dp[0][j]=0
3. 动态规划状态转移:
  • 字符相同: 如果 s [ i − 1 ] = = t [ j − 1 ] s[i-1]==\mathrm{t}[j-1] s[i1]==t[j1] ,有两种选择:
  1. 不使用 s [ i − 1 ] s[i-1] s[i1] ,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] d p[i][j]=d p[i-1][j] dp[i][j]=dp[i1][j] ,忽略 s [ i − 1 ] s[i-1] s[i1] ,继续用前面的字符来匹配 t [ j ] t[j] t[j]
  2. 使用 s [ i − 1 ] s[i-1] s[i1], 即 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] d p[i][j]=d p[i-1][j-1] dp[i][j]=dp[i1][j1] ,让 s [ i − 1 ] s[i-1] s[i1] t [ j − 1 ] t[j-1] t[j1] 匹配。
  • 最终 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j ] d p[i][j]=d p[i-1][j-1]+d p[i-1][j] dp[i][j]=dp[i1][j1]+dp[i1][j] ,两者相加表示两种选择的总数。
  • 字符不同: 如果 s [ i − 1 ] ! = t [ j − 1 ] s[i-1]!=t[j-1] s[i1]!=t[j1] ,只能忽略 s [ i − 1 ] s[i-1] s[i1] ,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] d p[i][j]=d p[i-1][j] dp[i][j]=dp[i1][j] ,表示继续用 s s s 的前 i − 1 i-1 i1 个字符来匹配 t [ j ] t[j] t[j]
4. 返回结果:
  • 最终结果保存在 d p [ m ] [ n ] d p[m][n] dp[m][n] 中,它表示字符串 s s s 中有多少个不同的子序列与 t t t 完全匹配。
  • 由于动态规划数组使用的是 unsigned long long 来避免溢出,但题目要求返回 int 类型的结果,因此最后使用 static_cast<int>将其转换为 int

总结:

  • 不同子序列问题是动态规划的一个重要应用,展示了如何通过状态转移方程来计算字符串之间的匹配。
  • 理解该问题的解决方案可以帮助掌握动态规划的核心思想,并为处理其他类似问题提供借鉴。
  • 本文提供的代码实现既清晰又高效,为学习和应用动态规划提供了实际示例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2197289.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高级算法设计与分析 学习笔记11 动态规划

要讲动态规划&#xff0c;当然少不了斐波拉及数列&#xff1a; 可以看到&#xff0c;动态规划效率高的秘诀就在于有记忆&#xff0c;不用做重复的事。 矩阵列乘法&#xff1a; 可以看到&#xff0c;只要找到了一个恰到好处的计算顺序&#xff08;注意矩阵乘法只有结合律没有交换…

HT8513 内置自适应同步升压和防破音功能的6.5W D类及AB类音频功率放大器

1、特征 防削顶失真功能(防破音,Anti-Clipping Function, ACF) 免滤波器数字调制&#xff0c;直接驱动扬声器 输出功率 3W (VBAT3.3V, RL-4Ω, THDN<1%, 20-20kHz full band) 2.0W (VBAT3.3V, RL8Ω,THDN<1%, 20-20kHz full band) 6.5W (VBAT4.2V, RL2Ω, THDN10%,f1kHz…

SQL第14课挑战题

1. 将两个select语句结合起来&#xff0c;以便从OrderItems表中检索产品ID(prod_id)和quantity。其中&#xff0c;一个select语句过滤数量为100的行&#xff0c;另一个select语句过滤ID以BNBG开头的产品。按产品ID对结果进行排序。 2. 重新第一题&#xff0c;仅使用单个select语…

2021浙江省赛 F I

F - Fair Distribution 题意 给定两个a,b,每次操作可以使b或者a--,为使得b是a的倍数,最小操作次数是多少 思路 朴素版本:枚举a一直到1为止,每一次找到离b最近的a的倍数(>b),然后每次更新一下最小操作次数是多少,时间复杂度O(n*T) 优化版本:每一个a在找到:找到离b最近的…

图像增强论文精读笔记-Low-Light Image Enhancement via a Deep Hybrid Network

1. 论文基本信息 论文标题&#xff1a;Low-Light Image Enhancement via a Deep Hybrid Network 作者&#xff1a;Wenqi Ren等 发表时间和期刊&#xff1a;2019&#xff1b;IEEE TIP 论文链接&#xff1a;https://ieeexplore.ieee.org/document/8692732 2. 研究背景和动机 …

Linux防火墙-案例(二)snatdnat

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们经过上小章节讲了Linux的部分进阶命令&#xff0c;我们接下来一章节来讲讲Linux防火墙。由于目前以云服务器为主&#x…

YOLOv11改进 ,YOLOv11改进主干网络为MobileNetV3,助力涨点

YOLOv11改进介绍 YOLOv11 跟 YOLOv8 结构差不多相似,只是作者在 YOLOv8 基础上进行了改进,我感觉 YOLOv11 训练速度更快,map和精度应该比 YOLOv8 高一些,所以我会把 YOLOv11 改进也写在本专栏里面。YOLOv11 改进,可以看往期 YOLOv8 改进主干网络教程,原理都是一样的,这…

Qt(10.8)

作业&#xff1a;完善登录界面 源文件 #include "widget.h" #include "ui_widget.h" #include<QDebug> #include<QLabel> #include<QMessageBox> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setu…

MySQL连接查询:自连接

先看我的表结构 emp表 自连接也就是把一个表看作是两个作用的表就好&#xff0c;也就是说我把emp看作员工表&#xff0c;也看做领导表 自连接 基本语法 select 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件;例子1&#xff1a;查询员工 及其 所属领导的名字 select a.n…

【stm32】寄存器(stm32技术手册下载链接)

1、资料下载 RM0008_STM32F101xx,STM32F102xx,STM32F103xx,STM32F105xx和STM32F107xx单片机参考手册 | STMCU中文官网 2、代码 设置PB7 //设置PB7 #define SDA_IN() {GPIOB->CRL&0X0FFFFFFF;GPIOB->CRL|(u32)8<<28;} #define SDA_OUT() {GPIOB->…

wsl中配置cuda,pytorch,cudnn,vscode

参考链接 查看python版本 从 NVIDIA 的官网上下载 CUDA 的 pin 文件。这个文件确保 CUDA 仓库的优先级更高&#xff0c;防止与其他仓库发生冲突。 wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin将下载的 cuda-wsl-u…

Python 卸载所有的包

Python 卸载所有的包 引言正文 引言 可能很少有小伙伴会遇到这个问题&#xff0c;当我们错误安装了一些包后&#xff0c;由于包之间有相互关联&#xff0c;导致一些已经安装的包无法使用&#xff0c;而由于我们已经安装了很多包&#xff0c;它们的名字我们并不完全知道&#x…

点餐小程序实战教程17角色管理

目录 1 创建API2 创建全局变量3 加载角色4 引导用户注册总结 小程序中如果有多重角色人员使用的&#xff0c;通常需要根据用户的角色来进行页面跳转。我们点餐小程序也是区分不同的用户&#xff0c;有顾客和员工的区分&#xff0c;本篇我们讲解一下如何利用API来加载用户的角色…

移除元素(算法题分享)

移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&#xf…

使用欧拉安装ceph分布式存储,ceph的集群安装、添加主机集群和删除主机、添加osd硬盘和手动添加硬盘为osd和移除osd。

1.ceph安装 1.1 首先准备3台机子&#xff0c;配置ip&#xff0c;给每台机子添加3块硬盘,设置主机名为ceph01、ceph02、ceph03。 192.168.10.20ceph01192.168.10.21ceph02192.168.10.22ceph03 1.2 三台机子关闭防火墙&#xff0c;setenforce 0&#xff0c;添加hosts解析、配置…

【数据结构 | PTA】表

文章目录 7-1 重排链表7-2 链表去重7-3 两个有序链表序列的合并7-4 两个有序链表序列的交集 7-1 重排链表 输入格式&#xff1a; 每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址和结点总个数&#xff0c;即正整数N (≤105)。结点的地址是5位非负整数&#xff…

OgreNext高级材质中增加线宽,点大小,虚线模式绘制支持

修改Ogre高级材质系统,增加线宽,点大小,虚线模式,虚线参数的支持,效果如下: 需要修改的代码文件如下: 修改如下 代码文本: //范围[0.2 - 51] 0.2 * [0,255];Ogre::uint8 mLineWidth;//范围[0.5 - 127.5] 0.5 * [0, 255];Ogre::uint8 mPointSize;//虚线标记Ogre::ui…

【Redis】Redis线程模型

目录 1. Redis 是单线程的&#xff0c;还是多线程的&#xff1f;2. Redis单线程模式是怎么样的&#xff1f;Redis 单线程模式的优势Redis 单线程的局限性Redis 单线程的优化策略 3. Redis采用单线程为什么还这么快4. Redis 6.0 之前为什么使用单线程&#xff1f;5. Redis 6.0 之…

每日学习一个数据结构-图

文章目录 图基础一、图的定义二、图的相关概念三、图的分类四、图的使用场景 和图相关的算法一、图的遍历算法二、最短路径算法三、最小生成树算法四、图匹配算法五、网络流算法 图基础 一、图的定义 在数学中&#xff0c;图是描述于一组对象的结构&#xff0c;其中某些对象对…

数据结构单向链表

单向链表的转置 转置的思想&#xff1a; (1) 将头节点与当前链表断开&#xff0c;断开前保存下头节点的下一个节点&#xff0c;保证后面链表能找得到&#xff0c;定义一个q保存头节点的下一个节点&#xff0c;断开后前面相当于一个空的链表&#xff0c;后面是一个无头的单向链表…