leetcode-pp / 91alg-5-daily-check

91 天学算法第五期打卡
55 stars 14 forks source link

【Day 23 】2021-10-02 - 30. 串联所有单词的子串 #39

Open azl397985856 opened 2 years ago

azl397985856 commented 2 years ago

30. 串联所有单词的子串

入选理由

暂无

题目地址

https://leetcode-cn.com/problems/substring-with-concatenation-of-all-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"] 输出:[]

yanglr commented 2 years ago

思路

题意: words数组中的单词以任意顺序进行拼接, 需要找出每一种可能的拼接结果串在原字符串中是否能找到, 如果能找到, 将其index加入到结果数组中, 否则返回空数组。且子串要与 words 数组中的单词完全匹配,中间不能有其他字符

哈希表

使用两个哈希表: 一个循环外的目标哈希表needWordDict和一个循环内部的哈希表 hasWordsDict。

每次使用一个word长度的窗口去扫描~

LeetCode 76 用的方法有点像。

代码

实现语言: C++

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> res;
        int wordsCount = words.size();
        if (wordsCount == 0) return {};
        int wordLen = words.front().size();
        // 目标哈希表 needWordDict, map: string -> count
        unordered_map<string, int> needDict;
        for (auto word : words)
        {
            if (needDict.find(word) != needDict.end())
                needDict[word]++;
            else needDict[word] = 0;            
        }
        for (int i = 0; i < s.size() - wordLen*wordsCount + 1; i++)
        {
            int count = 0;
            // 构建已有串的哈希表 hasWordsDict
            unordered_map<string, int> hasWordsDict;          
            while (count < wordsCount)
            {
                string curWord = s.substr(i + count * wordLen, wordLen);
                if (needDict.find(curWord) != needDict.end())
                {
                    if (hasWordsDict.find(curWord) != hasWordsDict.end())
                        hasWordsDict[curWord]++;
                    else hasWordsDict[curWord] = 0;
                }
                else break;

                if (hasWordsDict[curWord] > needDict[curWord]) break;
                count++;                                        
            }
            if (count == wordsCount) res.push_back(i);
        }

        return res;
    }
};

复杂度分析

yachtcoder commented 2 years ago

Not optimal but anyways.

To find a subarray that contains all the words but of an arbitrary order, we can compare their counters. This can be done in brute force, but a O(n^2) solution will not work given n = 10^4.

Now the problem becomes: given an index I, how to find the counter of the next k words efficiently. And this can be done by preprocessing the entire string and store the next k words starting from I in a hashtable. We build the table recursively with memoization, so the preprocessing time is O(n). Space is O(n^2) because we are basically storing a substring for each index.

Once we have the hash table ready, we can check every index and compare the counters of the next k words. The cost for each comparison is O(k) and in total we have O(nk).

Time: O(nk) where n is the length of string and k is the number of words Spaec: O(n^2)

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        counter = Counter(words)
        M, W, K = len(s), len(words[0]), len(words)
        @lru_cache(None)
        def getWords(idx):
            if idx >= M: return []
            w = s[idx: idx+W]
            return [w] + getWords(idx+W)
        wsm, ret = {}, []
        for i in range(M):
            wsm[i] = getWords(i)
        for i in range(M):
            ws = wsm[i]
            c = Counter(ws[:K])
            if c == counter:
                ret.append(i)
        return ret
yingliucreates commented 2 years ago

link:

https://leetcode.com/problems/substring-with-concatenation-of-all-words/

代码 Javascript

const findSubstring = (s, words) => {
  if (!words || words.length === 0) return [];

  const m = words.length,
    n = words[0].length,
    len = m * n,
    result = [];

  // Build the word-count hash map
  const map = {};
  for (word of words) map[word] = ~~map[word] + 1;

  // Try every possible start position i
  for (let i = 0; i < s.length - len + 1; i++) {
    // Make a copy of the hash map
    const temp = Object.assign({}, map);

    for (let j = i; j < i + len; j += n) {
      const str = s.substr(j, n);
      // Cannot find the word in hash map (words list), try another position
      if (!(str in temp)) break;
      // All the same word str are found, remove it from the hash map
      if (--temp[str] === 0) delete temp[str];
    }

    // We have gone through the whole s and used all our words in the list
    if (Object.keys(temp).length === 0) result.push(i);
  }

  return result;
};

复杂度分析

time: O(mn)? space: O(n)?

famine330 commented 2 years ago

思路

滑动窗口+哈希表

代码

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        WordsCounter = collections.Counter(words)
        wordNum = len(words)
        wordLen = len(words[0])
        res = []
        for i in range(len(s) - wordNum * wordLen + 1):
            WordCounter = collections.defaultdict(int)
            index = i
            while index < i + wordNum * wordLen:
                curWord = s[index: index + wordLen]
                if curWord not in WordsCounter or WordCounter[curWord] == WordsCounter[curWord]:
                    break
                WordCounter[curWord] += 1
                index += wordLen
            if index == i + wordNum * wordLen:
                res.append(i)
        return res

复杂度分析

时间复杂度:O(N * M), N为字符串长度,M为单词个数 空间复杂度:O(N)

laofuWF commented 2 years ago
# 2 maps
# build counter of word in words
# iterate through s and check every possible window of length total words length
# break if a new word appears in the substring or counter exceeds

# time: O(len(s) * words_length * num_of words)
# space: O(number of unique words)

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        words_length = len(words) * len(words[0])
        # not enough chars in s
        if words_length > len(s):
            return []

        counter = {}
        res = []
        # build counter
        for word in words:
            counter[word] = counter.get(word, 0) + 1

        for i in range(len(s) - words_length + 1):
            seen = {}

            # increment by single word length
            for j in range(i, i + words_length, len(words[0])):
                next_word = s[j : j + len(words[0])]

                if next_word in counter:
                    seen[next_word] = seen.get(next_word, 0) + 1
                    if seen[next_word] > counter[next_word]:
                        break
                else:
                    break

            if seen == counter:
                res.append(i) 

        return res
lihanchenyuhua commented 2 years ago

class Solution: def findSubstring(self, s: str, words: List[str]) -> List[int]: from collections import Counter if not s or not words:return [] all_len = sum(map(len, words)) n = len(s) words = Counter(words) res = [] for i in range(0, n - all_len + 1): tmp = s[i:i+all_len] flag = True for key in words: if words[key] != tmp.count(key): flag = False break if flag:res.append(i) return res

JiangyanLiNEU commented 2 years ago

Yesterday's interview motivated me to practice JavaScript more.... Will do leetcode in python and JS

class Solution(object):
    def findSubstring(self, s, words):
        num_words = len(words)
        each_length = len(words[0])
        length = num_words * each_length
        result = []
        def check(segments):
            dic = Counter(words)
            index = 0
            while index<len(segments):
                start = index
                end = start+each_length
                if segments[start:end] in dic and dic[segments[start:end]]>=1:
                    dic[segments[start:end]]-=1
                    index=end
                else:
                    return False
            return True

        for i in range(len(s)):
            start = i
            end = start + length
            if end<=len(s):
                segments = s[start:end]
                if check(segments):
                    result.append(start)
        return result
