LeetCode 1961 - Check If String Is a Prefix of Array
The problem is asking us to determine whether a given string s can be formed by concatenating the first k elements of an array words, where k is some positive integer less than or equal to the length of words.
Difficulty: 🟢 Easy
Topics: Array, Two Pointers, String
Solution
Problem Understanding
The problem is asking us to determine whether a given string s can be formed by concatenating the first k elements of an array words, where k is some positive integer less than or equal to the length of words. In other words, s must match exactly the prefix of the array when all its strings are concatenated in order.
The input consists of a string s and a list of strings words. The expected output is a boolean value: true if s can be formed by concatenating a prefix of words and false otherwise. The constraints indicate that words is relatively small (up to 100 elements) and each string within it is short (up to 20 characters), while s can be longer (up to 1000 characters). Therefore, efficiency is important but we do not need extreme optimizations like advanced string matching algorithms.
Important edge cases include when s is shorter than the first element in words, when s equals a single word in words, when s is exactly the concatenation of all words in words, and when words contains elements that partially match s but cannot form a full prefix.
Approaches
A brute-force approach would concatenate every prefix of words incrementally and check if the resulting string matches s. This works because it directly simulates the definition of a prefix string. However, this can be inefficient, especially if the concatenated prefix grows larger than s, as unnecessary concatenation may occur.
The key insight for an optimal solution is that we do not need to build the entire concatenated string at once. Instead, we can iterate through words while keeping track of our position in s. For each word, we check if it matches the corresponding substring in s. If at any point a word does not match, we can immediately return false. This avoids unnecessary concatenations and allows for early termination.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * m) | O(n * m) | Concatenate prefixes and compare with s directly. n = length of words, m = length of s. |
| Optimal | O(m) | O(1) | Iterate through words and match substrings of s without creating a new string. |
Algorithm Walkthrough
- Initialize a variable
posto 0 to track our current position in strings. - Iterate through each word in
words. - For each word, check if the substring of
sstarting atposand with length equal to the word matches the word. - If it does not match, return
falseimmediately. - If it matches, increment
posby the length of the current word. - After each increment, check if
posequals the length ofs. If it does, we have matched the full stringsusing a prefix ofwordsand can returntrue. - If the loop completes without returning,
sis longer than the concatenated prefix ofwords, so returnfalse.
Why it works: The algorithm maintains the invariant that s[:pos] equals the concatenation of the first few words processed. If any word fails to match its corresponding substring in s, we can immediately conclude s is not a prefix string. This ensures correctness while avoiding unnecessary concatenation.
Python Solution
from typing import List
class Solution:
def isPrefixString(self, s: str, words: List[str]) -> bool:
pos = 0
for word in words:
if pos + len(word) > len(s) or s[pos:pos + len(word)] != word:
return False
pos += len(word)
if pos == len(s):
return True
return False
The code initializes a pos pointer to track the progress in s. For each word in words, it checks if the substring of s starting at pos matches the word. If not, it returns false. After adding the length of the word to pos, it checks if pos has reached the end of s, in which case the prefix is successfully matched. If the loop finishes without matching s completely, it returns false.
Go Solution
func isPrefixString(s string, words []string) bool {
pos := 0
for _, word := range words {
if pos+len(word) > len(s) || s[pos:pos+len(word)] != word {
return false
}
pos += len(word)
if pos == len(s) {
return true
}
}
return false
}
The Go solution mirrors the Python logic. It uses pos to track the current index in s. The key differences are Go’s slice syntax s[pos:pos+len(word)] for substring extraction and explicit use of len() for lengths. No additional memory is allocated beyond integer tracking.
Worked Examples
Example 1: s = "iloveleetcode", words = ["i","love","leetcode","apples"]
| Step | word | s[pos:pos+len(word)] | pos | Action |
|---|---|---|---|---|
| 1 | "i" | "i" | 0 -> 1 | Match, increment pos |
| 2 | "love" | "love" | 1 -> 5 | Match, increment pos |
| 3 | "leetcode" | "leetcode" | 5 -> 13 | Match, pos equals len(s) -> return true |
Example 2: s = "iloveleetcode", words = ["apples","i","love","leetcode"]
| Step | word | s[pos:pos+len(word)] | pos | Action |
|---|---|---|---|---|
| 1 | "apples" | "ilove" | 0 -> 6 | Mismatch -> return false |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m) | Each character in s is checked at most once against words. |
| Space | O(1) | No extra data structures are used; only a pointer is maintained. |
The time complexity is linear in the length of s because each character is examined at most once. The space complexity is constant since no additional arrays or concatenated strings are created.
Test Cases
# Basic examples
assert Solution().isPrefixString("iloveleetcode", ["i","love","leetcode","apples"]) == True # exact prefix
assert Solution().isPrefixString("iloveleetcode", ["apples","i","love","leetcode"]) == False # wrong start
# Edge cases
assert Solution().isPrefixString("a", ["a"]) == True # single character match
assert Solution().isPrefixString("a", ["b"]) == False # single character mismatch
assert Solution().isPrefixString("abc", ["a","b","c"]) == True # multiple words exact match
assert Solution().isPrefixString("abc", ["a","b"]) == False # prefix too short
# Longer s
assert Solution().isPrefixString("leetcode", ["leet","code"]) == True
assert Solution().isPrefixString("leetcode", ["leet","coder"]) == False
| Test | Why |
|---|---|
| "iloveleetcode" with correct prefix | Validates basic matching logic |
| "iloveleetcode" with wrong first word | Validates early termination on mismatch |
| Single character match | Tests minimal input edge case |
| Single character mismatch | Confirms mismatch handling |
| Multiple words exact match | Confirms handling multiple concatenations |
| Prefix too short | Confirms detection of incomplete prefix |
Longer s with exact match |
Tests standard non-trivial concatenation |
Longer s with last word mismatch |
Ensures algorithm stops correctly on mismatch |
Edge Cases
One important edge case is when s is shorter than the first word in words. A naive implementation that attempts to concatenate or slice without bounds checking could crash or produce incorrect results. Our implementation explicitly checks pos + len(word) > len(s) before comparing.
Another edge case is when s is exactly the concatenation of all words in words. The algorithm handles this because pos will increment to match len(s) on the last word, and True will be returned correctly.
A third edge case is when words contains elements that partially match s but cannot fully construct it. For instance, s = "abcde" and words = ["ab", "cd", "f"]. The algorithm correctly identifies the mismatch at "f" and returns False immediately, preventing an incorrect match.