Quick Overview

Given a list of distinct lowercase words, return, in input order, every word that can be split into two or more shorter words from the same list, with reuse allowed. The problem tests efficient string segmentation and dictionary lookups across a large word list.

Find All Words Formed by Concatenating Two or More Shorter Words in the List

Company: Amazon

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

You are given a list of distinct words. A word is *composite* if it can be split into two or more consecutive pieces such that every piece is itself a word in the list. The same word may be used as a piece more than once in a split. Return every composite word in the list. In the reported interview this was a variant of a well-known problem whose exact changes were not recorded, and only the approach was discussed. This version states the standard form so that it can be run. ### Function Signature ```python def composite_words(words: list[str]) -> list[str]: ``` ### Rules - A split of a word `w` is a sequence of two or more non-empty strings whose concatenation, in order, equals `w`. The word is composite if at least one of its splits uses only strings that appear in `words`. - Every piece of a split is shorter than `w`, so a word can never use itself as a piece. - Return the composite words in the order in which they appear in `words`. If no word is composite, return `[]`. ### Constraints - `1 <= len(words) <= 10^4` - `1 <= len(words[i]) <= 30` - The total length of all words is at most `10^5`. - Every word consists of lowercase English letters, and all words are distinct. ### Examples **Example 1** ```text Input: words = ["ab", "cd", "abcd", "abc", "dab", "abcdab", "cdcd"] Output: ["abcd", "abcdab", "cdcd"] ``` `"abcd"` is `"ab" + "cd"`, `"abcdab"` is `"ab" + "cd" + "ab"`, and `"cdcd"` uses `"cd"` twice. Neither `"abc"` nor `"dab"` can be split into words from the list. **Example 2** ```text Input: words = ["x", "xy", "yz", "xxyz", "xyz"] Output: ["xxyz", "xyz"] ``` `"xyz"` is `"x" + "yz"`, and `"xxyz"` is `"x" + "xyz"`. `"xy"` cannot be split, because `"y"` is not in the list. **Example 3** ```text Input: words = ["a", "bc", "abcd"] Output: [] ``` Any split of `"abcd"` needs a last piece ending in `"d"`, and the only word in the list that ends in `"d"` is `"abcd"` itself.

Overview: Given a list of distinct lowercase words, return, in input order, every word that can be split into two or more shorter words from the same list, with reuse allowed. The problem tests efficient string segmentation and dictionary lookups across a large word list.

You are given a list `words` of distinct words. A word `w` is **composite** if it can be split into two or more consecutive pieces such that every piece is itself a word in `words`. The same word may be used as a piece more than once in a split. Implement `composite_words(words)` and return every composite word in the list. **Rules** - A split of a word `w` is a sequence of two or more non-empty strings whose concatenation, in order, equals `w`. The word is composite if at least one of its splits uses only strings that appear in `words`. - Every piece of a split is shorter than `w`, so a word can never use itself as a piece. - Return the composite words in the order in which they appear in `words`. If no word is composite, return an empty list `[]`. **Example 1** ```text Input: words = ["ab", "cd", "abcd", "abc", "dab", "abcdab", "cdcd"] Output: ["abcd", "abcdab", "cdcd"] ``` `"abcd"` is `"ab" + "cd"`, `"abcdab"` is `"ab" + "cd" + "ab"`, and `"cdcd"` uses `"cd"` twice. Neither `"abc"` nor `"dab"` can be split into words from the list. **Example 2** ```text Input: words = ["x", "xy", "yz", "xxyz", "xyz"] Output: ["xxyz", "xyz"] ``` `"xyz"` is `"x" + "yz"`, and `"xxyz"` is `"x" + "xyz"`. `"xy"` cannot be split, because `"y"` is not in the list. The output keeps the input order, so `"xxyz"` comes first. **Constraints** - `1 <= len(words) <= 10^4` - `1 <= len(words[i]) <= 30` - The total length of all words is at most `10^5`. - Every word consists of lowercase English letters, and all words are distinct. No count or length in this problem exceeds 2^31 - 1, so 32-bit integers suffice in every language.

Constraints

  • 1 <= len(words) <= 10^4
  • 1 <= len(words[i]) <= 30
  • The total length of all words is at most 10^5.
  • Every word consists of lowercase English letters, and all words are distinct.

Examples

Input: (['ab', 'cd', 'abcd', 'abc', 'dab', 'abcdab', 'cdcd'],)

Expected Output: ['abcd', 'abcdab', 'cdcd']

Explanation: Source example 1: 'abcd' = 'ab'+'cd', 'abcdab' = 'ab'+'cd'+'ab', 'cdcd' reuses 'cd'; 'abc' and 'dab' cannot be split.

Input: (['x', 'xy', 'yz', 'xxyz', 'xyz'],)

Expected Output: ['xxyz', 'xyz']

Explanation: Source example 2, multi-level chain: 'xyz' = 'x'+'yz' and 'xxyz' = 'x'+'xyz'; 'xy' fails because 'y' is absent. Input order is kept.

Hints

  1. A piece must be strictly shorter than the word being tested, so a word can never be built from itself, and a single-letter word is never composite.
  2. A valid split may reuse the same piece and may need three or more pieces; a first piece that matches can still lead to a remainder that cannot be covered.
  3. Every word has at most 30 letters, so the number of places where a single word can be cut is small.

Loading coding console...

Show the approach

Approach

Put every word in a hash set. For each word w of length n (in input order), compute reach[i] for 0 <= i <= n: reach[i] is true when the prefix w[0:i] can be written as a concatenation of list words that are each strictly shorter than w. reach[0] is true (empty prefix), and reach[i] is true when some j < i has reach[j] true, the piece w[j:i] is in the set, and its length i - j is less than n. w is composite exactly when reach[n] is true.

Invariant: after position i is processed, reach[i] is true if and only if w[0:i] has a cover by allowed pieces, because any such cover ends in a last piece w[j:i] whose preceding prefix w[0:j] is itself covered, and every j < i was already decided.

Correctness: requiring each piece to be shorter than w is equivalent to the source rule that a word cannot use itself; since all words are distinct, the only list word of length n that could match w[0:n] is w itself. A full cover made only of pieces shorter than n must contain at least two pieces, and conversely every split into two or more non-empty pieces uses pieces shorter than n, so reach[n] matches the definition of a composite word. Pieces may repeat because the set lookup does not consume words. Exploring every j for every i (instead of greedily taking the longest or shortest matching prefix) handles dead-end prefixes.

Edge cases: a single-letter word can never be composite (it is skipped); a singleton list returns []; output order is the input order, not sorted by length or alphabetically, and a composite may appear before its pieces in the input because the set is built before any word is tested.

Time complexity:
O(S * L^2), where S is the total length of all words and L <= 30 is the maximum word length (O(L^2) cut pairs per word, each hashing a substring of length at most L)
Space complexity:
O(S) for the set of words plus O(L) per word for the reach array