wangzehan123 commented 2 years ago

public class Solution {
    public ArrayList<Integer> findSubstring(String S, String[] L) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        HashMap<String, Integer> toFind = new HashMap<String, Integer>();
        HashMap<String, Integer> found = new HashMap<String, Integer>();
        int m = L.length, n = L[0].length();
        for (int i = 0; i < m; i ++){
            if (!toFind.containsKey(L[i])){
                toFind.put(L[i], 1);
            }
            else{
                toFind.put(L[i], toFind.get(L[i]) + 1);
            }
        }
        for (int i = 0; i <= S.length() - n * m; i ++){
            found.clear();
            int j;
            for (j = 0; j < m; j ++){
                int k = i + j * n;
                String stub = S.substring(k, k + n);
                if (!toFind.containsKey(stub)) break;
                if(!found.containsKey(stub)){
                    found.put(stub, 1);
                }
                else{
                    found.put(stub, found.get(stub) + 1);
                }
                if (found.get(stub) > toFind.get(stub)) break;
            }
            if (j == m) result.add(i);
        }
        return result;
    }
}

复杂度分析

令 n 为数组长度。

BpointA commented 2 years ago

思路

哈希表+滑动窗口。用一个哈希表记录各字符出现的位置,用另一个哈希表记录每个单词的个数。 分别从第0,第1... 第l-1个字符开始初始化滑动窗口。移动滑动窗口,得到新单词。 1.若其不在哈希表中,将left移到right处,记录位置的哈希表重置。 2.若其在哈希表中且造成该单词出现次数大于原列表,则把left移动到该单词现在在窗口中第一次出现位置的右边,更新记录位置的哈希表 3.否则,直接更新哈希表 当移动完指针后,若窗口长度与应有长度相等,则得到答案。

Python3代码

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        from collections import defaultdict
        d=defaultdict(list)
        left=0
        right=0
        m=len(s)
        n=len(words)
        l=len(words[0])
        cnt=defaultdict(int)
        res=[]
        for i in words:
            d[i]=[]
            cnt[i]+=1
        for right in range(l):
            left=right
            for i in words:
                d[i]=[]
            while right<m:         
                right+=l
                wd=s[right-l:right]

                if wd not in d:
                    left=right
                    for i in d:
                        d[i]=[]                

                elif len(d[wd])>=cnt[wd]:
                    left=d[wd][0]+l
                    d[wd].pop(0)
                    d[wd].append(right-l)

                    for i in d:
                        while len(d[i])>0 and d[i][0]<left:
                            d[i].pop(0)

                else:
                    d[wd].append(right-l) 

                if right-left==n*l:

                    res.append(left)

                    left+=l
        return res

复杂度

时间复杂度:O(m),m为s长度,l为words长度。一共进行了l次长度为m/l的滑动数组

空间复杂度:O(m),存储了每个单词的位置和次数

florenzliu commented 2 years ago

Explanation

Python

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        d = defaultdict(int)
        n = len(words)
        wordLength = len(words[0])
        for w in words:
            d[w] += 1

        result = []
        for i in range(len(s)-n*wordLength+1): # range: +1
            currD = d.copy()

            flag = True
            for j in range(i, i+n*wordLength, wordLength):
                if s[j:j+wordLength] not in currD:
                    flag = False
                    break
                else:
                    if currD[s[j:j+wordLength]] == 0:
                        flag = False
                        break
                    else:
                        currD[s[j:j+wordLength]] -= 1
            if flag:
                result.append(i)
        return result 

Complexity:

falconruo commented 2 years ago

思路:

n = 字符串s长度 m = 字符串数组words中的字符串个数 k = 字符串数组words中的单个字符串长度

增加判断滑动窗口中的子串是否在给定的字符串数组中(!cnt.count(ss))

复杂度分析: 时间复杂度: O(n m), n为字符串s的长度, m为字符串数组words的size(words.size(),字符串数) 空间复杂度: O(m k), 哈希表空间,m为字符串数组中不同的字符串个数,k为单个字符串的长度

代码(C++):

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        int n = s.length();
        int m = words.size();
        int k = words[0].length();

        if (n < m * k) return {};

        map<string, int> cnt;

        vector<int> res;
        for (auto &w : words)
            cnt[w]++;

        for (int i = 0; i < n - m * k + 1; ++i) {
            map<string, int> tmp;
            for (int j = 0; j < m; ++j) {
                string ss = s.substr(i + j * k, k);
                if (!cnt.count(ss)) break;
                tmp[ss]++;
            }

            if (tmp == cnt)
                res.push_back(i);
        }

        return res;
    }
};
akxuan commented 2 years ago

维护一个固定长度的字符串 slicing window O(N)

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        from collections import Counter
        if not s or not words:return []
        one_word = len(words[0])
        all_len = len(words) * one_word
        n = len(s)
        words = Counter(words)
        res = []
        for i in range(0, n - all_len + 1):
            tmp = s[i:i+all_len]
            c_tmp = []
            for j in range(0, all_len, one_word):
                c_tmp.append(tmp[j:j+one_word])
            if Counter(c_tmp) == words:
                res.append(i)
        return res
heyqz commented 2 years ago

solution

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        dic = {}
        res = []
        word_len = len(words[0])
        words_len = len(words) * word_len

        for word in words:
            dic[word] = dic.get(word, 0) + 1

        for i in range(len(s) - words_len + 1):

            seen = defaultdict(int) # reset seen for every i

            for j in range(i, i + words_len, word_len):
                word = s[j : j + word_len] 
                if word in dic:
                    seen[word] += 1
                    if seen[word] > dic[word]:
                        break

            if seen == dic:
                res.append(i)

        return res
xj-yan commented 2 years ago

Sliding Window

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        Map<String, Integer> map = new HashMap<>();
        for (String word : words){
            map.put(word, map.getOrDefault(word, 0) + 1);
        }
        List<Integer> result = new ArrayList<>();

        int wordPerLen = words[0].length(), offset = 0, numOfWords = words.length;
        while (offset < s.length() - wordPerLen * numOfWords + 1){
            Map<String, Integer> hasMatched = new HashMap<>();
            int numOfMatched = 0;
            while (numOfMatched < numOfWords){
                String str = s.substring(offset + numOfMatched * wordPerLen, offset + (numOfMatched + 1) * wordPerLen);
                if (!map.containsKey(str)) break;
                hasMatched.put(str, hasMatched.getOrDefault(str, 0) + 1);
                if (hasMatched.get(str) > map.get(str)) break;
                numOfMatched++;
            }
            if (numOfMatched == numOfWords) result.add(offset);
            offset++;
        }
        return result;
    }
}

