力扣第392题 判断子序列 c++ 双指针 动态规划 + 其他解法 附Java代码

41 篇文章 1 订阅
8 篇文章 0 订阅

题目

392. 判断子序列

简单

相关标签

双指针   字符串   动态规划

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

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

进阶:

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

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

示例 1:

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

示例 2:

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

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字符组成。

思路和解题方法 1 双指针

  1. 采用了双指针的思想,用两个指针 i 和 j 分别指向字符串 s 和 t 的开头,并进行比较。
  2. 如果 s[i] 和 t[j] 相等,则说明 s 中的一个字符已经在 t 中找到了匹配的字符,将 i 加 1。
  3. 否则,只将 j 加 1,因为当前的 t[j] 不能匹配 s 中的任何字符。
  4. 当指针 i 到达 s 的结尾时,说明 s 中的所有字符都已经在 t 中按顺序出现,此时返回 true。如果指针 i 没有到达 s 的结尾,但是 j 已经到达了 t 的结尾,则说明 t 中不包含完整的 s ,此时返回 false。

复杂度

        时间复杂度:

                O(N*M)

时间复杂度:

  • 外层循环最多遍历 s 字符串的长度,共需执行 s.size() N 次。
  • 内层循环最多遍历 t 字符串的长度,共需执行 t.size() M 次。
  • 在每个循环中,进行常数时间(O(1))的比较和赋值操作。 所以,总体时间复杂度为 O(s.size() + t.size())  O(N*M)

        空间复杂度

                O(1)

空间复杂度:

  • 只需要使用常数级别的额外空间,因此空间复杂度为 O(1)。

c++ 代码 1

// 定义函数 isSubsequence,输入参数为字符串 s 和字符串 t
bool isSubsequence(string s, string t) {
    // 获取字符串 s 和 t 的长度
    int ss = s.length(),tt = t.length();
    // 定义两个指针 i 和 j,初始化为 0
    int i = 0 , j = 0;
    // 当 i < ss 并且 j < tt 时,执行循环
    while(i<ss&&j<tt)
    {
        // 如果 s[i] 等于 t[j],则将 i 加 1
        if(s[i] == t[j])
            i++;
        // 将 j 加 1
        j++;
    }
    // 如果 i 等于 ss,则返回 true,否则返回 false
    return i == ss;
}

思路和解题方法 2 动态规划

  1. 声明一个二维动态数组 dp,其中 dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符是否可以匹配成一个子序列。

  2. 遍历 s 和 t 的所有子序列,计算 dp 数组的值。对于每个位置 (i, j),根据以下两种情况更新 dp[i][j] 的值:

    a. 如果 s 的第 i 个字符和 t 的第 j 个字符相等,则 dp[i][j] 的值等于 dp[i-1][j-1] + 1,表示当前字符可以匹配成功,并且在之前的匹配基础上多了一个字符。

    b. 如果 s 的第 i 个字符和 t 的第 j 个字符不相等,则 dp[i][j] 的值等于 dp[i][j-1],表示当前字符无法匹配,需要跳过。

  3. 遍历结束后,如果 dp[s.size()][t.size()] 的值等于 s 的长度,则说明 s 是 t 的子序列,返回 true,否则返回 false。

复杂度

        时间复杂度:

                O(N*M)

时间复杂度:

  • 外层循环遍历 s 字符串的长度,共需执行 s.size() N 次。
  • 内层循环遍历 t 字符串的长度,共需执行 t.size() M 次。
  • 在每个循环中,进行常数时间(O(1))的比较和赋值操作。 所以,总体时间复杂度为 O(s.size() * t.size())   O(N*M)

        空间复杂度

                O(N*M)

空间复杂度:

  • 创建了一个二维数组 dp,大小为 (s.size() + 1) * (t.size() + 1)   N*M
  • 需要额外的空间来存储计算结果,因此空间复杂度为 O(s.size() * t.size())  O(N*M)

c++ 代码 2

