java数据结构与算法刷题-----LeetCode392. 判断子序列

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/123063846

在这里插入图片描述

1. 双指针

解题思路:时间复杂度O( s + t s+t s+t)两个字符串都遍历的一遍,空间复杂度O( 1 1 1)
  1. 一个指针指向s的字符,一个指针指向t的字符
  2. 如果两个字符对应,则匹配成功一个字符,两个指针一起后移
  3. 如果两个字符没有对应上,则t的指针后移,试图继续和s的当前字符匹配
代码

在这里插入图片描述

class Solution {
    public boolean isSubsequence(String s, String t) {
        int sLen = s.length(),tLen = t.length();
        char[] sCA = s.toCharArray(),tCA = t.toCharArray();
        int sIndex = 0;//s字符串的指针
        for(int i = 0;i<tLen;i++){//t字符串的指针
            //如果t当前字符,满足s当前字符,则sIndex后移,匹配下一个s中的字符
            if(sIndex<sLen && sCA[sIndex] == tCA[i]) sIndex++;
        }
        return sIndex == sLen;//如果s中字符完美进行匹配,返回true
    }
}

2. 动态规划

上面的方法时间复杂度很低,但是如果有1亿个s想要判断是否是t的子序列,就必须先对t进行预处理,也就是只有预处理的时候会慢,之后处理s的时候,会非常快。

  1. 前缀树,把t所有可能得前缀预处理,后续直接用s匹配即可
  2. 动态规划仿前缀树。使用二维数组,保存如果想在当前位置之后找到特定字母,是否可以找到,如果能找到,应该跳转到哪个位置找
    在这里插入图片描述
解题思路:时间复杂度O( t ∗ 26 + s t*26 + s t26+s),空间复杂度O( t ∗ 26 t*26 t26)
  1. 时间复杂度:26是因为t和s由26个字母组成。dp数组预处理的时间复杂度是O( t ∗ 26 t*26 t26)。之后查找t的子序列,时间复杂度最多不超过O( s s s),也就是完全可以匹配时,才会消耗s时间复杂度,而如果s中只有一个字母可以完成匹配,则与dp数组比较2次即可判断无法完全匹配成功
动态规划思考5步曲
  1. DP数组及下标含义

我们要求出的是在t字符串的i以及i之后的位置,想要匹配指定字母,是否可以匹配到,如果能应该去哪个位置匹配,那么dp数组中存储的就是当前i位置为起始,最近的一个指定字母出现的位置。要求出谁的位置。显然是到达i位置后,指定字母的出现位置,那么下标就是代表现在到了t字符串哪个位置,我们要在当前位置找哪个字母。显然,需要两个下标表示,一个下标代表当前t的位置,另一个下标表示我们接下来想要匹配的是26个字母中的哪一个。故这道题的dp数组需要二维数组

  1. 递推公式
  1. 假设t字符串的长度为m,我们建立dp大小为dp[m][26]. 因为下标从0开始,正好dp[m-1]是t的最后一个字符位置。而dp[m]行表示字符串结束位置。
  2. 我们先将dp[m]行全部赋值为m,表示在当前位置匹配任何字母都是匹配不到的
  3. 然后处理dp[m-1]也就是t的最后一个位置。假设t最后一个位置是a.那么dp[m-1][0]位置,就表示t的最后一个位置为a字母。将其设置为m-1. 而其余字母位置,dp[m-1][1-26]都为m(dp[m][1-26]),表示从m-1位置找除了a以外的字母,都是找不到的。
  4. 同理,dp[m-2]行也就是t的倒数第二个位置
  1. 假设这个位置字母也是a,那么对于dp[m-2][0] = m-2,表示m-2行找字母a,m-2行就是最近的位置,其余位置都为m-1行的状态 = dp[m-1][1-26]
  2. 假设这个位置是b。那么对于dp[m-2][1] = m-2,表示m-2行找字母b,m-2行就是最近的位置
  3. 而dp[m-2][0] = dp[m-1][0] = m-1,为什么?因为我们知道dp[m-1]位置字母是a,那么我们现在想在m-2后面找a的最近出现位置,那么自然是m-1位置。
  1. 因此递推公式为
  1. m行dp[m][1-26] = m,表示m是不可达区间,m表示当前位置和之后的位置找指定字母是找不到的
  2. 其余的dp[i][j],如果t[i] = j 则dp[i][j] = i.表示如果当前i位置出现字母是j,那么当前位置及之后,j字母最早出现位置就是i
  3. 而剩余的t[i] != j的位置,dp[i][j] = dp[i+1][j].表示如果当前i位置出现字母不是j,那么去问问i+1行吧
  1. dp数组初始化

在这里插入图片描述

  1. 数组遍历顺序:肯定是先行后列,因为行是当前t的字符位置,列是26个字母中的哪一个。主角是对t进行预处理,所以先行后列
  2. 打印dp数组(自己生成dp数组后,将dp数组输出看看,是否和自己预想的一样。)

在这里插入图片描述

代码

在这里插入图片描述

class Solution {
    //前缀树,使用动态规划简单实现前缀树
    public boolean isSubsequence(String s, String t) {
        int n = s.length(), m = t.length();
        //对t建立dp数组,每个位置都有26种可能(因为t是由26个字母组成)
        //dp数组含义:dp[i:第几个位置][j:要找26个字母的哪个] = 在当前i和后面[i,m]的位置,最近的j字母的位置
        //简单来讲,我们想要实现,匹配s的每一个字符时,可以直接获取应该去t的哪一个位置匹配
        int[][] f = new int[m + 1][26];
        //m是t字符串之外的地方,我们将其设置为不可达,也就是匹配不到
        for (int i = 0; i < 26; i++) f[m][i] = m;//将匹配不到,设置为m
        //自底向上规划
        for (int i = m - 1; i >= 0; i--) {
            for (int j = 0; j < 26; j++) {//每一个行的每个字母都要处理
                //如果t当前行(i位置),存放的正好是j。则f[i][j] = i.表示第i行匹配j字母的最近位置 = i
                if (t.charAt(i) == j + 'a')f[i][j] = i;
                //如果存放的不是j,那么想要在当前位置找j,就得去下一行找。也就是f[i+1][j],看看这一行有没有
                else f[i][j] = f[i + 1][j];
            }
        }
        //规划完成后,开始匹配
        int add = 0;//dp数组的行,也就是t的下标
        for (int i = 0; i < n; i++) {
            //如果当前add行匹配s[i]字母时,发现最近的一个s[i]字母 = m行,也就是匹配不到的含义,则返回false
            if (f[add][s.charAt(i) - 'a'] == m) return false;
            //如果当前位置发现最近的一个s[i]字母 != m ,说明后面有这个字母
            //则直接跳到这一行完成匹配,然后去下一行,匹配s[i+1]字母
            add = f[add][s.charAt(i) - 'a'] + 1;
        }
        return true;//匹配完成则返回true
    }
}
  • 32
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殷丿grd_志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值