面试经典150题——判断子序列

news2024/12/27 14:35:15

​"Success is not final, failure is not fatal: It is the courage to continue that counts." 

- Winston Churchill

brown cardboard box with yellow light

1. 题目描述

2.  题目分析与解析

2.1 思路一——双指针

按照双指针的解法应该大家都能比较快的想出来,就是一个指针pointS指向字符串s,一个指针pointT指向字符串t,通过从前向后遍历t字符串,判断pointT指向的当前字符和pointS的字符是否相等,相等就将pointS指针也后移,如果pointS能够遍历结束则说明满足条件,返回true。

2.2 思路二——通过一个二维数组存放t字符串中每个字符及其对应下标

由于题目的进阶版本要求:

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

如果每个s都需要去遍历一遍t字符串,那么时间开销是很大的,所以我们现在就要想办法把 t 中的信息怎么存储起来来换取时间。之所以是需要存储 t 中的信息,是因为我们每一个 s 字符串是不相同的,而每一次的 t 字符串都是相同的,也就是说对于一个多次使用的东西,我们要想办法把他的信息最不能得高效的去利用。

因为我们想要达到的目的是遍历每一个s字符串,要在t中找到是否存在该序列,我们就可以通过遍历s的每一个字符,看t中是否有满足的匹配字符——不仅字符要匹配,而且下标要呈递增的模式

  • 因此我们就可以把 t 字符串中每一个字符的下标存储起来,这样我们就能够得到一个类似于二维数组的结构,如下:

  • 而后对于每一个s字符串,遍历,根据当前字符找到对应的键,在下标数组中找到一个合适的下标,能找到就继续,不能找到就返回false。

  • 这个合适的下标指的是:当前选择的下标要比上一次选择的下标大,保证字符间顺序。

  • 同时这个寻找合适下标的过程,是可以运用二分查找的,这是因为我的下标数组肯定都是有序的,因为我是从前到后遍历t字符串,这样形成的下标数组肯定是升序的,给使用二分查找创造了条件。

算法执行主要步骤:

  1. 通过遍历长字符串t,把每一个字符出现的位置记录下来

  2. 然后遍历短字符串s,查找对应的字符在键值和下标数组的hashMap中是否存在,如果存在找到满足下标大于前一个字符在长字符串中下标的字符

    • 该查找过程可以使用二分查找

    • 也可以通过一个新的数组记录上一次在该字符处使用的位置

主要讲一下通过一个新的数组记录上一次在该字符处使用的位置的方法,见下图:

上图展示了找目标字符串s的前三个字符的过程,当找最后一个字符a时,因为我当前a对应的数组的值为0,所以下一次我就只需要从1处找是否有下标满足大于前一个下标也就是c的下标5的目标,如下:

3. 代码实现

3.1 思路一——双指针

    // 判断子序列
    public boolean isSubsequence(String s, String t) {
        // 解题思路
        // 1. 双指针判断是否为子序列,一个指向s,一个指向t,
        //    如果s的指针指向的字符等于t的指针指向的字符,s的指针向后移动
        //    最后判断s的指针是否指向了s的末尾
        // 2. 时间复杂度O(n)
        // 3. 空间复杂度O(1)
        // 4. 代码实现
        int i = 0, j = 0;
        if (s.length == 0){
            return true;​
        ​}
        while (i < s.length() && j < t.length()) {
            if (s.charAt(i) == t.charAt(j)) {
                i++;
            }
            j++;
            if (i == s.length()) {
                return true;
            }
        }
        return false;
    }

3.2 思路二——二维数组

