Skip to content

30. Substring with Concatenation of All Words (Hard)

Problem

Given a string s and a list words, return every starting index where a substring is made from all the words in any order. Each word must appear exactly as many times as it appears in words, and every word has the same length.

Examples

  • s = "barfoothefoobarman", words = ["foo", "bar"] returns [0, 9]. The matching windows are "barfoo" and "foobar".
  • s = "wordgoodgoodgoodbestword", words = ["word", "good", "best", "word"] returns []. The text never supplies the second required "word" inside one window.
  • s = "barfoofoobarthefoobarman", words = ["bar", "foo", "the"] returns [6, 9, 12].

Constraints

  • 1 <= s.length <= 10⁴
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • s and every word contain lowercase English letters.

LeetCode 30 · Link · Hard

Try it yourself

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Approach 1: Recount every candidate window

The total candidate length is fixed: words.length * words[0].length. Start at every character where that full window fits, split the window into word-sized pieces, and compare its frequency map with the required frequencies.

idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).

Let n be the length of s, m the number of words, and k the length of each word.

  • Time: O(n⋅m⋅k)O(n \cdot m \cdot k) character work. Each candidate rebuilds a map from m slices of length k.
  • Space: O(m⋅k)O(m \cdot k) for the distinct word keys and counts, excluding the result. Swift also uses O(n)O(n) space for its character array.

This is correct, but neighboring candidates repeat almost all the same counting work.

Approach 2: Slide through word-aligned lanes

A valid match can begin at any character, but once its start offset modulo k is fixed, every word boundary in that match is fixed too. Process the k possible offsets as independent lanes:

  1. Move right by one whole word at a time.
  2. Reset the lane when the new chunk is not required.
  3. If a required word becomes overrepresented, move left by whole words until its count is valid again.
  4. When the window contains exactly m words, record left, then remove one word so overlapping matches remain possible.
idle
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
  • Time: O(m⋅k+n⋅k+rlog⁡r)O(m \cdot k + n \cdot k + r \log r), where r is the number of matches. Building the required counts takes O(m⋅k)O(m \cdot k). Across all lanes, the scan performs O(n)O(n) hash-map operations, each involving a word of length k. These implementations sort the matching indices before returning them, adding O(rlog⁡r)O(r \log r).
  • Space: O(m⋅k+r)O(m \cdot k + r) for the word counts, result, and sorting workspace. Swift also uses O(n)O(n) space for its character array.

Walkthrough

For s = "barfoofoobarthefoobarman" and three-letter words, consider the lane starting at offset 0:

Chunk enteringWindow after adjustmentResult
bar[bar]Not full yet.
foo[bar, foo]Not full yet.
foo[foo]foo was overused, so remove bar and the older foo.
bar[foo, bar]Not full yet.
the[foo, bar, the]Record index 6.
foo[bar, the, foo]Record index 9.
bar[the, foo, bar]Record index 12.

Offsets 1 and 2 form their own lanes and produce no matches.

How to recognize this pattern

Look for all three signals:

  • The answer concerns contiguous substrings.
  • Every token has the same fixed width.
  • The required tokens form a multiset, so duplicate counts matter.

The equal-width rule is the giveaway. It turns an apparent permutation problem into a small number of aligned sliding-window scans. A character-frequency window is not enough because word boundaries and whole-word multiplicities define validity.

Key takeaways

  • Treat words as a frequency map, not a set. Duplicate requirements are part of the contract.
  • Split the string into k alignment lanes instead of advancing a word-based window one character at a time.
  • An unknown word invalidates the whole current lane window.
  • An overused known word only requires shrinking until that word’s count is legal again.
  • After recording a match, advance by one word to preserve overlapping matches.