30. 串联所有单词的子串

30. 串联所有单词的子串

题目描述

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例1:

输入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。

示例2:

输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]

题解:

s 的长度为 nwords 的长度为 m ,每个单词的长度为 w

使用滑动窗口,因为单词长度都相同,可以每次滑动一个单词长度。

根据 ws 分成 w 组候选序列,对于每组序列能否由 words 构成,使用哈希表统计数目即可,见代码。

时间复杂度: O ( n ∗ w ) O(n * w) O(nw)

代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if ( !s.size() || !words.size() || !words[0].size() ) return {};
        int n = s.size(), m = words.size(), w = words[0].size();

        unordered_map<string, int> hash;
        for( auto& it : words ) ++hash[it];
        vector<int> ret;
        string word;
        for ( int i = 0; i < w; ++i ) {
            unordered_map<string, int> wd;
            int cnt = 0;//统计 wd 中存在几个单词在 hash 中也存在
            for ( int j = i; j + w <= n; j += w ) {
                if ( j >= i + m * w ) {
                    word = s.substr( j - m * w, w );
                    if ( --wd[word] < hash[word] ) --cnt;
                }
                word = s.substr(j, w);
                ++wd[word];
                if ( wd[word] <= hash[word] ) ++cnt;
                if ( cnt == m ) ret.push_back( j - (m - 1) * w );
            }
        }
        return ret;
    }
};
/*
时间:108ms,击败:70.05%
内存:41MB,击败:34.65%
*/

优化:可以看到上面一份代码中,不管 word 是不是 words 列表中的单词,都无脑加入或删除。对于 s="foobarthefoo"words=['foo','bar,'foo'] 来说,在匹配到 the 时,该单词不在 words 中,也就是说,前面窗口维护的元素没有用了,需要重置。于是可以考虑维护只存在 words 中的单词的窗口,下面给出优化代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if ( !s.size() || !words.size() || !words[0].size() ) return {};
        int n = s.size(), m = words.size(), w = words[0].size();

        unordered_map<string, int> hash;
        for( auto& it : words ) ++hash[it];
        vector<int> ret;
        for ( int i = 0; i < w; ++i ) {
            unordered_map<string, int> wd;
            int cnt = 0, l = i;
            for ( int j = i; j + w <= n; j += w ) {
                auto word = s.substr(j, w);
                if ( hash.count( word ) ) {
                    ++wd[word];
                    if ( wd[word] <= hash[word] ) ++cnt;
                    else {
                        while ( wd[word] > hash[word] ) {
                            auto tmp = s.substr( l, w );
                            --wd[tmp];
                            if ( wd[tmp] < hash[tmp] ) --cnt;
                            l += w;
                        }
                    }
                    if ( cnt == m ) {
                        ret.push_back( l );
                        --wd[s.substr(l, w)];
                        --cnt;
                        l += w;
                    }
                } else {
                    wd.clear();
                    cnt = 0;
                    l = j + w;
                }
            }
        }
        return ret;
    }
};
/*
时间:40ms,击败:96.45%
内存:16.1MB,击败:86.31%
*/

上述代码每次取出一个长度为 w 的子串,插入或删除,每次操作时间复杂度为: O ( w ) O(w) O(w) ,可以考虑使用字符串哈希优化成 O ( 1 ) O(1) O(1)

时间复杂度: O ( n ) O(n) O(n)

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        using ULL = unsigned long long;
        const int P = 131;

        int n = s.size();
        int m = words.size();
        int w = words[0].size();
        if ( !n || !m || !w ) return {};
        if ( n < m * w ) return {};

        vector<ULL> h( n + 1 );
        ULL ans = 1;
        for ( int i = 1; i <= n; ++i ) {
            if ( i <= w ) ans *= P;
            h[i] = h[i - 1] * P + s[i - 1];
        }
        unordered_map<ULL, int> hash;
        for( auto& it : words ) {
            ULL t = 0;
            for ( auto& c : it ) t = t * P + c;
            ++hash[t];
        }
        vector<int> ret;
        for ( int i = 1; i <= w; ++i ) {
            unordered_map<ULL, int> wd;
            int cnt = 0, l = i;
            for ( int j = i; j + w <= n + 1; j += w ) {
                auto word = h[j + w - 1] - h[j - 1] * ans;
                if ( hash.count( word ) ) {
                    ++wd[word];
                    if ( wd[word] <= hash[word] ) ++cnt;
                    else {
                        while ( wd[word] > hash[word] ) {
                            auto tmp = h[l + w - 1] - h[l - 1] * ans;
                            --wd[tmp];
                            if ( wd[tmp] < hash[tmp] ) --cnt;
                            l += w;
                        }
                    }
                    if ( cnt == m ) {
                        ret.push_back( l - 1 );
                        --wd[h[l + w - 1] - h[l - 1] * ans];
                        --cnt;
                        l += w;
                    }
                } else {
                    wd.clear();
                    cnt = 0;
                    l = j + w;
                }
            }
        }
        return ret;
    }
};
/*
时间:24ms,击败:99.70%
内存:13.1MB,击败:98.88%
*/

最后,贴一版本枚举 s 的每个字符,然后判断子串 s[i...i+m*w-1] 是否满足条件的代码,使用了字符串哈希,时间复杂度: O ( n ∗ m ) O(n*m) O(nm)

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        using ULL = unsigned long long;
        const int P = 131;
        if ( !s.size() || !words.size() ) return {};
        int lens = s.size();
        int n = words.size();
        int l = words[0].size();
        if ( lens < n * l ) return {};
        vector<ULL> h( lens + 1 );
        ULL ans = 1;
        for ( int i = 1; i <= lens; ++i ) {
            if ( i <= l ) ans *= P;
            h[i] = h[i - 1] * P + s[i - 1];
        }
        unordered_map<ULL, int> hash;
        for( auto& it : words ) {
            ULL t = 0;
            for ( auto& c : it ) t = t * P + c;
            hash[t] += 1;
        }
        int tot = n * l;
        unordered_map<ULL, int> window;
        bool flag;
        vector<int> ret;
        for ( int i = 1; i <= lens - tot + 1; ++i ) {
            window.clear();
            flag = true;
            for ( int j = i; j + l <= i + tot; j += l ) {
                ULL tmp = h[j + l - 1] - h[j - 1] * ans;
                ++window[tmp];
                if ( window[tmp] > hash[tmp] ) {
                    flag = false;
                    break;
                }
            }
            if ( flag ) ret.push_back( i - 1 );
        }
        return ret;
    }
};
/*
时间:124ms,击败:67.71%
内存:18.9MB,击败:71.09%
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值