【面试经典150 | 双指针】判断子序列

2023-09-18 12:20:01

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【双指针】【动态规划】【字符串】


题目来源

392. 判断子序列


题目解题

判断字符串 s 是不是字符串 t 的子序列,字符串的子序列指的是原字符串删除一些字符或者不删除字符但是不改变原来字符顺序而形成的新的字符串。


解题思路

方法一:双指针

我们使用两个指针 ij,初始化分别指向字符串 st 的初始位置。从前往后对 s[i]t[j] 进行匹配:

  • 如果 s[i] = t[j],则同时向右移动双指针;
  • 如果 s[i] != t[j],则只移动指向字符串字符的指针 j
  • 无论是否匹配,都需要移动 j 指针;
  • 最终,如果 i 移动到了字符串 s 的末尾,说明 st 的子序列。

实现代码

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

复杂度分析

时间复杂度: O ( n + m ) O(n+m) O(n+m) n n ns 的长度, m m mt 的长度。无论匹配是否成功,都至少youyige有一个指针向右移动,两指针的移动总距离为 n + m n+m n+m

空间复杂度: O ( 1 ) O(1) O(1),仅仅使用了两个指针变量。

方法二:动态规划

方法一有可以进行优化的地方,在方法一中,我们需要枚举匹配 t 中的字符,如果 t 中不匹配的字符很长,我们会有大量的时间浪费在 t 中找下一个匹配的字符。

于是,我们可以先对字符串 t 进行预处理,记录从每个位置开始往后每一个字符第一次出现的位置。

状态

f[i][j] 表示字符串 t 中从位置 i 开始往后字符 j 第一次出现的位置。

状态转移

有如下的状态转移关系:

  • 如果 t[i] = j,那么 f[i][j] = i
  • 否则,f[i][j] = f[i+1][j]

根据以上转移关系,我们需要对字符串 t 从后往前进行动态规划。

base case

我们的边界状态为 f[m-1][...],我们置 f[m][...] = m,让 f[m-1][...] 正常转移,如果 f[i][j] = m,则表示从位置 i 开始往后不存在字符 j

我们通过 f 数组,可以快速定位到字符串 t 后面每一个第一次出现的字符(s 中的字符):

  • 如果 f[i][j] = m,则表示从字符串 t 位置 i 开始往后不存在 s 中的字符 j,则直接返回 false
  • 否则,更新 i,从新的位置开始定位 s 中的字符;
  • 如果一直没遇到 m,最后返回 true

方法二使用动态规划的方法对字符串 t 进行一次处理,可以大大提高匹配,也是 进阶 题目的一种解法。

实现代码

class Solution {
public:
    bool isSubsequence(string s, string t) {
        int n = s.size(), m = t.size();
        vector<vector<int>> f(m+1, vector<int>(26, 0));
        for (int i = 0; i < 26; ++i) {
            f[m][i] = m;
        }

        for (int i = m-1; i >= 0; --i) {
            for (int j = 0; j < 26; ++j) {
                if (t[i] == j + 'a') {
                    f[i][j] = i;
                }
                else 
                    f[i][j] = f[i+1][j];
            }
        }

        int start = 0;
        for (int i = 0; i < n; ++i) {
            if (f[start][s[i] - 'a'] == m)
                return false;
            start = f[start][s[i] - 'a'] + 1;
        }
        return true;
    }
};  

复杂度分析

时间复杂度: O ( m × ∣ ∑ ∣ + n ) O(m \times \left| \sum \right| + n) O(m×+n) n n n 为字符串 s 的长度,m 为字符串 t 的长度, ∣ ∑ ∣ \left| \sum \right| 为字符集, ∣ ∑ ∣ = 26 \left| \sum \right| = 26 =26

空间复杂度: O ( m × ∣ ∑ ∣ ) O( m \times \left| \sum \right|) O(m×),使用的额外空间为对字符串 t 预处理所占用的空间。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

更多推荐

Jmeter 自动化性能测试常见问题汇总

