【leetcode】392. 判断子序列

问题描述:

  • 给定字符串 st,判断 s 是否为 t 的子序列。
    • 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,aceabcde 的一个子序列,而 aec 不是)。
  • 进阶:
    • 如果有大量输入的 S,称作 S1, S2, ... , Sk,其中 k >= 10^9(10 亿),你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

核心思路:

  • 暴力解法就是双指针遍历,这种方法是带有贪心思想的,也就是对于 s 对应位置的字符,都选择 t 中对应位置字符第一次出现的位置。
  • 但对于进阶问题来说,双指针解法就显得太慢了。
    • 动态规划解法就是在遍历 s 前,先预先处理 t 串,这种思想和 KMP 字符匹配的思想很相似,将长字符串中的信息提取出来供后续使用。
    • 实际上就是要考虑什么样的信息对于 s 来说是有效的就可以了,所需要的信息就是遍历 s 中字符,待匹配字符 s[i] 在长字符串 t 中下一次出现的位置是多少
    • 假如长字符串 t 的长度为 n,建立一个 n*26 大小的矩阵 dp,表示每个位置上 26 个字符下一次出现的位置,也就是说 dp[i][c] 代表t[i] 位置上下一个匹配字符 c 的索引
    • 得到 dp 数组之后,只需要遍历 s 串,过程中从 dp 数组中获得对应字符的索引即可。【可以注意到该方法其实也是有贪心思想,dp 数组保留的是下一个字符的位置,因为当前只需要跳到下一个匹配的字符即可】
  • 还有一种二分搜索的做法。
    • 首先仍然是预处理字符串 t,但和动态规划做法不同,只需要保存每个字符存在的索引即可。
    • 接着外部初始化一个索引位置 p = -1 用作后续的遍历;遍历 s 串,因为每一个字符都保存了一系列位置,所以可以用二分搜索的方法获得比 p 索引大的第一个位置,如果找到这个位置 idx,就将 p 更新为 idx

代码实现:

  • 动态规划解法代码实现如下:【代码参考自网友题解,和官方题解不一样,该方法有其他的一些处理,如先在 t 串前面加多一个空字符,原因可以参考注释或参考下面给出链接】
    class Solution {
    public:
        bool isSubsequence(string s, string t) {
            t = " " + t; // 加多一个字符,方便后续 dp[0][~] 的准确性
            int m = s.size(), n = t.size();
            vector<vector<int>> dp(n, vector<int>(26, 0));
            for(char c = 'a'; c <= 'z'; ++c) {
                int next_pos = -1; // 表示接下来再不会出现该字符
                for(int i = n-1; i >= 0; --i) { // 从后往前遍历
                    dp[i][c-'a'] = next_pos;
                    if(t[i] == c) // 如果当前位置和 c 相等,则更新 next_pos,给前面的 dp[i-1][c] 使用
                        next_pos = i;
                }
            }
            int idx = 0;
            for(char& c : s) {
                idx = dp[idx][c-'a']; // 找到下一个位置
                if(idx == -1) return false;
            }
            return true;
        }
    };
    
  • 二分搜索解法代码实现如下:
    class Solution {
    public:
        bool isSubsequence(string s, string t) {
            vector<vector<int>> pos(26);
            for (int i = 0; i < t.size(); ++i)
                pos[t[i] - 'a'].push_back(i);
            if (s.size() > t.size())
                return false;
            int p = -1;
            for (char c : s) {
                auto &ps = pos[c - 'a'];
                auto it = upper_bound(ps.begin(), ps.end(), p);
                if (it == ps.end())
                    return false;
                p = *it;
            }
            return true;
        }
    };
    

参考内容:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值