Time Complexity: O(n * L), L is the length of concatenated word, Space Complexity: O(n)

nonevsnull commented 2 years ago

思路

AC

代码

//hashmap
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        HashMap<String, Integer> map = new HashMap<>();
        List<Integer> res = new ArrayList<>();
        int wordLength = words[0].length();
        for(String word: words){
            map.put(word, map.getOrDefault(word, 0)+1);     
        }

        HashMap<String, Integer> found = new HashMap<>();
        for(int i = 0;i <= s.length() - words.length * wordLength;i++){
            found.clear();
            int cur = i;
            int size = 0;
            for(int j = 0;j < words.length;j++){
                String str = s.substring(cur, cur + wordLength);
                if(map.containsKey(str)){
                    found.put(str, found.getOrDefault(str, 0)+1);
                    if(found.get(str) > map.get(str)) break;
                    size++;
                } else {
                    break;
                }
                cur = cur + wordLength;
            }
            if(size == words.length){
                res.add(i);
            }
        }
        return res;      
    }
}

//不可能的words排列组合,还是记录一下backtrack
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<String> res = backtrack(Arrays.asList(words), new ArrayList<>(), "", words.length*words[0].length());
        System.out.println(res);

        return new ArrayList<>();
    }

    public List<String> backtrack(List<String> words, List<String> res, String combination, int size){
        if(size == combination.length()) {
            res.add(combination);
            return res;
        }
        for(int i = 0;i < words.size();i++){
            if(words.get(i) == "") continue;
            String temp = combination;
            String word = words.get(i);
            combination += word;
            words.set(i, "");
            backtrack(words, res, combination, size);
            combination = temp;
            words.set(i, word);
        }
        return res;
    }
}

复杂度

time:

space:map所需的空间O(N)

MonkofEast commented 2 years ago

30. Substring with Concatenation of All Words

Click Me

Algo

  1. Corner: words is empty
  2. make words a hashmap, key->word, val->times
  3. window length is the total length of words
  4. check window one by one
  5. turn current window into a hashmap
  6. compare curr_hash & target_hash

Code

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        # corner
        if not words: return []

        # cal the length of the continuous sub-str        
        word_len = len(words[0])
        sub_len = len(words) * word_len

        # set words to a hash set, key->word, val->times
        target_hash = collections.defaultdict(int)
        for word in words: target_hash[word] += 1

        # arr to save idx
        res = []

        # fixed sliding window
        slow = 0
        fast = sub_len
        while fast != len(s) + 1:
            # get curr window
            curr = s[slow:fast]

            # transfer curr to a hashmap
            window_words = collections.defaultdict(int)
            cut = 0            
            while cut != sub_len:
                window_words[curr[cut:cut + word_len]] += 1
                cut += word_len

            # compare words and curr
            wrong = False
            for key in target_hash.keys():
                if target_hash[key] != window_words[key]: wrong = True
                if wrong: break
            if not wrong: res.append(slow)

            # update slow & fast
            slow += 1
            fast += 1

        return res

Comp

T: O(N)

S: O(1)

RocJeMaintiendrai commented 2 years ago

题目

https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/

思路

HashMap + 双指针,slidding window的思想。 map里存储单词和对应频率。i是str的开始idx,j每次移动一个单词的长度的距离,然后看str(i, j)是否存在于map中并且频率是否 >= 1,是的话就将map频率减1(copy of map),然后继续移动j指针。使用一个k来记录匹配到的word的数量,如果变为0时就记录下当前i的idx,然后iterate到s.length() - words.length * words[0].length()结束。

代码

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        if(s == null || words == null) return new ArrayList<>();
        List<Integer> res = new ArrayList<>();
        final int n = words.length;
        final int m = words[0].length();
        Map<String, Integer> map = new HashMap<>();
        for(String str : words) {
            map.put(str, map.getOrDefault(str, 0) + 1);
        }
        for(int i = 0; i <= s.length() - n * m; i++) {
            Map<String, Integer> copy = new HashMap<>(map);
            int k = n;
            int j = i;
            while(k > 0) {
                String str = s.substring(j, j + m);
                if(!copy.containsKey(str) || copy.get(str) < 1) {
                    break;
                }
                copy.put(str, copy.get(str) - 1);
                k--;
                j += m;
            }
            if(k == 0) res.add(i);
        }
        return res;
    }
}

复杂度分析

时间复杂度

O(n^2)

空间复杂度

O(n)

zol013 commented 2 years ago

思路:首先创建一个对于words的counter, 然后创建一个固定大小的sliding window, sliding window的长度为words里的所有单词能组成的子串的长度,然后用这个滑动窗口来遍历s,每次遍历找到可能的子串时再拆成单词表,用counter和words的counter比较

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        wordlen = len(words[0])
        substrlen = wordlen * len(words)
        count_1 = Counter(words)
        ans = []
        for i in range(0, len(s) - substrlen + 1):
            substr = s[i: i + substrlen]
            word_list = []
            for j in range(0, substrlen, wordlen):
                word_list.append(substr[j: j+wordlen])
            count_2 = Counter(word_list)
            if count_1 == count_2:
                ans.append(i)
        return ans

Time Complexity: O(len(s) len(words)len(words[0])) Space Complexity: O(len(words))

pan-qin commented 2 years ago

idea: scan the whole string for substring with length of unit word length * word number. Since each word has the same length, we can break the substring into parts with equal size. Use a hashmap to record each part and its frequency. Use another hashmap to record the same info of the original word array. Compare these two hashmap, if they are equal, save the index. Time: O(mnk) Space: O(m)
m is the # of words in the array, k is the unit length of word

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        int unitLen = words[0].length();
        int substringLen = unitLen * words.length;
        HashMap<String, Integer> wordMap = new HashMap<>();
        ArrayList<Integer> res = new ArrayList<>();
        for(String word: words) {
            wordMap.put(word,wordMap.getOrDefault(word,0)+1);
        }
        for(int i=0;i+substringLen<=s.length();i++) {
            String substring=s.substring(i,i+substringLen);
            HashMap<String, Integer> substrMap = new HashMap<>();
            for(int j=0;j<substring.length();j=j+unitLen) {
                String part = substring.substring(j,j+unitLen);
                substrMap.put(part,substrMap.getOrDefault(part,0)+1);
            }
            if(wordMap.equals(substrMap))
                res.add(i);
        }
        return res;
    }
}
mmboxmm commented 2 years ago

思路

写了一个有点复杂, 效率还可以

代码

