leetcode438-找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:

输入:
s: "cbaebabacd" p: "abc"

输出:
[0, 6]

解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。

示例 2:

输入:
s: "abab" p: "ab"

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。

一、思路

(一)一次比较的字符串匹配方法

之前想过一些方法,都有各种各样的缺点,比如:
(1)从字符串s中取一个长度与p一样的子串,对两者进行排序,判断是否相等
(2)使用映射法:将每个字符映射到某个数字,并且要求无论怎么相加,都不会出现两个不同(不包括异构)的字符串,结果一致的情况

实际上
(1)是可以通过部分用例的,但是会超时,其根源在于s中的某些字符进行了多次重复匹配,导致时间复杂度过高
(2)的问题在于如何求解这样的映射

一次比较的字符串匹配方法:可以有效解决字符的重复匹配问题。
我们首先设置一个数组dp(这个方法借鉴了一部分动态规划的思想):
dp[i]:表示 从字符串s的第i个字符开始算起,往前dp[i]个字符,都能在字符串p中找到。

我们对字符串s中的每一个字符进行逐个匹配:

  • 当发现字符s[i]能够在p中找到时,认为匹配成功,并且将字符s[i]从p中删除,并且匹配长度+1:dp[i]=dp[i-1]+1
  • 当发现字符s[i]不能在p中找到时,认为匹配失败,那么失败的原因是什么呢?
    其一:字符s[i]确实不在p中,此时长度变为0,要重新开始匹配
    其二:字符s[i]在p中,但是因为之前的删除操作,使得p中的s[i]字符被删除了;
    这种情况下,我们可以从dp[i-1]入手:
    (1)从 i − d p [ i − 1 ] i-dp[i-1] idp[i1]位置开始,直到 i − 1 i-1 i1位置,都是被删除的p中的字符,从中离开始位置最近的相同字符s[j] == s[i]
    (2)新的匹配长度从这里开始,然后再将j之前的字符串补到p上面

C++代码:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        // dp[i]:表示 从字符串s的第i个字符开始算起,往前dp[i]个字符,都能在字符串p中找到
        vector<int> ans;
        vector<int> dp(s.size(), 0);
        if(p.size() > s.size())
            return ans;
        
        int p_len = p.size();
        // sort(p.begin(), p.end());
        string p1 = p;
        if(isMatch(p, s[0]))
            dp[0]++;
        if(dp[0] == p_len){
            int num = 0;
            ans.push_back(num);
            p = p + s[num];
        }

        for(int i=1; i < s.size(); i++){
            if(isMatch(p, s[i])){
                dp[i] = (dp[i - 1] == p_len) ? (p_len) : (dp[i - 1] + 1);
                if(dp[i] == p_len){
                    int num = i - p_len + 1;
                    ans.push_back(num);
                    p = p + s[num];
                }
            }
            else{
                // 不匹配,两种可能:
                //(1) s[i]在字符串p中,因为之前的操作,去掉了
                //(2) s[i]不在字符串中
                if(dp[i - 1] != 0){
                    int begin = i - dp[i - 1];
                    int j = begin;
                    // 从被删除的字符串中,从前往后寻找第一个与s[i]相等的字符
                    while(j < i && s[j] != s[i])
                        j++;
                    if(j == i){     // 没有找到,s[i]不存在于p中
                        p = p1;
                        dp[i] = 0;
                    }
                    else{           // 找到了,从s[j]开始起的字符串直到s[i],都能存在于p
                        dp[i] = i - j;
                        if(dp[i - 1] != p_len)
                            p = p + s.substr(begin, j - begin);
                    }
                }
                else{
                    p = p1;
                    dp[i] = 0;
                }
            }
        }
        return ans;
    }

    // 判断是否匹配,匹配则删除
    bool isMatch(string& s, char c){
        for(int i=0; i < s.size(); i++){
            if(s[i] == c){
                s.erase(s.begin() + i, s.begin() + i + 1);
                return true;
            }
        }
        return false;
    }
};

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值