【LeetCode】30. Substring with Concatenation of All Words题解

       给定一个字符串s和很多相同长度的字符串words,这些字符串可以有相同的,任务是在s里找到一部分正好是words的全部字符串按任意顺序组合起来,输出符合条件的部分在s的起始位置。题意依旧是so easy,几句话搞定的事,初看的时候感觉复杂度好高,这得比到海枯石烂啊,偷偷看了下大佬的DA,分享一下两种解法。先贴原题。

You are given a string, s, and a list of words, words, that are all of the same length. Find all starting indices of substring(s) in s that is a concatenation of each word in words exactly once and without any intervening characters.

Example 1:

Input:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.

Example 2:

Input:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
Output: []

 一、挨个比较不成就无脑换下一个法

       想法其实很朴素,但是我题做得少一开始没敢下手。依次从s[0],s[1],s[2]...开始找words中的字符串,如果不多不少全部找到,就成功找到一个位置,然后继续从s的下一个位置找,如果多了少了或者中混进了其他字符串就失败,还是从s的下一个位置找。举个栗子:比如words字符串长度都是4,在s中从起点0开始比较,发现0-3符合,继续4-7也符合,但是8-11不符合,于是退出,从起点1再依次比较......

       假设words中字符串长度都是len,需要注意的是(1)在s中取一段len后怎么确定是否在words里面,(不要求顺序所以稍微增加了寻找难度),(2)又怎么确定这一段是否重复出现了(words里有重复的串,但是如果s里的一部分重复次数超过words也是不行的)。

       不借助其他工具,我们可以用在s里取len,然后遍历words数组看下有木有,如果有,就把words[i]和word[n--]交换,那么后面再遍历words的时候就不会再看它了,这样既解决了有没有又解决了重不重复的问题,是一种比较好用且常见的手段。具体细节参看完整代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (words.size() > 0){
		    int num = words.size(), len = words[0].size();
		    int ed = s.size() - num * len + 1;
		    for (int i = 0; i < ed; ++i){
			    int m = 0, n = num, k = i;
			    while (m < n){
                    //words[m][0] == s[k]似乎没有用,删掉一样能跑,但是有它可以简化很多比较
                    //测试用例有它是368ms,没有是2944ms,差距很大
				    if (words[m][0] == s[k] && s.substr(k, len) == words[m]){
					    std::swap(words[m], words[--n]);
					    k += len; m = -1;
				    }
				    ++m;
				    if (0 == n) result.push_back(i);
			    }
		    }
        }
		return result;
    }
};

       这种方法思路和上面是一样的,只是在words里查找的时候借助了STL里的unordered_map容器,萌新也是做这道题刚知道这个神器,内部实现哈希散列,提供更快地查找功能,实测下来在对于这道题一般般吧,比上面的方法快一倍而已(176ms)。unordered_map的元素必须是成对的,一个是不允许重复也不能修改的key值,这里是words的各个字符串,另一个就随意了没什么限制,这里用来记录数量。先把words全部插进unordered_map B1里,在取s的len长度子串查找时,再构造一个unordered_map B2,先在B1里查找有木有,如果没有就break,有的话记录下数量,然后把子串插入B2,数量++,再看B2中此串的数量如果没超过B1说明可以,直到取的子串数量和words里一样多,说明完全匹配,记录下坐标加到结果里。具体细节参看完整代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (s.empty() || words.empty()) return result;
        unordered_map<string,int> B1;
        for (string str : words)    B1[str]++;    //构造表1
        int num = words.size();int len = words[0].size();
        int ed = s.size()-num*len;
        unordered_map<string,int>::iterator it;
        int j,n1,n2;
        for (int i = 0;i <= ed;i++){
            unordered_map<string,int> B2;    //构造表2
            int temp = i+num*len;
            for (j = i;j < temp;j+=len){
                string str = s.substr(j,len);
                if ((it = B1.find(str)) == B1.end())  break;    //如果此串在表1里没有则退出
                n2 = ++B2[str];
                n1 = it->second;
                if (n2 > n1)    break;    //如果数量比表1的多也退出
            }
            if (j == temp)   result.push_back(i);    //不多不少全部都有则成功
        }
		return result;
    }
};

二、不成功也要比到s结尾只需len趟就搞定绝对不重复无用比较法

       上述两种方法存在一个共同的问题,比如words字符串长度都是4,在s中从起点0开始比较,发现0-3符合,继续4-7也符合,但是8-11不符合,于是退出从起点1再依次比较,等到起点为4的时候又要重复比较一边4-7和8-11。于是就有了这种办法,可以避免重复比较。如上面的例子,当起点为1,2,或3时并不会去重复比较4-7和8-11,只有起点间隔是4的整数倍的时候才会重复。

       可以分为len趟(words中字符串的长度),这样每一趟之间都不会有重复。在具体的其中一趟,无论比较是否成功都会从s的头比到尾。依然借助unordered_map,用words构造一个B1,每一趟都构造一个和B1相同的B2,并用cnt初始化为words字符串的数量。如果在s中取的子串在B2中有且数量大于0,则数量减一,表示已经匹配了一个,cnt也减1(由于unordered_map的特性,通过B1[]访问的时候,如果要查找的字符串不在表里,就会插进去),然后再取words所有字符串总长之前的一段子串,因为这一段肯定已经超过可能匹配的范围,把它的数量加回来,如果+1后大于0说明是words里的串,cnt++(因为words里的串初始值都大于0,即使全部拿去匹配也只会是0,而不在words里的串都是-1)。如果cnt==0,说明连续匹配上了words中的全部串(如果不连续,因为上一句的cnt++,cnt不会等于0,只有连续匹配才能不断cnt--),把坐标加入result即可。测试用例耗时92ms,比前一种方法又快了大约一倍。具体细节参看完整代码:

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> result;
        if (s.empty() || words.empty()) return result;
        int n = s.size(), len = words[0].size(), total = words.size(), cnt = total;
        unordered_map<string, int> B1;
        for (string str : words) B1[str]++;
        for (int i = 0; i < len; i++) {    //分为len趟比较,避免重复
            unordered_map<string, int> B2 = B1;
            cnt = total;
            for (int j = i; j + len <= n; j += len) {
                string str = s.substr(j, len);
                if (B2[str]-- > 0) cnt--;    //如果str在words里面
                if (j - total*len >= 0) {                   
                    string out = s.substr(j - total*len, len);
                    if (++B2[out] > 0) cnt++;    //如果超出匹配范围再把数量加回来
                }
                if (cnt == 0) result.push_back(j - (total-1)*len);   正好连续cnt个都匹配则成功              
            }
        }
        return result;
    }
};

初次接触unordered_map和map,感觉特别强大。最后欢迎大家留言讨论,如有错误或改进还请不吝赐教。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值