串联所有单词的子串

1. 问题描述:

给定一个字符串 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”]
输出:[]

2. 问题分析:

该算法的难点是不需要考虑 words 中单词串联的顺序。而利用 HashMap 可以解决这个问题。

方法一:
我们可以借助两个 HashMap 来实现。Map1 用来存储 words 中的单词及其出现次数(key 表示单词,value 表示出现次数)。Map2用来存储子串中出现的单词,若子串中的单词在 words 中,将该单词存入Map2,比较Map2中单词出现的次数是否大于Map1,若大于,就代表该子串不是我们要找的,接着判断下一个子串;若不大于,继续判断下一个单词。子串扫描结束,如果子串的全部单词都符合,那么该子串就是我们找的其中一个。

方法二:
方法一每次只能移动一个字符,时间效率较低。我们可以一次移动一个单词。这样只需要进行 words[0].length() 次大循环。

循环时,主要有以下三种情况:

  1. 情况一:当子串完全匹配,需要移动到下一个子串的时候。当此情况发生时,我们并不需要对下一个子串重新进行判断,因为判断上一个子串时,我们已经将下一个子串的前 n - 1 个进行了判断(n 表示 words 中单词的个数),因此,只需要判断下一个子串的最后一个单词是否能和前面的单词组成一个符合要求的子串。
  2. 情况二:当判断过程中,出现不符合的单词。此情况发生时,我们不需要判断包含错误单词的子串,因此我们直接从错误单词的下一个单词开始的子串进行判断。
  3. 情况三:判断过程中,出现的是符合的单词,但是次数超了。此情况发生时,我们需要将超过次数的单词“删掉”,需要注意的是,我们不能直接删掉,而是从子串开始的位置往后依次删掉单词,直到该单词的次数不大于 Map1 中的次数。

3. 代码实现:

方法一:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> result = new ArrayList<>();
        if(words.length == 0)   return result;

        int wordLeng = words[0].length();
        int wordCount = words.length;
        Map<String, Integer> wordMap = new HashMap<>();

        // 将words中的字符串存放在HashMap中,words[i]表示key,出现的次数表示value
        for(String temp : words) {
            int value = wordMap.getOrDefault(temp, 0);
            wordMap.put(temp, value+1);
        }

        for(int i = 0; i <= s.length()-wordCount*wordLeng; i++) {
            int num = 0;
            Map<String, Integer> hasMap = new HashMap<>();
            while(num < wordCount) {
                String t = s.substring(i+num*wordLeng,i+(num+1)*wordLeng);
                if(wordMap.containsKey(t)) {
                    int value = hasMap.getOrDefault(t, 0);
                    hasMap.put(t, value+1);
                    if(hasMap.get(t) > wordMap.get(t)) {
                        break;
                    }
                } else {
                    break;
                }
                num++;
            }
            
            if(num == wordCount) {
                result.add(i);
            }
        }
        return result;
    }
}

方法二:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<Integer>();
        int wordNum = words.length;
        if (wordNum == 0) {
            return res;
        }
        int wordLen = words[0].length();
        HashMap<String, Integer> allWords = new HashMap<String, Integer>();
        for (String w : words) {
            int value = allWords.getOrDefault(w, 0);
            allWords.put(w, value + 1);
        }
        //将所有移动分成 wordLen 类情况
        for (int j = 0; j < wordLen; j++) {
            HashMap<String, Integer> hasWords = new HashMap<String, Integer>();
            int num = 0; //记录当前 HashMap2(这里的 hasWords 变量)中有多少个单词
            //每次移动一个单词长度
            for (int i = j; i < s.length() - wordNum * wordLen + 1; i = i + wordLen) {
                while (num < wordNum) {
                    String word = s.substring(i + num * wordLen, i + (num + 1) * wordLen);
                    if (allWords.containsKey(word)) {
                        int value = hasWords.getOrDefault(word, 0);
                        hasWords.put(word, value + 1);
                        //出现情况三,遇到了符合的单词,但是次数超了
                        if (hasWords.get(word) > allWords.get(word)) {
                            int removeNum = 0;
                            //一直移除单词,直到次数符合了
                            while (hasWords.get(word) > allWords.get(word)) {
                                String firstWord = s.substring(i + removeNum * wordLen, i + (removeNum + 1) * wordLen);
                                int v = hasWords.get(firstWord);
                                hasWords.put(firstWord, v - 1);
                                removeNum++;
                            }
                            num = num - removeNum + 1; //加 1 是因为我们把当前单词加入到了 HashMap 2 中
                            i = i + (removeNum - 1) * wordLen; //这里依旧是考虑到了最外层的 for 循环,看情况二的解释
                            break;
                        }
                    //出现情况二,遇到了不匹配的单词,直接将 i 移动到该单词的后边(但其实这里
                    //只是移动到了出现问题单词的地方,因为最外层有 for 循环, i 还会移动一个单词
                    //然后刚好就移动到了单词后边)
                    } else {
                        hasWords.clear();
                        i = i + num * wordLen;
                        num = 0;
                        break;
                    }
                    num++;
                }
                if (num == wordNum) {
                    res.add(i);
					// 情况一
                    String firstWord = s.substring(i, i + wordLen);
                    int v = hasWords.get(firstWord);
                    hasWords.put(firstWord, v - 1);
                    num = num - 1;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值