leetCode 392. 判断子序列 动态规划 + 优化空间 / 双指针 等多种解法

news2024/11/19 8:24:36

392. 判断子序列 - 力扣(LeetCode)


给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?


示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

>>思路和分析

  • 只需要计算删除的情况

(一)动规五部曲

1.确定dp数组(dp table)以及下标的含义

  • dp[i][j] : 表示以下标 i-1 为结尾的字符串 s ,和以下标 j-1 为结尾的字符串 t ,相同子序列的长度为dp[i][j]

2.确定递推公式

  • ① if(s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + 1
  • ② if(s[i-1] != t[j-1]) dp[i][j] = dp[i][j-1] 

如果 s[i-1] == t[j-1],说明当前在遍历串t 时找到了一个字符和 串s的字符 匹配,那么相同子序列长度就在dp[i-1][j-1]的基础上加1

如果 s[i-1] != t[j-1],说明当前在遍历串t 时这个字符和 串s的字符 不匹配,此时相当于 t 要删除元素,若 t 把当前元素t[j-1]删除,那么 dp[i][j] 的数值就是看 s[i-1]t[j-2] 的比较结果,即:dp[i][j] = dp[i][j-1];

3.dp数组初始化

  • 从递推公式可以看出 dp[i][j] 都是依赖于 dp[i-1][j-1] dp[i][j-1] ,所以 dp[0][0]dp[i][0] 是一定要初始化的
vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));

4.确定遍历顺序

  • 从递推式可以看出 dp[i][j] 依赖于 dp[i-1][j-1]dp[i][j-1],遍历顺序应该是从上到下,从左到右

5.举例推导dp数组

  • dp[i][j] : 表示以下标 i-1 为结尾的字符串 s ,和以下标 j-1 为结尾的字符串 t ,相同子序列的长度为dp[i][j]

那么dp[s.size()][t.size()] = 3,而s.size() = 3,故 st 的子序列,返回 true

注:观察此表格我们可以发现,当串s串t中寻找某一个字符成功时,每一行的最后一个数值都等于当前的 i 值。所以下文代码中有一句 if(dp[i][t.size()] !=i) return false; 说明 串s 中的某一个字符在 串t 中实在无法找不到了,就直接返回false 

(1)动态规划 二维dp 

class Solution {
public:   
    // 动态规划 二维dp
    bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
        for(int i=1;i<=s.size();i++) {
            for(int j=1;j<=t.size();j++) {
                if(s[i-1] == t[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                else dp[i][j] = dp[i][j-1];
            }
            if(dp[i][t.size()] !=i) return false;
        }
        return dp[s.size()][t.size()] == s.size();
    }
};
  • 时间复杂度:O(n × m)
  • 空间复杂度:O(n × m)

(2)二维dp 优化空间

class Solution {
public:
    // 二维dp 优化空间复杂度
    bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(2,vector<int>(t.size()+1,0));
        for(int i=1;i<=s.size();i++) {
            for(int j=1;j<=t.size();j++) {
                if(s[i-1] == t[j-1]) dp[i % 2][j] = dp[(i-1)%2][j-1] + 1;
                else dp[i % 2][j] = dp[i % 2][j-1];
            }
            if(dp[i%2][t.size()] !=i) return false;
        }
        return dp[s.size() % 2][t.size()] == s.size();
    }
};
  • 时间复杂度:O(n × m)
  • 空间复杂度:O(m)

(3)一维dp 优化空间

class Solution {
public: 
    // 一维dp 滚动数组 优化空间复杂度
    bool isSubsequence(string s, string t) {
        vector<int> dp(t.size()+1,0);
        for(int i=1;i<=s.size();i++) {
            int pre = dp[0];
            for(int j=1;j<=t.size();j++) {
                int tmp = dp[j];
                if(s[i-1] == t[j-1]) dp[j] = pre + 1;
                else dp[j] = dp[j-1];
                pre = tmp;
            }
            if(dp[t.size()] !=i) return false;
        }
        return dp[t.size()] == s.size();
    }
};
  • 时间复杂度:O(n × m)
  • 空间复杂度:O(m)
  • 我思考之后发现内层for循环里的 j 的起始位置可以改一下
class Solution {
public:  
    bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
        int tmp=1;
        for(int i=1;i<=s.size();i++) {
            bool flag = 0;
            for(int j=tmp;j<=t.size();j++) {
                if(s[i-1] == t[j-1]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                    if(!flag) tmp=j;
                    flag=1;
                }
                else dp[i][j] = dp[i][j-1];
            }
            if(dp[i][t.size()] !=i) return false;
        }
        return dp[s.size()][t.size()] == s.size();
    }