fun findSubstring(s: String, wordsArray: Array<String>): List<Int> {
  val w = wordsArray[0].length
  if (s.length < w) return emptyList()

  val map = LinkedHashMap<Int, String>()
  val freq = wordsArray.asSequence().groupingBy { it }.eachCount()

  for (i in 0..s.length - w) {
    val sub = s.substring(i, i + w)
    if (freq.contains(sub)) map[i] = sub
  }

  val res = mutableListOf<Int>()
  map.keys.forEach key@{ start ->
    val f: MutableMap<String, Int> = freq.toMutableMap()
    var index = start
    repeat(wordsArray.size) {
      map[index]?.let { word ->
        f[word]?.let {
          f[word] = it - 1
          if (it == 1) f.remove(word)
        } ?: return@key
      } ?: return@key
      index += w
    }
    res.add(start)
  }
  return res
}

可能Java写的更简单些

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

        int total = words.length;
        int len = words[0].length();
        Map<String, Integer> ptn = new HashMap<>();
        for (String word : words) {
            ptn.compute(word, (k, v) -> v == null ? 1 : v + 1);
        }

        Map<String, Integer> found = new HashMap<>();

        for (int i = 0; i <= s.length() - total * len; i++) {
            found.clear();
            int cnt = 0;

            while (cnt < total) {
                String t = s.substring(i + cnt * len, i + (cnt + 1) * len);
                if (!ptn.containsKey(t)) break;

                found.compute(t, (k, v) -> v == null ? 1 : v + 1);
                if (found.get(t) > ptn.get(t)) break;
                cnt++;
            }

            if (cnt == total) res.add(i);
        }

        return res;
    }
}
skinnyh commented 2 years ago

Note

Solution

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        n, wl = len(words), len(words[0])
        w_dict, res = {}, []
        for w in words:
            w_dict[w] = w_dict.get(w, 0) + 1
        for i in range(0, len(s) - n * wl + 1):
            tmp_dict = w_dict.copy()
            match, j = 0, i
            while match < n:
                w = s[j : j + wl]
                if w not in tmp_dict or tmp_dict[w] < 1:
                    break
                match += 1
                tmp_dict[w] -= 1
                j += wl
            if match == n:
                res.append(i)
        return res

Time complexity: O(LMN) L=len(s), M=len(words), N=len(words[0])

Space complexity: O(M)

xieyj17 commented 2 years ago
from collections import Counter
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        word_dict = Counter(words)
        word_len = len(words[0])
        total_len = len(words) * word_len

        res = []

        for i in range(len(s) - total_len + 1):
            sub_string = s[i:i+total_len]
            t_dict = word_dict.copy()
            sub_list = [sub_string[j:j+word_len] for j in range(0, total_len, word_len)]
            sub_dict = Counter(sub_list)
            if t_dict == sub_dict:
                res.append(i)
        return res

Time: O(KMN), where M is the size of words and K is the length of each word

Space: O(N)

chakochako commented 2 years ago
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        allWords = collections.Counter(words)
        wordNum = len(words)
        wordLen = len(words[0])
        res = []
        for i in range(len(s) - wordNum * wordLen + 1):
            subWords = collections.defaultdict(int)
            index = i
            while index < i + wordNum * wordLen:
                curWord = s[index: index + wordLen]
                if curWord not in allWords or subWords[curWord] == allWords[curWord]:
                    break
                subWords[curWord] += 1
                index += wordLen
            if index == i + wordNum * wordLen:
                res.append(i)
        return res
AgathaWang commented 2 years ago

python

class Solution(object):
    def findSubstring(self, s, words):
        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        # method: 双指针和固定长度滑动窗口
        ans = []
        words.sort()
        len1word = len(words[0])
        lenslidewin = len1word * len(words)
        l,r = 0, lenslidewin-1
        while r <= len(s)-1:
            tempword = s[l:r+1]
            templst = []
            segpointer = l
            #分割字符串为list
            while segpointer <= r: 
                templst.append(s[segpointer:segpointer+len1word])
                segpointer += len1word
            templst.sort()
            if templst == words:
                ans.append(l)
            r += 1
            l += 1
        return ans

time complexity: O(n*len1word*wc);

n: length of s; len1word: length of one word in words; wc: word count, the length of words;

Menglin-l commented 2 years ago

思路:

滑窗+哈希表

(自己没想出来,参考了大佬的写法,学习之)


代码部分:

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        // 记录words中所有的单词到targetWords中 && 记录words中所有单词的总长度
        HashMap<String, Integer> targetWords = new HashMap<>();
        int sum = 0;
        for (String w: words) {
            targetWords.put(w, targetWords.getOrDefault(w, 0) + 1);
            sum += w.length();
        }

        // s中每个字母都作为窗口滑动的起点
        int left, right, wordLen, len, valid;
        List<Integer> res = new ArrayList<>();
        HashMap<String, Integer> visitedWords = new HashMap<>();
        HashSet<Integer> visitedLeft = new HashSet<>();
        wordLen = words[0].length();
        len = s.length();
        for (int i = 0; i + sum <= len; i ++) {

            valid = 0;
            right = left = i;
            visitedWords.clear();

            if (visitedLeft.contains(left)) continue;
            while (right + wordLen <= len){
                // 判断当前位置的下一个单词是否在words中
                String curStr = s.substring(right, right+wordLen);
                if (!targetWords.containsKey(curStr)) break;

                // 窗口扩大
                visitedWords.put(curStr, visitedWords.getOrDefault(curStr, 0) + 1);
                if (visitedWords.get(curStr).equals(targetWords.get(curStr))) valid ++;
                right += wordLen;

                // 窗口缩小
                while (visitedWords.get(curStr).compareTo(targetWords.get(curStr)) > 0) {
                    String leftStr = s.substring(left, left + wordLen);
                    if (visitedWords.get(leftStr).equals(targetWords.get(leftStr))) valid --;
                    if (visitedWords.get(leftStr) == 1) visitedWords.remove(leftStr);
                    else visitedWords.put(leftStr, visitedWords.get(leftStr) - 1);
                    left += wordLen;
                    visitedLeft.add(left);
                }

                // 记录left
                if (valid == targetWords.size()) res.add(left);
            }
        }

        return res;

    }
}

复杂度:

Time: O(M * N)

Space: O(N)

tongxw commented 2 years ago

思路

关键信息是要找的单词长度是相同的 固定长度滑动窗口 + 窗口中用哈希表计数,窗口每走一步,都要统计窗口中出现的单词个数是否和words完全一致。

代码

/**
 * @param {string} s
 * @param {string[]} words
 * @return {number[]}
 */
var findSubstring = function(s, words) {
  const wordCounters = new Map();
  for (const word of words) {
    const counter = wordCounters.get(word) || 0;
    wordCounters.set(word, counter + 1);
  }

  // sliding window with fixed length
  const ans = [];
  const windowLength = words.length * words[0].length;
  for (let i=0; i<=s.length - windowLength; i++) {
    const newCounters = new Map(wordCounters);
    let isMatch = true;
    for (let j=i; j<i+windowLength; j+=words[0].length) {
      const word = s.substring(j, j+words[0].length);
      const counter = newCounters.get(word) || 0;
      if (counter > 0) {
        newCounters.set(word, counter - 1);
      } else {
        // not match
        isMatch = false;
        break;
      }
    }

    if (isMatch) {
      ans.push(i);
    }
  }

  return ans;
};