class Solution {
public:
    bool isSubsequence(string s, string t) {
        // 声明一个二维动态数组 dp,其中 dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符是否可以匹配成一个子序列
        vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));

        // 遍历 s 和 t 的所有子序列,计算 dp 数组的值
        for (int i = 1; i <= s.size(); i++) {
            for (int j = 1; j <= t.size(); j++) {
                // 如果 s 的第 i 个字符和 t 的第 j 个字符相等,则 dp[i][j] 的值等于 dp[i-1][j-1] + 1,否则 dp[i][j] 等于 dp[i][j-1]
                if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
                else dp[i][j] = dp[i][j - 1];
            }
        }

        // 如果 dp[s.size()][t.size()] 的值等于 s 的长度,则说明 s 是 t 的子序列,返回 true,否则返回 false。
        if (dp[s.size()][t.size()] == s.size()) return true;
        return false;
    }
};

其他c++算法代码

class Solution {
public:
    bool isSubsequence(string s, string t) {
        // 在字符串 t 的开头插入一个空格,使得下标从1开始
        t.insert(t.begin(), ' ');
        
        int len1 = s.size(); // 字符串 s 的长度
        int len2 = t.size(); // 字符串 t 的长度
        
        // 定义二维数组 dp,用于存储字符在 t 中的下一个出现位置
        vector<vector<int> > dp(len2 , vector<int>(26, 0));

        // 遍历所有小写字母
        for (char c = 'a'; c <= 'z'; c++) {
            int nextPos = -1; // 表示接下来再不会出现该字符

            // 从后往前遍历字符串 t
            for (int i = len2 - 1; i >= 0; i--) {
                dp[i][c - 'a'] = nextPos;
                
                if (t[i] == c)
                    nextPos = i;
            }
        }

        int index = 0; // 记录当前在 t 中的位置
        for (char c : s) {
            index = dp[index][c - 'a']; // 获取下一个字符 c 的位置
            
            if (index == -1)
                return false; // 如果下一个字符 c 不存在,则返回 false
        }
        
        return true; // 如果所有字符都存在,则返回 true
    }
};



class Solution {
public:
    // 定义函数 isSubsequence,输入参数为字符串 s 和字符串 t,返回 bool 类型
    bool isSubsequence(string s, string t) {
        // 使用队列来存储字符串 s 中的字符
        queue<char> q;
        for(auto& c : s){
            q.push(c);
        }
        // 循环遍历字符串 t 中的每个字符
        for(auto& c : t){
            // 如果队列不为空,且队头元素等于当前字符,则将队头元素出队
            if(!q.empty() && c == q.front())  q.pop();
        }
        // 如果队列为空,说明字符串 s 是字符串 t 的子序列,返回 true,否则返回 false
        return q.empty();
    }
};




java

  • 使用一个二维数组 dp 来存储状态转移值,其中 dp[i][j] 表示字符串 s 的前 i 个字符和字符串 t 的前 j 个字符之间的最长公共子序列长度。
  • 通过遍历字符串 s 和 t 的所有字符,根据字符是否相等来更新状态转移值。
  • 最后判断 dp[length1][length2] 是否等于字符串 s 的长度,如果相等,则说明字符串 s 是字符串 t 的子序列。
public class Solution {
    public boolean isSubsequence(String s, String t) {
        // 获取字符串 s 和 t 的长度
        int length1 = s.length();
        int length2 = t.length();
        
        // 定义一个二维数组 dp,dp[i][j] 表示以 s 的前 i 个字符和 t 的前 j 个字符为结尾的子序列长度
        int[][] dp = new int[length1+1][length2+1];
        
        // 遍历字符串 s 的每个字符
        for(int i = 1; i <= length1; i++){
            // 遍历字符串 t 的每个字符
            for(int j = 1; j <= length2; j++){
                // 当 s 的第 i 个字符等于 t 的第 j 个字符时
                if(s.charAt(i-1) == t.charAt(j-1)){
                    // 子序列长度加1,取决于前一个字符子序列长度加1
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    // 当 s 的第 i 个字符不等于 t 的第 j 个字符时,取决于前一个字符在 t 中的位置
                    dp[i][j] = dp[i][j-1];
                }
            }
        }
        
        // 判断 dp[length1][length2] 是否等于 s 的长度,如果等于,则 s 是 t 的子序列,返回 true,否则返回 false
        if(dp[length1][length2] == length1){
            return true;
        }else{
            return false;
        }
    }
}

觉得有用的话可以点点赞,支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每天都会不定时更新哦  >人<  。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值