(二)双指针

(1)while循环写法 

class Solution {
public:
    bool isSubsequence(string s,string t) {
        int i=0,j=0;
        while(i<s.size() && j<t.size()) {
            if(s[i] == t[j]) {
                i++;
                j++;
            }
            else j++; 
        }
        if(i == s.size()) return true;
        return false;
    }
};

 (2)for循环写法

class Solution {
public:
    bool isSubsequence(string s,string t) {
        if(s.size()==0) return true;
        int i=0;
        for(auto c:t) {
            if(s[i] == c) {
                i+=1;
                if(i == s.size()) return true;
            }
        }
        return false;
    }
};

 参考和推荐文章、视频:

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://www.programmercarl.com/0392.%E5%88%A4%E6%96%AD%E5%AD%90%E5%BA%8F%E5%88%97.html#%E6%80%9D%E8%B7%AF动态规划,用相似思路解决复杂问题 | LeetCode:392.判断子序列_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1tv4y1B7ym/?spm_id_from=pageDriver&vd_source=a934d7fc6f47698a29dac90a922ba5a3来自代码随想录课堂视频截图:

  • 我写的另一个版本(串 s 用双指针,串 t 也用双指针):
class Solution {
public:
    bool isSubsequence(string s,string t) {
        int sl=0,sr=s.size()-1;
        int tl=0,tr=t.size()-1;
        int count = 0;
        if(s.size()==0) return true;
        while(sl<=sr && tl <= tr) {
            if(s[sl] == t[tl]) {
                sl++;
                tl++;
                count++;
            }
            else tl++;
            if(tl < tr && s[sr] == t[tr]) {
                sr--;
                tr--;
                count++;
            }
            else tr--;
        }
        if(s.size() == count) return true;
        return false;
    }
};

把串s看作是一个装着 串s 字符的栈 :

class Solution {
public:
    bool isSubsequence(string s,string t) {
        if(s.empty()) return true;
        for(int i=t.size()-1;i>=0;i--) {
            if(t[i] == s.back()) {
                s.pop_back();
                if(s.empty()) return true;
            };
        }
        return false;
    }
};

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

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

相关文章

【2023】redis-stream配合spring的data-redis详细使用

目录 一、简介1、介绍2、对比二、整合spring的data-redis实现1、使用依赖2、配置类2.1、配置RedisTemplate bean2.2、异常类3、实体类3.1、User3.2、Book4、发送消息4.1、RedisStreamUtil工具类4.2、通过延时队列线程池模拟发送消息4.3、通过http主动发送消息5、&#x1f31f;消…

003数据安全传输-多端协议传输平台:Protobuf - 部署

文章目录 一、Windows环境二、Linux Centos环境三、protobuf测试3.1 新建.proto文件生成相应的类3.2 .proto生成相应的类的使用3.3 配置VS3.4 test代码 一、Windows环境 在windows下配置&#xff0c;无论protobuf是什么版本&#xff0c;IDE和编译器的版本都要保持一致。 比如…

Linux - 大括号的妙用

示例1 touch demo_{1..10}.txt示例2 touch case_{a,b,c,d}.txt示例3 touch {a,b}{1..4}.txt

第三章 内存管理 十一、虚拟内存的基本概念

目录 一、传统存储管理 1、缺点 二、局部性原理 1、时间局部性&#xff1a; 2、空间局部性&#xff1a; 三、虚拟内存的定义和特征 1、结构 ​编辑 2、定义 3、特征 &#xff08;1&#xff09;多次性: &#xff08;2&#xff09;对换性: &#xff08;3&#xff09;…

【来点小剧场--项目测试报告】个人博客系统测试报告

一、项目背景 个人博客系统采用前后端分离的方法来实现&#xff0c;使用了MySQL数据库来存储相关的数据&#xff0c;同时对Redis进行配置&#xff0c;将session会话存储在redis中以方便分布式运转&#xff0c;最后通过云服务器将项目部署到网络上。前端主要有六个页面构成&…

Vue3 + Nodejs 实战 ,文件上传项目--大文件分片上传+断点续传

目录 1.大文件上传的场景 2.前端实现 2.1 对文件进行分片 2.2 生成hash值&#xff08;唯一标识&#xff09; 2.3 发送上传文件请求 3.后端实现 3.1 接收分片数据临时存储 3.2 合并分片 4.完成段点续传 4.1修改后端 4.2 修改前端 5.测试 博客主页&#xff1a;専心_前端…

[牛客]计算机网络习题笔记_1019