复杂度分析

naomiwufzz commented 2 years ago

思路:hash+双指针

利用题目说的words长度相同的条件,每次遍历考虑判断总长度和模式字符串相同的子字符串。判断子字符串和模式字符串的hash相同(但感觉复杂度可能可以降低,看了大佬的解答,好像比较难优化了)

代码

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        # hash
        target_hash = dict()
        for w in words:
            target_hash[w] = target_hash.get(w, 0) + 1
        target_length = len(words[0]) * len(words)
        def str_hash(s):
            hashmap = dict()
            i = 0
            while i <= len(s)-len(words[0]):
                # 字符串bba 匹配['bb', 'ba']? 看上去限制了目标长度不会碰到这种问题了,仅仅count会有问题,但是子串长度限定之后,hashmap和目标hashmap必须匹配的条件下就不会有问题
                if s[i:i+len(words[0])] in target_hash:
                    hashmap[s[i:i+len(words[0])]] = hashmap.get(s[i:i+len(words[0])], 0) + 1
                    i += len(words[0])
                else:
                    i += 1
            return hashmap
        res = []
        for i in range(len(s)):
            if i + target_length > len(s):
                break
            cur_str = s[i:i+target_length]
            cur_hash = str_hash(cur_str)
            if cur_hash == target_hash:
                res.append(i)
        return res

复杂度分析

zhangzz2015 commented 2 years ago

思路

关键点

代码

C++ Code:


class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {

        vector<int> ret; 
        unordered_map<string, int> record; 
        int oneWordSize = words[0].size(); 
        for(int i =0; i < words.size(); i++)
            record[words[i]]++;  
        if(s.size()< words.size() * oneWordSize)
               return ret; 

        for(int i =0; i<=s.size() - words.size() * oneWordSize; i++)
        {

            int count =0; 
            unordered_map<string, int> record2; 
            if(dfs(s, record, i, oneWordSize, words.size(), record2, count))
                ret.push_back(i);
        }

        return ret; 

    }

    bool dfs(string& s,  unordered_map<string,int>& record, int root, int oneWordSize, int wordSize,   unordered_map<string,int>& record2, int count)
    {
        if(root>=s.size())
            return false; 
        string tmp = s.substr(root, oneWordSize); 
        if(record.find(tmp)==record.end())
            return false; 
        else 
        {
            record2[tmp]++;
            if(record2[tmp]> record[tmp])
                return false; 
        }
        count++; 
        if(count == wordSize)
            return true; 
        return dfs(s, record, root+oneWordSize, oneWordSize, wordSize, record2, count);
    }
};
HouHao1998 commented 2 years ago

思想

把words全部储存在哈希表里,遍历s,依次删除哈希表中的值

代码

    public List<Integer> findSubstring(String s, String[] words) {
        Map<String,Integer> map1= new HashMap<>();
        int l=0;
        int sum= 0;
        for (String word :
                words) {
            map1.put(word,map1.getOrDefault(word,0)+1);
            l=word.length();
            sum=sum+l;
        }

        Map<String, Integer> map2 = new HashMap<>(map1);
        int start ;
        int end ;
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            start =i;
            end =i + l;
            while (end<=i+sum&&end<=s.length()){
                String substring = s.substring(start, end);
                start= end;
                end = end+l;
                if(map2.containsKey(substring)){
                    if(map2.get(substring)-1>0){
                        map2.put(substring,map2.get(substring)-1);
                    }else {
                        map2.remove(substring);
                    }
                }else {
                    break;
                }
            }
            if(map2.size()==0){
                list.add(i);
            }
            map2 = new HashMap<>(map1);
        }
return list;
    }

复杂度

T:O(S.length()*words.size()) T: O(S.length())

wangyifan2018 commented 2 years ago
func findSubstring(s string, words []string) []int {
    res := []int{}
    counter := map[string]int{}
    for _, w := range words {
        counter[w]++
    }
    length, totalLen, tempCounter := len(words[0]), len(words[0])*len(words), copyMap(counter)
    for i, start := 0,0; i < len(s) - length + 1 && start < len(s) - length + 1; i++{
        if (tempCounter[s[i:i+length]] > 0) {
            tempCounter[s[i:i+length]]--
            if checkWords(tempCounter) && (i+length-start == totalLen) {
                res = append(res, start)
                continue
            }
            i = i + length - 1
        } else {
            start++
            i = start - 1
            tempCounter = copyMap(counter)
        }
    }
    return res
}

func checkWords(s map[string]int) bool {
    flag := true
    for _, v := range s {
        if v > 0 {
            flag = false
            break
        }
    }
    return flag
}

func copyMap(s map[string]int) map[string]int {
    c := map[string]int{}
    for k, v := range s {
        c[k] = v
    }
    return c
}
Leonalhq commented 2 years ago

思路

很明显的滑动窗口,即type3固定间距指针 我的想法是在窗口内看看包不包含所有的内容。 没想到hash表一开始,参考了答案

反思

但是hash的话是用字符还是每个string呢?-》 当然是单词啦 有些纠结怎么比较,其实在外面iterate好word以后里面存到字典直接比数字也ok啦。 Python方面老是忘记怎么写getOrDefault = wordDict[word] = wordDict.get(word,0) + 1

解题

class Solution(object):
    def findSubstring(self, s, words):

        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        l = 0
        k = len(words)
        n = len(words[0])
        wordDict = {}
        result = []
        for w in words:
            wordDict[w] = wordDict.get(w,0) + 1
            # wordDict[w] += 1
        index = 0

        while l+k*n<= len(s):
            # split
            split_sub = [str(s[index : index + n]) for index in range(l,l+k*n, n)]
            diction = {}
            j = 0
            while j<k:
                # put or +1 
                sub = split_sub[j]
                diction[sub] = diction.get(sub,0) + 1
                if diction.get(sub) > wordDict.get(sub):
                    break
                j = j + 1 

            if(j==k):
                result.append(l)

            l = l + 1

        return result
chenming-cao commented 2 years ago

解题思路

哈希表。建立初始哈希表储存串联所有单词的子串的单词和对应出现次数。遍历字符串s确定子串起始点。每次先复制初始的哈希表,然后从起始点开始截取长度为words[i].length的子字符串,看该子字符串是否出现在哈希表中。如果没出现,则将起始点后移一位。如果出现,更新哈希表,出现次数减1,次数为0时则将Key从哈希表中删除。重复次循环继续截取长度为words[i].length的子字符串,直到哈希表为空。如果循环结束哈希表为空,表示子串中所有单词全部找到,将起始点存放到结果数组中,将起始点后移一位继续遍历。最后返回结果数组。