3.2.1 使用二分查找
class Solution {
   public boolean isSubsequence(String s, String t) {
       int n = s.length(), m = t.length();
       // 通过遍历长字符串,把每一个字符的位置记录下来
       HashMap<Character, ArrayList<Integer>> hashMap = new HashMap<>();
       for (int i = 0; i < m; i++) {
           char ch = t.charAt(i);
           if (!hashMap.containsKey(ch)) {
               hashMap.put(ch, new ArrayList<>());
          }
           hashMap.get(ch).add(i);
      }
       // 用来记录上一个字符的索引
       int preIndex = -1;
       for (int i = 0; i < n; i++) {
           // 方法1:使用二分查找在hashMap.get(s.charAt(i))找到大于preIndex得最小值
           if (!hashMap.containsKey(s.charAt(i))) {
               return false;
          }
           int left = 0;
           int right = hashMap.get(s.charAt(i)).size() - 1;
           // 二分查找过程
           while (left < right) {
               int mid = left + (right - left) / 2;
               if (hashMap.get(s.charAt(i)).get(mid) <= preIndex) {
                   left = mid + 1;
              } else {
                   right = mid;
              }
          }
           if (hashMap.get(s.charAt(i)).get(left) <= preIndex) {
               return false;
          }
           preIndex = hashMap.get(s.charAt(i)).get(left);    
      }
       return true;
  }
}
3.2.2 使用数组记录下标
    public boolean isSubsequence3(String s, String t) {
        int n = s.length(), m = t.length();
        // 通过遍历长字符串,把每一个字符的位置记录下来
        HashMap<Character, ArrayList<Integer>> hashMap = new HashMap<>();
        HashMap<Character, Integer> indexMap = new HashMap<>();
        
        for (int i = 0; i < m; i++) {
            char ch = t.charAt(i);
            if (!hashMap.containsKey(ch)) {
                hashMap.put(ch, new ArrayList<>());
            }
            hashMap.get(ch).add(i);
        }
        // 给indexArray初始化
        for (Character c : hashMap.keySet()) {
            indexMap.put(c, -1);
        }
        // 用来记录上一个字符的索引
        int preIndex = -1;
        // 然后遍历短字符串,看其相应的顺序,对应在hashMap对应得Array中最小的索引,最后看是否能找全
        for (int i = 0; i < n; i++) {
     // 没有该字母 || (该字母的索引已经到末位&&该字母的索引不是-1也就是不是第一个字母)
            if ((!hashMap.containsKey(s.charAt(i)) || (hashMap.get(s.charAt(i)).get(hashMap.get(s.charAt(i)).size() - 1) <= indexMap.get(s.charAt(i)))  && indexMap.get(s.charAt(i)) != -1)) {
                return false;
            }
            // 理论上的下一个目标位置
            int index = indexMap.get(s.charAt(i)) + 1;
            // 有对应的下标
            if (index < hashMap.get(s.charAt(i)).size()) {
                // 找到大于preIndex得最小值
                while (preIndex >= hashMap.get(s.charAt(i)).get(index)) {
                    index++;
                    if (index == hashMap.get(s.charAt(i)).size()) {
                        break;
                    }
                }
                //没找到
                if (index == hashMap.get(s.charAt(i)).size()) {
                    return false;
                }
                //找到了
                indexMap.put(s.charAt(i), index);
                preIndex = hashMap.get(s.charAt(i)).get(index);
            }
            //无对应的字母键
            else {
                return false;
            }
        }
        return true;
    }

4. 运行结果

4.1 双指针

4.2 二维数组——二分和数组记录下表相同

5. 相关复杂度分析

5.1 双指针

  • 时间复杂度:O(n+m),n为字符串t的长度,m为字符串s的长度。循环同时对s和t进行,每次无论是匹配成功还是失败,都有至少一个指针发生右移,两指针能够位移的总距离为n+m。

  • 空间复杂度:0(1),声明了2个变量

5.2 二维数组(二分查找)

  • 时间复杂度:O(MlogN+N),N为字符串s的长度,M为字符串t长度。对t中的每个字符都需要logN的查找一次;而且为了处理出待查找的序列,还需要O(N)的遍历字符串S。

  • 空间复杂度:O(N),需要一个大小为N的哈希表。

本人公众号,专注简单易懂的算法解析

下一篇:使用动态规划解决该题目

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

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

相关文章

消息中间件(消息队列)简介

MQ&#xff08;message queue&#xff09;消息队列&#xff0c;也叫消息中间件。消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能&#xff0c;成为异步RPC的主要手段之一。它是类似于数据库一样需要独立部…

消息中间件之RocketMQ源码分析(六)

Consumer消费方式 RocketMQ的消费方式包含Pull和Push两种 Pull方式。 用户主动Pull消息&#xff0c;自主管理位点&#xff0c;可以灵活地掌控消费进度和消费速度&#xff0c;适合流计算、消费特别耗时等特殊的消费场景。 缺点也显而易见&#xff0c;需要从代码层面精准地控制…

【发票识别】新增针对图片发票的识别(升级中)

说明 为了完善发票识别的功能&#xff0c;目前发票识别支持发票图片格式的识别&#xff0c;增加可用性。 体验 体验地址&#xff1a;https://invoice.behappyto.cn/invoice-service/ 体验地址上面有示例的发票&#xff0c;可以下载上传识别或者复制url地址进行识别。 技术栈…

数据结构.二叉树

一、树的基本概念 二、树的常考性质 三、二叉树的基本概念 四、二叉树的顺序存储 五、二叉树的链式存储 六、二叉树的遍历

深入剖析 Cortex-M4 微控制器在嵌入式系统中的特性和优势

Cortex-M4 微控制器是 ARM Cortex-M 架构中的一种类型&#xff0c;它具有许多功能和特性&#xff0c;使其在嵌入式系统中具有显著的优势。本文将深入剖析 Cortex-M4 微控制器的特性和优势&#xff0c;并提供示例代码来演示其用法。 ✅作者简介&#xff1a;热爱科研的嵌入式开发…

TreeSet 集合

TreeSet 集合 1. 概述2. 方法3. 遍历方式4. 两种排序方式4.1 默认排序规则/自然排序4.1.1 概述4.1.2 compareTo()方法4.1.3 代码示例14.1.4 代码示例2 4.2 比较器排序4.2.1 概述4.2.2 compare()方法4.2.3 代码示例14.2.4 代码示例2 4.3 排序方式的对比 5. 注意事项 文章中的部分…

5 款提升 UI 设计效率的软件工具

