串联全部单词的子串

原题

  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.
  For example, given:
  s: "barfoothefoobarman"
  words: ["foo", "bar"]
  You should return the indices: [0,9].
  (order does not matter).java

题目大意

  给定一个字符串s和一个字符串数组words,wrods中的字符串长度都相等,找出s中全部的子串刚好包含words中全部字符各一次,返回子串的起始位置。算法

解题思路

  把words转化为一个HashMap数组

代码实现

算法实现类spa

import java.util.*;

public class Solution {

    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> list = new ArrayList<Integer>();
        if (words.length == 0) return list;
        int wLen = words[0].length();
        int len = s.length();
        if (len < wLen * words.length) return list;
        Map<String, Integer> mapW = new HashMap<String, Integer>();
        for (String word : words)
            mapW.put(word, mapW.containsKey(word) ? mapW.get(word) + 1 : 1);
        for (int start = 0; start < wLen; start++) {
            int pos = start;
            int tStart = -1;
            Map<String, Integer> mapT = new HashMap<String, Integer>(mapW);
            while (pos + wLen <= len) {
                String cand = s.substring(pos, pos + wLen);
                if (!mapW.containsKey(cand)) {
                    if (tStart != -1) mapT = new HashMap<String, Integer>(mapW);
                    tStart = -1;
                } else if (mapT.containsKey(cand)) {
                    tStart = tStart == -1 ? pos : tStart;
                    if (mapT.get(cand) == 1) mapT.remove(cand);
                    else mapT.put(cand, mapT.get(cand) - 1);
                    if (mapT.isEmpty()) list.add(tStart);
                } else {
                    while (tStart < pos) {
                        String rCand = s.substring(tStart, tStart + wLen);
                        if (cand.equals(rCand)) {
                            tStart += wLen;
                            if (mapT.isEmpty()) list.add(tStart);
                            break;
                        }
                        tStart += wLen;
                        mapT.put(rCand, mapT.containsKey(rCand) ? mapT.get(rCand) + 1 : 1);
                    }
                }
                pos += wLen;
            }
        }
        return list;
    }
}

 

这道题让咱们求串联全部单词的子串,就是说给定一个长字符串,再给定几个长度相同的单词,让咱们找出串联给定全部单词的子串的起始位置,仍是蛮有难度的一道题。这道题咱们须要用到两个哈希表,第一个哈希表先把全部的单词存进去,而后从开头开始一个个遍历,中止条件为当剩余字符个数小于单词集里全部字符的长度。这时候咱们须要定义第二个哈希表,而后每次找出给定单词长度的子串,看其是否在第一个哈希表里,若是没有,则break,若是有,则加入第二个哈希表,但相同的词只能出现一次,若是多了,也break。若是正好匹配完给定单词集里全部的单词,则把i存入结果中,具体参见代码以下:.net

 

解法一:设计

复制代码

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;
        if (s.empty() || words.empty()) return res;
        int n = words.size(), m = words[0].size();
        unordered_map<string, int> m1;
        for (auto &a : words) ++m1[a];
        for (int i = 0; i <= (int)s.size() - n * m; ++i) {
            unordered_map<string, int> m2;
            int j = 0; 
            for (j = 0; j < n; ++j) {
                string t = s.substr(i + j * m, m);
                if (m1.find(t) == m1.end()) break;
                ++m2[t];
                if (m2[t] > m1[t]) break;
            }
            if (j == n) res.push_back(i);
        }
        return res;
    }
};

复制代码

 

这道题还有一种O(n)时间复杂度的解法,设计思路很是巧妙,可是感受很难想出来,博主目测还未到达这种水平。这种方法再也不是一个字符一个字符的遍历,而是一个词一个词的遍历,好比根据题目中的例子,字符串s的长度n为18,words数组中有两个单词(cnt=2),每一个单词的长度len均为3,那么遍历的顺序为0,3,6,8,12,15,而后偏移一个字符1,4,7,9,13,16,而后再偏移一个字符2,5,8,10,14,17,这样就能够把全部状况都遍历到,咱们仍是先用一个哈希表m1来记录words里的全部词,而后咱们从0开始遍历,用left来记录左边界的位置,count表示当前已经匹配的单词的个数。而后咱们一个单词一个单词的遍历,若是当前遍历的到的单词t在m1中存在,那么咱们将其加入另外一个哈希表m2中,若是在m2中个数小于等于m1中的个数,那么咱们count自增1,若是大于了,那么须要作一些处理,好比下面这种状况, s = barfoofoo, words = {bar, foo, abc}, 咱们给words中新加了一个abc,目的是为了遍历到barfoo不会中止,那么当遍历到第二foo的时候, m2[foo]=2, 而此时m1[foo]=1,这是后已经不连续了,因此咱们要移动左边界left的位置,咱们先把第一个词t1=bar取出来,而后将m2[t1]自减1,若是此时m2[t1]<m1[t1]了,说明一个匹配没了,那么对应的count也要自减1,而后左边界加上个len,这样就能够了。若是某个时刻count和cnt相等了,说明咱们成功匹配了一个位置,那么将当前左边界left存入结果res中,此时去掉最左边的一个词,同时count自减1,左边界右移len,继续匹配。若是咱们匹配到一个不在m1中的词,那么说明跟前面已经断开了,咱们重置m2,count为0,左边界left移到j+len,参见代码以下:code

 

解法二:rem

复制代码

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        if (s.empty() || words.empty()) return {};
        vector<int> res;
        int n = s.size(), cnt = words.size(), len = words[0].size();
        unordered_map<string, int> m1;
        for (string w : words) ++m1[w];
        for (int i = 0; i < len; ++i) {
            int left = i, count = 0;
            unordered_map<string, int> m2;
            for (int j = i; j <= n - len; j += len) {
                string t = s.substr(j, len);
                if (m1.count(t)) {
                    ++m2[t];
                    if (m2[t] <= m1[t]) {
                        ++count;
                    } else {
                        while (m2[t] > m1[t]) {
                            string t1 = s.substr(left, len);
                            --m2[t1];
                            if (m2[t1] < m1[t1]) --count;
                            left += len;
                        }
                    }
                    if (count == cnt) {
                        res.push_back(left);
                        --m2[s.substr(left, len)];
                        --count;
                        left += len;
                    }
                } else {
                    m2.clear();
                    count = 0;
                    left = j + len;
                }
            }
        }
        return res;
    }
};
相关文章
相关标签/搜索