代码

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> res = new ArrayList<>();
        int slen = s.length();
        int num = words.length;
        int wlen = words[0].length();
        if (slen < num * wlen) return res;

        Map<String, Integer> wordMap = new HashMap<>();
        for (String word: words) {
            wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);
        }

        Map<String, Integer> map = new HashMap<>();
        for (int i = 0; i <= slen - num * wlen; i++) {
            String sub = "";
            int start = i;         
            map.putAll(wordMap);
            do {
                sub = s.substring(start, start + wlen);
                if (!map.containsKey(sub)) break;
                int count = map.get(sub) - 1;
                if (count == 0) map.remove(sub);
                else map.put(sub, count);
                start += wlen;
            } while (!map.isEmpty());

            if (map.isEmpty()) res.add(i);
            map.clear();
        }
        return res;
    }
}

复杂度分析

ZJP1483469269 commented 2 years ago

思路

暴力求解 遍历字符串s 判断是否满足

代码

class Solution {
public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> res = new LinkedList<>();

    int len = words[0].length();
    for(int i=0;i<=s.length()-len*words.length;i++){
        HashMap<String,Integer> map = new HashMap<>();
        for(int k =0;k<words.length;k++){
            map.put(words[k],map.getOrDefault(words[k],0)+1);
        }
        int x=0;
        for(int j=0;j<words.length;j++){
            String cur_str = s.substring(i+j*len,i+(j+1)*len);
            if(map.containsKey(cur_str)){
                if(map.getOrDefault(cur_str,0)!=0) x++;
                map.put(cur_str,map.getOrDefault(cur_str,0)-1);
            }else{
                break;
            }
        }
        if(x==words.length){
            res.add(i);
        }

    }
    return res;
}
}

复杂度分析

时间复杂度:O(n*m)n为s长度 m为words长度 空间复杂度:O(m)m为words长度

user1689 commented 2 years ago

题目

https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/30-chuan-lian-suo-you-dan-ci-de-zi-chuan-bvy9/

思路

滑动窗口+哈希表

python3

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        # time n*m
        # space n
        # 思路一
        # 双哈希表
        # 第一个哈希表用于存储words中单词出现的频次
        # 第二个哈希表用于存储遍历过程中单词出现的频次

        allWords = collections.Counter(words)
        lengthWord = len(words[0])
        numOfWords = len(words)
        n = len(s)
        res = []
        for i in range(0, n - lengthWord * numOfWords + 1):
            subWords = collections.defaultdict(int)
            idx = i
            # 开始检查子串内部是否满足条件
            while idx < i + lengthWord * numOfWords:
                curWord = s[idx: idx+lengthWord]
                # 如果不存在或者存在的次数已经满了
                if curWord not in allWords or subWords[curWord] == allWords[curWord]:
                    break
                subWords[curWord] += 1
                idx = idx + lengthWord
            # 此时如果idx位于末尾表示满足条件 即没有被break过
            if idx == i + lengthWord * numOfWords:
                res.append(i)
        return res

复杂度分析

相关题目

  1. https://leetcode-cn.com/problems/minimum-window-substring/
xuezhongyuan commented 2 years ago

代码:

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) {
            boolean hasRemoved = false; //防止情况三移除后,情况一继续移除
            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)) {
                        // hasWords.put(word, value);
                        hasRemoved = true;
                        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);

            }
            //出现情况一,子串完全匹配,我们将上一个子串的第一个单词从 HashMap2 中移除
            if (num > 0 && !hasRemoved) {
                String firstWord = s.substring(i, i + wordLen);
                int v = hasWords.get(firstWord);
                hasWords.put(firstWord, v - 1);
                num = num - 1;
            }

        }

    }
    return res;
}

}
BlueRui commented 2 years ago

Problem 30. Substring with Concatenation of All Words

Algorithm

Complexity

Code

Language: Java

public List<Integer> findSubstring(String s, String[] words) {
    Map<String, Integer> counts = new HashMap<>();
    for (String word : words) {
        counts.put(word, counts.getOrDefault(word, 0) + 1);
    }
    List<Integer> result = new ArrayList<>();
    int wordsCount = words.length;
    int wordLength = words[0].length();
    for (int i = 0; i < s.length() - wordsCount * wordLength + 1; i++) {
        Map<String, Integer> seen = new HashMap<>();
        int j = 0;
        while (j < wordsCount) {
            String word = s.substring(i + j * wordLength, i + (j + 1) * wordLength);
            if (counts.containsKey(word)) {
                seen.put(word, seen.getOrDefault(word, 0) + 1);
                if (seen.get(word) > counts.getOrDefault(word, 0)) {
                    break;
                }
            } else {
                break;
            }
            j++;
        }
        if (j == wordsCount) {
            result.add(i);
        }
    }
    return result;
}
zjsuper commented 2 years ago

brute-force

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        lens = len(words[0])
        total = lens * len(words)
        if len(s) < total:
            return []
        ans = []
        #print(len(s),total)
        dic1 = {}
        for word in words:
            if word in dic1:
                dic1[word] += 1
            else:
                dic1[word] =0
        for i in range(len(s)-total+1):
            allin = 0
            for w in words:
                if w in s[i:i+total]:
                    allin +=1
                else:
                    #slow += 1
                    break
            if allin == len(words):
                ans.append(i)
        refine_ans = []
        for start in ans:
            temp = s[start:start+total]
            dic2 = {}
            for i in range(0,total,lens):
                print(temp[i:i+lens])
                if temp[i:i+lens] in dic2:

                    dic2[temp[i:i+lens]] += 1
                else:
                    dic2[temp[i:i+lens]] =0    
            good = True
            for k,v in dic1.items():
                if k in dic2:
                    if dic2[k] == v:
                        pass
                    else:
                        good = False
                else:
                    good = False
            if good:
                refine_ans.append(start)
        return refine_ans
biancaone commented 2 years ago
class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        if not s or not words:
            return []

        word_map = Counter(words)
        results = []

        word_size = len(words[0])
        num_word = len(words)

        list_size = word_size * num_word

        for i in range(len(s) - list_size + 1):
            seen = dict(word_map)
            word_used = 0
            for j in range(i, i + list_size, word_size):
                sub_str = s[j: j + word_size]
                if sub_str in seen and seen[sub_str] > 0:
                    seen[sub_str] -= 1
                    word_used += 1
                else:
                    break
            if word_used == num_word:
                results.append(i)

        return results
Daniel-Zheng commented 2 years ago

思路

HashMap。

