Leetcode1-100: 30. Substring with Concatenation of All Words

Leetcode1-100: 30. Substring with Concatenation of All Words

问题描述

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 “barfoo” 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: []

题目要求: 输入一个字符串和一个单词数组(数组全是一样长度的字符串),要求找出字符串中所有的位置,满足从当前位置开始的字符串中每一个单词数组中的单词都出现一次(单词数组中内容可以重复)

解题思路

  1. Brute force
    这个想法比较明显,迭代字符串中所有位置然后查看每个位置是否满足条件。比较巧妙地方法是使用HashMap,利用HashMap查询操作只花费常数时间的效率来把每次查询的时间缩短到O(n)。通过两个HashMap来实现,第一个用来存储单词数组中每个单词出现的次数。每次判断是生成一个新的HashMap来判断能否找到相同的元素以及单词出现次数是否超过限制。
  2. Sliding window.
    Sliding Window是一个很有用的算法(更像是一种思想),因为这题本来我的想法是用循环队列来做,来确保过程中能够省略很多不必要的步骤,另外循环队列能够控制顺序,因为FIFO的结构使得先出现的单词能够先出栈。因为这么想实际上是以单词为单位的,那么每次迭代增加的量就是一个单词的长度,那么就得设置wordLength次循环来保证0-wordLength开头的单词都被遍历到。
    但是后面出现了很多实现上的问题,于是参考了这里的思想,换用HashMap来做了,不过具体的实现也用了sliding window的思想,觉得这样更加直观一些。
    循环中用start和currentWord两个变量来表示window的范围,start表示window开始的位置,currentWord表示window当前所在的位置,总体上,每次遇到一个单词可以分为一下四种情况,用这个例子来辅助说明:
	Input:
  		s = "barfoofoothefoobarmanfoo",
  		words = ["foo","bar""man"]
	Output: [0,9]
  1. 单词在words数组中,然后这时window里面还没有够所有单词都出现过的长度,即第一个单词bar读取后,符合条件但是这时候window里面只有一个单词。这种情况也最好处理,只需要继续循环即可。

  2. 单词在words数组中,但是单词加入HashMap后在window中的数量太多超过上限。例如在第三个单词读取到foo后,start此时还是0,但是window里面已经有了两个foo了,但是由words数组中只有一个foo所以不符合要求,这时候为了节省时间就需要把start的位置换到3(以确保window中只有一个foo来满足限制),这时候还需要把window已经读取到的内容给去掉一部分(即foo前面的内容,假设此时window中已有bar, foo, foo, 则需要先去掉bar和foo),这里可以用一个while循环来实现,因为已经知道了跳出循环的条件(即单词读到第二个foo即可停止出栈)

  3. 单词没有出现在words数组过,例如例子中读取到第四个单词the的时候,发现hash里面没有the的记录,这时候可以知道the前面的内容都不会给result集添加新的内容了,所以直接设置start的值是5即可,即令start指向the后面的foo,并且需要清空window中的所有内容。

  4. 当前的window已经足够数量,能够满足条件,即words数组中所有单词均只出现过一次。由于每次添加完单词后都会更新start和currentWord的值,所以当window中的单词数量足够words.length的时候就是符合要求的window,这时候把start开始的位置添加进结果集。然后考虑如何优化这种情况,当window中的所有情况都满足条件的时候,只需要把start的位置往后一个,然后window中start原来指向的单词出栈即可,这样直接会检查后一个单词,例如例子中第五个单词开始的window,currentWord指向第七个单词时window满足条件,这时候让start指向第六个单词bar,window中去掉bar,剩下的元素bar和man可以继续使用即可,这样只需要检查bar, man和后面的foo能否完成一个window就行。

这时候window中添加完单词之后的情况只需要考虑window里面的内容是否满足即可条件。
话不多说,上代码。

代码实现

implement 1 brute force
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList();
        if(words.length == 0) return res;
        int len = words.length, wordLen = words[0].length();
        HashMap<String, Integer> hash = new HashMap<>();
        for(int i = 0; i < len; i++) { //新建HashMap用来存储每个单词出现的次数
            if(hash.containsKey(words[i]))
                hash.put(words[i], hash.get(words[i])+1);
            else 
                hash.put(words[i], 1);
        }
        for(int i = 0; i <= s.length()- len * wordLen; i++) {
            HashMap<String, Integer> test = new HashMap<>();
            int j = 0;
            for(; j < len; j++) {
                String word = s.substring(i+j*wordLen, i+j*wordLen+wordLen);
                if(hash.containsKey(word)) {
                    int t = test.getOrDefault(word, 0);
                    test.put(word, t+1);
                    if(t+1 > hash.get(word)) 
                        break;
                } else 
                    break;
            }
            if(j == len) //如果j=len说明这里的字符串可以满足要求
                res.add(i);
        }
        return res;
    }

在这里插入图片描述
复杂度分析:

时间复杂度:O(m*n) (m,n分别指s的长度和words数组的总长度)
空间复杂度:O(n) (使用HashMap来存储单词内容和用来检验)
implement 2 sliding window
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList();
        if(words.length == 0) return res;
        int len = words.length, wordLen = words[0].length(), wordNum = s.length() / wordLen;
        HashMap<String, Integer> hash = new HashMap<>();
        for(int i = 0; i < len; i++) {
            if(hash.containsKey(words[i]))
                hash.put(words[i], hash.get(words[i])+1);
            else 
                hash.put(words[i], 1);
        }
        for(int i = 0; i < wordLen; i++) {
            HashMap<String, Integer> window = new HashMap<>();
            int currentWord = 0, start = 0;
            while(i+currentWord*wordLen+wordLen-1 < s.length()) {
                String word = s.substring(i+currentWord*wordLen, i+currentWord*wordLen+wordLen);  
                currentWord++;
                if(hash.containsKey(word)) {
                    int tmp = window.getOrDefault(word, 0);
                    window.put(word, tmp + 1);
                    if(tmp+1 > hash.get(word)) { //当前单词在window中数量超过限制
                        String wordd = s.substring(i+start*wordLen, i+start*wordLen+wordLen);
                        while(!wordd.equals(word)) {
                            start++;
                            int temp = window.get(wordd);
                            window.put(wordd, temp - 1);
                            wordd = s.substring(i+start*wordLen, i+start*wordLen+wordLen);
                        }
                        int temp = window.get(word);
                        window.put(word, temp - 1);
                        start++;
                        continue;
                    }
                } else { //当前window中的单词不存在单词数组中,把window的开头跳到当前单词后
                    start = currentWord;
                    currentWord = start;
                    window.clear();
                    continue;
                } 
                if(currentWord - start == len) {
                    res.add(i+start*wordLen);
                    int temp = window.get(s.substring(i+start*wordLen, i+start*wordLen+wordLen));
                    window.put(s.substring(i+start*wordLen, i+start*wordLen+wordLen), temp - 1);
                    start++;
                }     
            }
        }
        return res;
    }

在这里插入图片描述
复杂度分析:

时间复杂度:O(n) 每个单词最多只使用两次,但是实际操作中无法达到线性效率,因为substring和构建HashMap的操作不是常数时间能够完成的
空间复杂度:O(n)

分析

这题比较复杂,实现2的code一开始写的很乱也写了很久,后来按照流程跟着例子走了一遍之后清晰了很多

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值