A two-pointer group comparison solution for counting how many words can be stretched to match a target string.
Problem Restatement
We are given a string s and an array of query strings words.
A query word is stretchy if it can be expanded to become exactly s.
Expansion works on groups of the same consecutive character. For example, in:
"heeellooo"the groups are:
"h", "eee", "ll", "ooo"A group in the query word may be stretched only if the corresponding group in s has length at least 3.
Return how many words in words are stretchy. The official rule is that a query word can be made equal to s by extending character groups, where extended groups must reach length at least 3.
Input and Output
| Item | Meaning |
|---|---|
| Input | A target string s and a list of query words |
| Output | Number of stretchy words |
| Stretch operation | Add copies of a character inside one consecutive group |
| Main condition | Character groups must match in order |
Examples
Example:
s = "heeellooo"
words = ["hello", "hi", "helo"]The word "hello" can become "heeellooo":
"h" -> "h"
"e" -> "eee"
"ll" -> "ll"
"o" -> "ooo"So "hello" is stretchy.
The word "hi" cannot match because the character groups do not match.
The word "helo" cannot match because the target has "ll" but the word has only "l". Since the target group length is 2, it cannot be created by stretching.
So the answer is:
1First Thought: Build Every Expansion
One possible idea is to generate every string that each word can become after stretching, then check whether s appears among them.
This is not practical.
A group can be stretched to many lengths, and generating all possible expanded words would create unnecessary work.
We only need to compare the word against s group by group.
Key Insight
The structure of character groups must be the same.
For example:
s = "heeellooo"
word = "hello"Their groups are:
| String | Groups |
|---|---|
s | h, eee, ll, ooo |
word | h, e, ll, o |
For each pair of groups:
- The characters must be the same.
- The group in
wordcannot be longer than the group ins. - If the lengths differ, the group in
smust have length at least3.
So a group pair is valid when:
s_count == word_countor:
s_count >= 3 and s_count > word_countIf s_count < 3, then the counts must be exactly equal.
Algorithm
Define a helper function:
is_stretchy(s, word)Use two pointers:
| Pointer | Meaning |
|---|---|
i | Current position in s |
j | Current position in word |
While both pointers are inside their strings:
- If
s[i] != word[j], returnFalse. - Count the size of the current character group in
s. - Count the size of the current character group in
word. - If the word group is larger, return
False. - If the counts differ and the group in
shas length less than3, returnFalse.
At the end, both strings must be fully consumed.
Then count how many words pass this helper.
Correctness
The helper compares s and word one character group at a time.
If two corresponding groups have different characters, no sequence of stretch operations can make them equal, because stretching only adds copies inside existing groups and cannot change letters or reorder groups.
If the word group is longer than the target group, it is impossible to shrink it, so the word is not stretchy.
If the group lengths differ and the target group has length less than 3, the target group could not have been produced by a valid stretch operation. Therefore, the word is not stretchy.
If the target group has length at least 3, then a shorter matching word group can be stretched to match it.
The helper accepts only when all groups satisfy these conditions and both strings end at the same time. Therefore, it returns True exactly for stretchy words.
The main function applies this exact test to every query word, so the final count is correct.
Complexity
Let S = len(s) and let T be the total length of all words.
| Metric | Value | Why |
|---|---|---|
| Time | O(S * len(words) + T) | Each check scans s and one word |
| Space | O(1) | Only counters and pointers are stored |
Implementation
class Solution:
def expressiveWords(self, s: str, words: list[str]) -> int:
def is_stretchy(word: str) -> bool:
i = 0
j = 0
while i < len(s) and j < len(word):
if s[i] != word[j]:
return False
ch = s[i]
s_start = i
while i < len(s) and s[i] == ch:
i += 1
s_count = i - s_start
w_start = j
while j < len(word) and word[j] == ch:
j += 1
w_count = j - w_start
if w_count > s_count:
return False
if s_count != w_count and s_count < 3:
return False
return i == len(s) and j == len(word)
answer = 0
for word in words:
if is_stretchy(word):
answer += 1
return answerCode Explanation
The helper starts with two pointers:
i = 0
j = 0They move through s and word.
If the current characters differ, the group structure cannot match:
if s[i] != word[j]:
return FalseWe count the current group length in s:
s_start = i
while i < len(s) and s[i] == ch:
i += 1
s_count = i - s_startThen we count the matching group length in word:
w_start = j
while j < len(word) and word[j] == ch:
j += 1
w_count = j - w_startIf the word group is too long, it cannot be reduced:
if w_count > s_count:
return FalseIf the counts differ, the target group must be stretchable:
if s_count != w_count and s_count < 3:
return FalseFinally, both strings must end together:
return i == len(s) and j == len(word)This prevents accepting a word that has extra groups or misses groups from s.
Testing
def run_tests():
sol = Solution()
assert sol.expressiveWords("heeellooo", ["hello", "hi", "helo"]) == 1
assert sol.expressiveWords("zzzzzyyyyy", ["zzyy", "zy", "zyy"]) == 3
assert sol.expressiveWords("abcd", ["abc"]) == 0
assert sol.expressiveWords("aaa", ["a", "aa", "aaa", "aaaa"]) == 3
assert sol.expressiveWords("heeellooo", ["heeellooo", "hello"]) == 2
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"heeellooo" sample | Checks normal stretching |
| Large groups in target | Multiple shorter groups can stretch |
| Missing final group | Both strings must end together |
Target group length 3 | Allows shorter groups but not longer groups |
| Exact match | Exact group counts are valid |