代码(C++)

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string, int> hashMapWords;
        vector<int> res;
        if (s.size() == 0 || words.size() == 0) return res;
        for (int i = 0; i < words.size(); i++) hashMapWords[words[i]]++;
        for (int i = 0; i < s.size() - words[0].size() * words.size() + 1; i++) {
            string tempWord = s.substr(i, words[0].size() * words.size());
            unordered_map<string, int> hashMapTemp;
            int j;
            for (j = 0; j < tempWord.size(); j += words[0].size()) {
                string subTempWord = tempWord.substr(j, words[0].size());
                if (!hashMapWords.count(subTempWord)) break;
                hashMapTemp[subTempWord]++;
                if (hashMapTemp[subTempWord] > hashMapWords[subTempWord]) break;
            }
            if (j == tempWord.size()) res.push_back(i);
        }
        return res;
    }
};

复杂度分析

Yufanzh commented 2 years ago

Intuition

another sliding window problem. Pay attention to shrinking and updating and condition: shrinking: when match == Len(needs hashmap) updating result: when right - left = word_len * wordlist_len

Algorithm in python3

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        needs = {}
        for word in words:
            if word in needs:
                needs[word] += 1
            else:
                needs[word] = 1

        n = len(s)
        # pay attention that word in the words list are of the same length
        m = len(words[0])
        size = len(words)  
        if size == 0:
            return;
        ans = []
        for i in range(m):
            left = i
            right = i
            valid = 0
          #  ans = []
            windows = {}
            while right <= n-m:
                wr = s[right: right+m]
                if wr in needs:
                    if wr in windows:
                        windows[wr] += 1
                    else:
                        windows[wr] = 1
                    if windows[wr] == needs[wr]:
                        valid += 1
                right += m

                while valid == len(needs):
                    wl = s[left: left+m]
                    print("right:", right)
                    print("left:", left)
                    # pay attention to ending condition
                    if right - left == size * m:
                        ans.append(left)
                    if wl in needs:
                        windows[wl] -= 1
                        if windows[wl] < needs[wl]:
                            valid -= 1
                    left += m
        return ans

Complexity Analysis:

flame0409 commented 2 years ago

思路

由于是乱序,那么s子串和words的串里面每一个word数量一样就行

所以按照这个思路:设置窗口大小为words总长,移动窗口计算s子串中各个word的数量

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        Map<String, Integer> map = new HashMap<>();
        List<Integer> res = new ArrayList<>();
        if(words == null){
            return res;
        }
        if(words.length == 0){
            return res;
        }
        for(String word :words){
            map.put(word, map.getOrDefault(word, 0)+1);
        }
        int sLen = s.length();
        int wordLen = words[0].length();
        int cnt = words.length;
        for(int i = 0; i < sLen - wordLen*cnt +1; i++){
            String childstr = s.substring(i, i + wordLen*cnt);
            Map<String, Integer> smap = new HashMap<>();
            int loc = 0;
            for(; loc < childstr.length(); loc += wordLen){
                String word = childstr.substring(loc, loc+ wordLen);
                if(!map.containsKey(word))break;
                smap.put(word, smap.getOrDefault(word, 0)+1);
                if(smap.get(word)>map.get(word))break;
            }
            if(loc == childstr.length()){

            res.add(i);
            }
        }
        return res;

    }
}

时间复杂度:当长度很短时,s视为每个字符都要遍历,为s的长度,在第二层中每次都要遍历words和word,所以复杂度为O(N M K),N为S长度,M为Words长度,K为每个word长度。

空间复杂度:O(M)

Francis-xsc commented 2 years ago

思路

双哈希表+滑动窗口 用一个哈希表存储words 另一个哈希表存储滑动窗口内的单词

代码


class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ans;
        unordered_map<string,int>b;
        int len=s.size();
        for(auto x:words)
            b[x]++;
        int wordlen=words[0].size();
        for(int m=0;m<wordlen;m++)
        {
            unordered_map<string,int>a;
            int i=m,j=m;
            for(int k=0;k<words.size();k++)
            {
                a[s.substr(j,wordlen)]++;
                j+=wordlen;
            }
            while(j<len)
            {
                if(a==b)
                    ans.push_back(i);
                a[s.substr(i,wordlen)]--;
                if(a[s.substr(i,wordlen)]==0)
                    a.erase(s.substr(i,wordlen));
                a[s.substr(j,wordlen)]++;
                i+=wordlen;
                j+=wordlen;
            }
            if(a==b)
                ans.push_back(i);
        }
        return ans;
    }
};

复杂度分析

a244629128 commented 2 years ago

var findSubstring = function(s, words) {
    const charLens = words[0].length;
    const totalWordsLens = charLens * words.length;
    const storage = {};
    const res = [];
    words.forEach(e=> storage[e] = (storage[e] || 0)+1);

    for(let i = 0; i<=s.length-totalWordsLens;i++){
        // coppy the origin storage
        let coppyStorage = {...storage};
        let count = words.length;
                // console.log("outer:",coppyStorage,storage,count)

        for(let j = i; j< j+totalWordsLens; j+= charLens){
            let currChar = s.slice(j,j+charLens);
                // console.log(i,j,currChar)

            if( !(currChar in coppyStorage) || coppyStorage[currChar]<= 0 ){
                break;
            }
            coppyStorage[currChar]--;
            count--;
                         // console.log("inner:",coppyStorage,storage,count)

        }
        if(count === 0){
            res.push(i);
        }
    }
    return res;
};
laurallalala commented 2 years ago

代码

class Solution(object):
    def findSubstring(self, s, words):
        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        res, ct = [], collections.defaultdict(int)
        word_num = len(words)
        s_len = len(s)
        if words != []:
            word_len = len(words[0])
        else:
            return res
        for i in range(word_num):
            ct[words[i]] += 1
        for i in range(s_len-word_num*word_len+1):
            cur, j = collections.defaultdict(int), 0
            while j<word_num:
                word = s[i+j*word_len:i+(j+1)*word_len]
                if not word in ct:
                    break
                cur[word] += 1
                if cur[word] > ct[word]:
                    break
                j += 1
            if j == word_num:
                res.append(i)
        return res

复杂度

ghost commented 2 years ago

题目

  1. Substring with Concatenation of All Words

思路

Hash table and Two pointers

代码


class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        memo = collections.defaultdict(lambda:0)
        n = len(words[0]) 
        m = len(words)

        res = []

        if len(s) < m*n : return []

        for word in words:
            memo[word] += 1

        for i in range(len(s) - n*m + 1):
            curr = s[i:i+m*n]
            temp = collections.defaultdict(lambda:0)
            j = 0
            while (j < m*n):
                sub_curr = curr[j:j+n]

                if sub_curr not in memo: break

                temp[sub_curr]+=1

                if temp[sub_curr] >memo[sub_curr]: break

                j += n

            if j ==  m*n : res.append(i)

        return res           

复杂度

Space: O(M) Time: O( S M N)

M is number of words, N is the length of each word

cicihou commented 2 years ago

import copy