一、request请求超时设置timeout超时时间是可以手动设置的,新建一个http请求,在“高级”设置中找到“超时”设置,设置连接、响应时间为2000ms。1.请求连接超时,连不上服务器。现象:Jmeter表现形式为:前面几个请求是成功的,但是后面请求有的会报错,有的请求成功报错1:Responsecode:Non

uniapp存值和取值,获取登录凭证 code方法

Uniapp的存值和取值Uniapp的存值和取值方法可以使用Vue.js的数据绑定方式,也可以使用uni.setStorageSync()和uni.getStorageSync()方法。使用Vue.js的数据绑定方式:在Vue组件中定义一个data属性,然后将需要存储的值赋给该属性。例如:<template><div>

05_2D3D转换

12D转换转换是CSS3中具有颠覆性的一个特征,可以实现元素的位移、旋转、变形、缩放。通过transform转换来实现2D转换或者3D转换。2D转换包括:缩放scale移动translate旋转rotate倾斜skew(了解)1.1缩放scale设置元素的缩放效果,只要给元素添加上了这个属性就能控制它放大还是缩小。语法

ram和rom的区别

ram和rom的区别主要在于:1、性质不同;2、特点不同;3、应用不同。其中性质不同是指RAM是随机存取存储器,也叫主存,是与CPU直接交换数据的内部存储器,而ROM是只读存储器,以非破坏性读出方式工作,只能读出无法写入信息。1、性质不同RAM是随机存取存储器,也叫主存,是与CPU直接交换数据的内部存储器。RAM(ra

地下城规划3d全景vr虚拟现实制作提高沟通效率

地下空间的合理有序开发,不仅形成了强劲的城市发展脉动,也为人们玩转地下空间“潮”生活提供了可能,因此为了更好宣传城市地下空间,引进web3d开发和VR全景制作技术,开发的城市地下空间3D全景虚拟漫游系统为客户提供线上、全新、丰富的交互体验。城市地下空间3D全景虚拟漫游让我们能够全方位、无死角地探索城市地下的神秘世界。进

Ansible自动化:简化你的运维任务

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🦄博客首页——🐅🐾猫头虎的博客🎐🐳《面试题大全专栏》🦕文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》🐾学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》🐅学会Gol

PyTorch深度学习(六)【循环神经网络-基础】

RNNCell:h0和x1生成h1,把h1作为输出送到下一次的RNNCell里面。(h1=linear(h0,x1))RNN计算过程:输入先做线性变换,循环神经网络常用的激活函数是tanh(±1区间)。构造RNNCell:代码:importtorch​batch_size=1seq_len=3input_size=4h

C++ Qt零基础入门进阶与企业级项目实战教程与学习方法分享

Qt是一个卓越的客户端跨平台开发框架,可以在Windows、Linux、macOS进行客户端开发,无缝切换,一统三端;当然除了桌面端,在移动端的早期,Qt也展现了其多才多艺,在Android和ios也可以使用Qt编写app,近些年移动端的蓬勃发展,大浪淘沙,Qt已退出移动端开发的舞台,但是在桌面端开发,尤其是跨平台方面

5G通信与蜂窝模组之间的关系

5G通信是第五代移动通信技术的简称,它代表了一种新一代的无线通信技术标准。5G通信的主要目标是提供更高的数据传输速度、更低的延迟、更大的网络容量以及更可靠的连接,以支持各种新兴应用和服务,包括高清视频流、虚拟现实、物联网(IoT)、自动驾驶汽车和远程医疗等。蜂窝模组在5G通信中代表了一种设备或组件,它用于使物联网(Io

SpringBoot集成Prometheus实现监控

SpringBoot配置Prometheuspom.xml引入监控以及prometheus依赖<dependency><groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId></dependency><

软件设计师笔记系列(三)

😀前言随着计算机技术的日益发展,操作系统作为计算机系统的核心组件,其重要性不言而喻。操作系统不仅管理和控制计算机硬件和软件资源,还为用户和其他软件提供服务,使得复杂的计算机系统能够高效、安全和方便地运行。本章将深入探讨操作系统的一些基本概念,如程序与进程、进程的三态模型、死锁及其处理策略,以及磁盘调度算法。通过对这些

热文推荐