你知道如何选择正确的UI设计软件吗&#xff1f;你知道设计漂亮的用户界面和带来良好用户体验的应用程序需要什么界面设计软件吗&#xff1f;基于APP界面的不同功能&#xff0c;所选择的APP界面设计软件也会有所不同。然而&#xff0c;并不是说所有的APP界面设计软件都非常精通&…

ShardingSphere 5.x 系列【3】分库分表中间件技术选型

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 前言2. My Cat3. ShardingSphe…

Chronos靶机渗透

Chronos靶机 一.信息收集1.靶机IP地址确认2.目录扫描3.常见漏洞扫描5.web网站探测1.网页2.源代码 二.网站渗透1.命令执行2.抓包---burp suite3.反弹shell 三.提权1.node.js原核污染第一个flag 2.sudo提权第二个flag 一.信息收集 1.靶机IP地址确认 ┌──(root㉿kali)-[/] └─…

Codeforces Round 914 (Div. 2)(D1/D2)--ST表

Codeforces Round 914 (Div. 2)(D1/D2)–ST表 D1. Set To Max (Easy Version) 题意&#xff1a; 给出长度为n的数组a和b&#xff0c;可以对a进行任意次数操作&#xff0c;操作方式为选择任意区间将区间内值全部变成该区间的最大值&#xff0c; 是否有可能使得数组a等于数组b…

centos跟新时间为网络时间

安装ntp yum install ntp -y 从本地获取网络时间并更新 ntpdate pool.ntp.org 时间设置成功

简单实验 java spring cloud 自定义负载均衡

1.概要 1.1 说明 这个是在前一个测试上的修改&#xff0c;所以这里只体现修改的内容。前一个测试的地址&#xff1a;检查实验 spring cloud nacos nacos-server-2.3.0-CSDN博客 1.2 记忆要点 1.2.1 引入对象 Autowired DiscoveryClient discoveryClient; 1.2.2 获取服务实…

简单说说redis分布式锁

什么是分布式锁 分布式锁&#xff08;多服务共享锁&#xff09;在分布式的部署环境下&#xff0c;通过锁机制来让多客户端互斥的对共享资源进行访问/操作。 为什么需要分布式锁 在单体应用服务里&#xff0c;不同的客户端操作同一个资源&#xff0c;我们可以通过操作系统提供…

Java SPI 代码示例

Java Service Provider Interface 是JDK自带的服务提供者接口又叫服务发现机制更是一种面向接口的设计思想。即JDK本身提供接口类&#xff0c; 第三方实现其接口&#xff0c;并作为jar包或其他方式注入到其中&#xff0c; 在运行时会被JDK ServiceLoader 发现并加载&#xff0c…

锐捷VSU和M-LAG介绍

参考网站 堆叠、级联和集群的概念 什么是堆叠&#xff1f; 框式集群典型配置 RG-S6230[RG-S6501-48VS8CQ]系列交换机 RGOS 12.5(4)B1005版本 配置指南 总结 根据以上的几篇文章总结如下&#xff1a; 级联&#xff1a;简单&#xff0c;交换机相连就叫级联&#xff0c;跟搭…

SaaS 电商设计 (八) 直接就能用的一套商品池完整的设计方案(建议收藏)

目录 一.前言1.1 在哪些业务场景里使用1.2 一些名词搞懂他1.3 结合业务思考一下-业务or产品的意图 二.方案设计2.1 业务主流程2.2 一步步带你分析B端如何配置2.3 数据流2.3.1 ES 数据表建设2.3.2 核心商品池流程2.3.2.1 商品池B端维护流程2.3.2.2 商品池版本更新逻辑 2.4 核心代…

PySpark(三)RDD持久化、共享变量、Spark内核制度,Spark Shuffle

目录 RDD持久化 RDD 的数据是过程数据 RDD 缓存 RDD CheckPoint 共享变量 广播变量 累加器 Spark 内核调度 DAG DAG 的宽窄依赖和阶段划分 内存迭代计算 Spark是怎么做内存计算的? DAG的作用?Stage阶段划分的作用? Spark为什么比MapReduce快&#xff1f; Spar…

Java项目服务器部署

Java项目服务器部署 Tomocat Java项目进行云服务器部署 如果有需要比赛部署的同学也可以联系我&#xff0c;我后续还会对于这个专栏继续展开 1、云服务器选购 1.1 阿里云选购&#xff08;宝塔面板&#xff09; 1.2 端口放行 这里说的就是端口放行&#xff0c;后面一些访问比…

打造直播带货商城APP:源码开发技术全解析

直播带货商城APP的创新模式吸引了用户&#xff0c;提升销售业绩&#xff0c;已经成为了近期开发者讨论的热门话题。今天&#xff0c;小编将深入讲解如何打造一款功能强大的直播带货商城APP&#xff0c;着重分析源码开发技术&#xff0c;为开发者提供全方位的指导。 一、前期准…

电商推荐系统

此篇博客主要记录一下商品推荐系统的主要实现过程。 一、获取用户对商品的偏好值 代码实现 package zb.grms;import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Doub…