class Solution:
    def findSubstring(self, s: str, words):
        ''' sliding window
        由于 words 里面的 word 等长
        那么 words 的长 m 以及 word 的长 n 可以组成 m * n
        用这个 window 在 s 中遍历查找即可
        '''
        m = len(words)
        n = len(words[0])
        if len(s) < m * n:
            return []

        words_count = {}
        for w in words:
            words_count[w] = words_count.get(w, 0) + 1

        res = []
        i = 0
        while i + m*n <= len(s):
            tmp = s[i:i+m*n]
            # 注意此处必须是深拷贝
            tmp_words = copy.deepcopy(words_count)

            j = 0
            while j < len(tmp) and tmp[j*n:(j+1)*n] in tmp_words:
                tmp_words[tmp[j*n:(j+1)*n]] -= 1
                j += 1
            counts = tmp_words.values()
            # 注意此处判断条件必须有这两个,才能保证 counts 里面有且只有 0
            if sum(counts) == 0 and len(set(counts)) == 1:
                res.append(i)
            i += 1
        return res

s = Solution()
s.findSubstring('barfoothefoobarman', ["foo","bar"])
s.findSubstring("wordgoodgoodgoodbestword", ["word","good","best","word"])
kidexp commented 2 years ago

thoughts

暴力就是从前往后遍历,用一个dict来存word出现次数,每次check word是不是在words里面,并且word的出现次数要小于等于在words里面的个数 否则就跳到下一个,

可以通过two pointers 来优化

code

from typing import List
from collections import Counter, defaultdict

class Solution:
    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        """
        brute force
        """
        word_count_dict = Counter(words)
        word_length = len(words[0])
        word_num = len(words)
        result = []
        for i in range(0, len(s) - word_length * word_num + 1):
            actual_word_count = defaultdict(int)
            end = i
            find_word_num = 0
            while end < len(s):
                word = s[end : end + word_length]
                if (
                    word in word_count_dict
                    and actual_word_count[word] < word_count_dict[word]
                    and find_word_num < word_num
                ):
                    actual_word_count[word] += 1
                    find_word_num += 1
                else:
                    break
                end += word_length
            if find_word_num == word_num:
                result.append(i)
        return result

    def findSubstring(self, s: str, words: List[str]) -> List[int]:
        """
        two pointers
        """
        word_count_dict = Counter(words)
        word_length = len(words[0])
        word_num = len(words)
        result = []
        for i in range(word_length):
            start, end = i, i
            actual_word_count = defaultdict(int)
            while end < len(s):
                word = s[end : end + word_length]
                while (
                    word in word_count_dict
                    and word_count_dict[word] <= actual_word_count[word]
                ):
                    actual_word_count[s[start : start + word_length]] -= 1
                    start += word_length

                if (
                    word in word_count_dict
                    and actual_word_count[word] < word_count_dict[word]
                ):
                    actual_word_count[word] += 1
                elif word not in word_count_dict:
                    start = end + word_length
                    actual_word_count = defaultdict(int)
                end += word_length
                if end - start == word_num * word_length:
                    result.append(start)
        return result

complexity

time O(len(s)len(words[0])len(words))

space O(len(words)*len(words[0]))

ivalkshfoeif commented 2 years ago
class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        Map<String, Integer> wordMap = new HashMap<>();
        List<Integer> result = new ArrayList<>();
        for (String word: words){
            wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);
        }
        int wordNum = words.length, wordLen = words[0].length();
        for (int i = 0; i + wordNum * wordLen - 1 < s.length(); i++){
            Map<String, Integer> winMap = new HashMap<>();
            int index = i;
            while (index < i + wordNum * wordLen){
                String str = s.substring(index, index+ wordLen);
                if (!wordMap.containsKey(str) || winMap.getOrDefault(str, 0) == wordMap.get(str)){
                    break;
                }
                winMap.put(str, winMap.getOrDefault(str, 0) + 1);
                index += wordLen;
            }
            if (index == i + wordNum * wordLen){
                result.add(i);
            }

        }
        return result;

    }
}

代码性能似乎一般,初学滑动窗口,为了降低学习曲线就优化到这吧

xjlgod commented 2 years ago
class Solution {
   public List<Integer> findSubstring(String s, String[] words) {
        Map<String, Integer> word2Count = new HashMap<>();
        for (String word : words) {
            word2Count.put(word, word2Count.getOrDefault(word, 0) + 1);
        }
        List<Integer> res = new ArrayList<>();
        int len = s.length(), wordLen = words[0].length(), wordsLen = wordLen * (words.length);
        if (s.length() < wordsLen) {
            return res;
        }
        int left = 0, right = 0;
        for (int i = 0; i < wordLen; i++) {
            left = right = i;
            int count = words.length;
            Map<String, Integer> window = new HashMap<>();
            while (right + wordLen <= len) {
                String temp = s.substring(right, right + wordLen);
                // 如果不包含此单词,直接窗口左移
                if (!word2Count.containsKey(temp)) {
                    right += wordLen;
                    left = right;
                    window.clear();
                    count = words.length;
                } else {
                    window.put(temp, window.getOrDefault(temp, 0) + 1);
                    count--;
                    while (word2Count.get(temp) < window.get(temp)) {
                        String w = s.substring(left, left + wordLen);
                        window.put(w, window.get(w) - 1);
                        count++;
                        left += wordLen;
                    }
                    right += wordLen;
                }
                if (count == 0) {
                    res.add(left);
                }
            }
        }
        return res;
    }
}
linearindep commented 2 years ago

【思路】滑动窗口,没有来得及优化。 就是固定words.length * words[0].length() 大小的窗口,每看一个窗口,就copy一个存入每个词count的hashmap(因为有可能一个词出现多次)。 这个题目最tricky的地方就是在于查看每个词,hashmap里面count--,如果hashmap为空,就找到了,return i。 比较难想到判断hashmap为空。之前老想着两个hashmap相等,但是就太复杂了

【复杂度】O(s.length words.length words[0].length)

public List<Integer> findSubstring(String s, String[] words){
        List<Integer> ans = new ArrayList<>();
        if(s == null || words == null || s.length() ==0||words.length ==0) return ans;
        int word_length = words[0].length();
        HashMap<String,Integer> word_count = new HashMap<>();
        for(int i  = 0; i<words.length; i++){
            int count = word_count.getOrDefault(words[i],0);
            word_count.put(words[i], count+1);
        }

        int total_length = word_length * words.length;
        for(int i = 0; i<=s.length()-total_length; i+=1){
            String current = s.substring(i, i+total_length);
            HashMap<String,Integer> current_count = new HashMap<String, Integer>(word_count);

            for(int j= 0; j<words.length;j+=1){
                String temp = current.substring(j*word_length,(j+1)*word_length);
                if(!current_count.containsKey(temp)) break;
                int count = current_count.get(temp);

                if(count == 1){
                    current_count.remove(temp);
                }else{
                    current_count.put(temp, count-1);
                }

                if(current_count.isEmpty()){
                    ans.add(i);
                }

            }

        }
        return ans;

    }