title: Check If String Is a Prefix of Array slug: check-if-string-is-a-prefix-of-array difficulty: easy leetcode_id: 1961 leetcode_url: https://leetcode.com/problems/check-if-string-is-a-prefix-of-array/ categories: - arrays - strings patterns: - slug: two-pointers is_optimal: true function_signature: "def is_prefix_string(s: str, words: list[str]) -> bool:" test_cases: visible: - input: { s: "iloveleetcode", words: ["i", "love", "leetcode", "apples"] } expected: true - input: { s: "iloveleetcode", words: ["apples", "i", "love", "leetcode"] } expected: false hidden: - input: { s: "a", words: ["a", "b"] } expected: true - input: { s: "abc", words: ["abc"] } expected: true - input: { s: "ab", words: ["a", "bc"] } expected: false - input: { s: "abc", words: ["ab", "c", "d"] } expected: true description: | Given a string `s` and an array of strings `words`, determine whether `s` is a **prefix string** of `words`. A string `s` is a **prefix string** of `words` if `s` can be made by concatenating the first `k` strings in `words` for some **positive** `k` no larger than `words.length`. Return `true` *if* `s` *is a prefix string of* `words`, *or* `false` *otherwise*. constraints: | - `1 <= words.length <= 100` - `1 <= words[i].length <= 20` - `1 <= s.length <= 1000` - `words[i]` and `s` consist of only lowercase English letters. examples: - input: 's = "iloveleetcode", words = ["i","love","leetcode","apples"]' output: "true" explanation: "s can be made by concatenating \"i\", \"love\", and \"leetcode\" together." - input: 's = "iloveleetcode", words = ["apples","i","love","leetcode"]' output: "false" explanation: "It is impossible to make s using a prefix of arr." explanation: intuition: | Think of building a puzzle where you must place pieces strictly from left to right in order. You have a target string `s` that you need to recreate using the words in `words`, but there's a crucial rule: you can only use words from the **beginning** of the array, in sequence. You pick the 1st word, then optionally the 2nd, then optionally the 3rd, and so on — never skipping any word or changing the order. The core insight is that you're essentially checking if the concatenation of `words[0] + words[1] + ... + words[k-1]` **exactly equals** `s` for some value of `k`. Not a substring, not a partial match — an **exact match**. As you concatenate words one by one, you're building up a string. At each step, you check: "Does what I've built so far match `s` exactly?" If yes, you've found your answer. If you've built something longer than `s` or something that doesn't match the prefix of `s`, you can stop early. approach: | We solve this using a **Single Pass with String Building**: **Step 1: Initialise a result string** - `built`: Start with an empty string that we'll grow by appending words   **Step 2: Iterate through the words array** - For each word in `words`, append it to `built` - After each append, check if `built == s` - If equal, return `true` immediately — we've found our prefix string - If `len(built) > len(s)`, return `false` — we've overshot, no point continuing   **Step 3: Return false if loop completes** - If we exhaust all words without matching `s` exactly, return `false`   This approach is efficient because we stop as soon as we find a match or overshoot the target length. We never do unnecessary work. common_pitfalls: - title: Checking for Substring Instead of Exact Match description: | A common mistake is to check if `s` is a *substring* of the concatenation, or if the concatenation *contains* `s`. For example, with `s = "i"` and `words = ["i", "love"]`, the concatenation `"ilove"` contains `"i"`, but that's not what we want. We need `s` to be **exactly equal** to the concatenation of some prefix of `words`. Always use equality (`==`), not substring checks (`in` or `startswith`). wrong_approach: "Check if s is substring of concatenation" correct_approach: "Check if s equals concatenation exactly" - title: Forgetting to Check After Each Word description: | Some implementations concatenate all words first, then check if the result equals `s`. This misses the requirement that `s` must equal the concatenation of the **first k words** for some specific `k`. With `s = "ilove"` and `words = ["i", "love", "leetcode"]`, concatenating all gives `"iloveleetcode"`, which doesn't equal `s`. But concatenating just the first 2 words gives `"ilove"`, which does equal `s`. You must check for equality **after each word** is added. wrong_approach: "Concatenate all, then check" correct_approach: "Check equality after each concatenation" - title: Not Handling the Overshoot Case description: | If the built string becomes longer than `s` but doesn't match, continuing to add more words is wasteful. For efficiency, check if `len(built) > len(s)` and return `false` early. This also handles the case where `words` contains very long strings that immediately overshoot `s`. key_takeaways: - "**Prefix concatenation**: This pattern of checking prefix concatenations appears in string matching problems — build incrementally and validate at each step" - "**Early termination**: Stop processing as soon as you find a match or determine a match is impossible (overshoot)" - "**Exact match vs. substring**: Be precise about what the problem asks — equality is stricter than containment" - "**Order matters**: The sequential constraint (must use words in order from the start) is what makes this problem tractable" time_complexity: "O(n) where n is the length of string `s`. In the worst case, we process characters up to the length of `s` before finding a match or overshooting." space_complexity: "O(n) where n is the length of string `s`. We build a string that grows up to the size of `s`." solutions: - approach_name: String Building is_optimal: true code: | def is_prefix_string(s: str, words: list[str]) -> bool: # Build the concatenation incrementally built = "" for word in words: # Add the next word to our built string built += word # Check if we've matched s exactly if built == s: return True # If we've overshot, no point continuing if len(built) > len(s): return False # Exhausted all words without matching s return False explanation: | **Time Complexity:** O(n) — We process at most n characters where n = len(s). **Space Complexity:** O(n) — The built string grows up to the size of s. This solution builds the concatenation one word at a time, checking for an exact match after each addition. Early termination on overshoot keeps it efficient. - approach_name: Index Tracking is_optimal: true code: | def is_prefix_string(s: str, words: list[str]) -> bool: # Track our position in s index = 0 for word in words: # Check if this word matches the next part of s if s[index:index + len(word)] != word: return False # Move our position forward index += len(word) # Check if we've matched all of s if index == len(s): return True # Didn't reach the end of s return False explanation: | **Time Complexity:** O(n) — We scan through s once using index tracking. **Space Complexity:** O(1) — Only using an integer index, no extra string built. This approach avoids building a new string by directly comparing slices of `s` with each word. It's more memory-efficient as it uses constant extra space. - approach_name: Join and Compare is_optimal: false code: | def is_prefix_string(s: str, words: list[str]) -> bool: # Try each possible prefix length k for k in range(1, len(words) + 1): # Join first k words and compare if "".join(words[:k]) == s: return True # Early exit if we've already exceeded s if len("".join(words[:k])) > len(s): return False return False explanation: | **Time Complexity:** O(k * n) — For each k, joining creates a new string. **Space Complexity:** O(n) — Each join creates a string up to length n. This approach is less efficient because `join` creates a new string for each value of k. The string building approach is better because it incrementally extends the same string.