1、物理层&#xff1a;以太网 调制解调器 电力线通信(PLC) SONET/SDH G.709 光导纤维 同轴电缆 双绞线等。 2、数据链路层&#xff08;网络接口层包括物理层和数据链路层&#xff09;&#xff1a;Wi-Fi(IEEE 802.11) WiMAX(IEEE 802.16) ATM DTM 令牌环 以太网 FDD…

高校教务系统登录页面JS分析——华东交通大学

高校教务系统密码加密逻辑及JS逆向 本文将介绍高校教务系统的密码加密逻辑以及使用JavaScript进行逆向分析的过程。通过本文&#xff0c;你将了解到密码加密的基本概念、常用加密算法以及如何通过逆向分析来破解密码。 本文仅供交流学习&#xff0c;勿用于非法用途。 一、密码加…

android studio打开flutter项目报红

一、android studio打开flutter项目报红&#xff0c;如下图&#xff1a; 二、解决方法&#xff1a; 2.1 在这个build.gradle添加以下代码&#xff0c;如图&#xff1a; 2.2 在build.gradle最顶部添加如下代码&#xff1a; def localProperties new Properties() def localPr…

水经注地图服务 5.0.1-rc 版发布

《水经注地图服务》&#xff08;WeServer&#xff09;是一款可快速发布全国乃至全球海量卫星影像的地图发布服务产品。 它可以轻松发布260TB级海量卫星影像&#xff0c;从而使“在内网建立一个离线版的地球”不只是一个梦想&#xff01; ​01 新版发布 水经注地图服务 5.0…

NodeMCU ESP8266 读取按键外部输入信号详解(图文并茂)

NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09; 文章目录 NodeMCU ESP8266 读取按键外部输入信号教程&#xff08;图文并茂&#xff09;前言按键输入常用接口pinModedigitalRead 示例代码结论 前言 ESP8266如何检测外部信号的输入&#xff0c;通常…

10kV-35kV交联电缆油杯终端

武汉凯迪正大油杯产品简介 KDZD-10 /KDZD-35 油杯终端是我公司在总结了大量的现场经验的基础上&#xff0c;自行开发、设计的一种 10~35kV 以下交联电缆和工频耐压试验的简易试验终端&#xff0c;该油杯操作简便&#xff0c;使用可靠。 目前电缆厂均拥有多条 XLPE 生产线&…

【Git】升级MacOS系统,git命令无法使用

终端执行git命令报错 xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun安装这个东东&#xff0c;&#xff1f;需要42小时 最终解决&#xff1a; 下载安装 https…

C语言进阶第七课-----------自定义类型的讲解(结构体枚举联合)

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

2023CRM排行:深度对比16款CRM

客户关系管理系统&#xff08;CRM&#xff09;作为数字化转型的重要载体&#xff0c;选择一个优秀的CRM系统将为企业未来健康增长保障。市场上CRM软件众多&#xff0c;但很难分清哪个适合自己&#xff0c;最近赶在公司选型&#xff0c;我对市场所有软件进行了一个调研&#xff…

postgresql(openGauss)模糊匹配参数

被pg系这个show要求精准匹配参数恶心的不轻。 原理是用.psqlrc&#xff08;openGauss用.gsqlrc&#xff09;文件set一个select常量进去&#xff0c;需要用&#xff1a;调用这个常量。理论上也可以增强其他的各种功能。 我在openGauss做的一个例子 .gsqlrc&#xff08;.psqlrc…

容灾备份——容灾系统介绍

目录 基本概述 容灾关键技术 容灾系统的级别 容灾主要技术 基本概述 容灾与备份的区别 容灾备份——备份技术系统架构与备份网络方案-CSDN博客https://blog.csdn.net/m0_49864110/article/details/123969802?ops_request_misc%257B%2522request%255Fid%2522%253A%252216…

【Java 进阶篇】JavaScript 表单验证详解

JavaScript 表单验证是网页开发中不可或缺的一部分。它允许您确保用户在提交表单数据之前输入了有效的信息。无论您是一个初学者还是一个有经验的开发人员&#xff0c;本文将为您详细介绍如何使用 JavaScript 来进行表单验证。我们将从基础知识开始&#xff0c;逐步深入&#x…

ubuntu20.04运用startup application开机自启动python程序

运用startup application开机自启动python程序。在终端中输入gnome-session-properties,如果显示没有则先进行安装&#xff0c;sudo apt-get update 和sudo apt install StartupApplications(根据显示提示安装)。在显示程序中搜索startup&#xff0c;打开应用程序。 在程序目录…

LeetCode 1595. 连通两组点的最小成本【记忆化搜索,状压DP】2537

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…