A clear explanation of the Long Pressed Name problem using a two-pointer scan.
Problem Restatement
We are given two strings:
| Item | Meaning |
|---|---|
name | The name the friend intended to type |
typed | The actual string produced by the keyboard |
Sometimes a key may be long pressed. If the intended character is "a", the typed output may contain "a", "aa", "aaa", and so on.
We need to return true if typed could be produced from name by long pressing some characters. Otherwise, return false.
For example:
name = "alex"
typed = "aaleex"This is valid because "a" and "e" were repeated.
But:
name = "saeed"
typed = "ssaaedd"This is invalid because the typed string does not contain enough consecutive "e" characters to match "saeed".
Input and Output
| Item | Meaning |
|---|---|
| Input | Two strings name and typed |
| Output | True if typed can be a long-pressed version of name, otherwise False |
| Constraint | Characters must appear in the same order |
| Important rule | Extra characters in typed are allowed only if they repeat the previous typed character |
Examples
Example 1
name = "alex"
typed = "aaleex"Output:
TrueExplanation:
"a" was typed as "aa".
"l" was typed as "l".
"e" was typed as "ee".
"x" was typed as "x".
So typed can come from name.
Example 2
name = "saeed"
typed = "ssaaedd"Output:
FalseExplanation:
The intended name has two consecutive "e" characters.
But in typed, there is only one "e" before "d" starts.
A long press can add extra copies. It cannot remove required copies.
Example 3
name = "leelee"
typed = "lleeelee"Output:
TrueThe extra "l" and "e" characters can be explained as long presses.
Example 4
name = "laiden"
typed = "laiden"Output:
TrueNo long press is required. The original string itself is valid.
First Thought: Compare Character Groups
A long press only creates extra copies of the same character next to itself.
So instead of thinking about single characters, we can think about consecutive groups.
For example:
name = "alex"
typed = "aaleex"The groups are:
| String | Groups |
|---|---|
name | a, l, e, x |
typed | aa, l, ee, x |
Each group in typed must match the corresponding group in name.
Also, the typed group must be at least as long as the name group.
This works, but there is an even simpler scan.
Key Insight
Use two pointers.
Let:
| Pointer | Meaning |
|---|---|
i | Current position in name |
j | Current position in typed |
We scan typed from left to right.
For each character typed[j], there are two valid cases.
First, it may match the next needed character in name.
name[i] == typed[j]In that case, we consume one character from name by moving i.
Second, it may be an extra long-pressed character.
That is only valid when it equals the previous typed character:
typed[j] == typed[j - 1]If neither case is true, then typed contains a character that cannot be explained.
Algorithm
Initialize i = 0.
Then scan each character t in typed.
For every position j:
- If
iis still insidenameandname[i] == typed[j], moveiforward. - Otherwise, if
typed[j]equalstyped[j - 1], treat it as a long press. - Otherwise, return
False.
At the end, return whether all characters in name were consumed:
i == len(name)This final check matters.
For example:
name = "alex"
typed = "ale"The typed string follows the right order, but it never types "x", so the answer is False.
Correctness
The algorithm scans typed from left to right and keeps i as the number of characters already matched from name.
When typed[j] equals name[i], the current typed character correctly represents the next required character of name, so advancing i is valid.
When typed[j] does not equal name[i], it can only be valid if it is an extra copy caused by a long press. A long press repeats the same key as the previous typed character, so the condition typed[j] == typed[j - 1] exactly captures this case.
If neither condition holds, the current character in typed is neither the next required character nor a valid repeated character. Therefore, no long-press interpretation can produce typed, and returning False is correct.
After scanning all of typed, the algorithm returns True only if every character in name has been matched. If some character in name remains unmatched, then typed is missing required input, so it cannot be valid.
Therefore, the algorithm returns True exactly when typed can be produced from name by long pressing characters.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan typed once |
| Space | O(1) | We only store pointer i |
Here, n = len(typed).
Implementation
class Solution:
def isLongPressedName(self, name: str, typed: str) -> bool:
i = 0
for j, char in enumerate(typed):
if i < len(name) and name[i] == char:
i += 1
elif j == 0 or char != typed[j - 1]:
return False
return i == len(name)Code Explanation
We start with:
i = 0This points to the next character in name that we still need to match.
Then we scan typed:
for j, char in enumerate(typed):If the current typed character matches the next required character in name, we consume that character:
if i < len(name) and name[i] == char:
i += 1If it does not match, it must be a long-pressed duplicate of the previous typed character.
This condition rejects invalid characters:
elif j == 0 or char != typed[j - 1]:
return FalseThe j == 0 check matters because the first character cannot be a long-pressed duplicate of a previous character.
Finally:
return i == len(name)This confirms that every character in name was matched.
Testing
def run_tests():
s = Solution()
assert s.isLongPressedName("alex", "aaleex") is True
assert s.isLongPressedName("saeed", "ssaaedd") is False
assert s.isLongPressedName("leelee", "lleeelee") is True
assert s.isLongPressedName("laiden", "laiden") is True
assert s.isLongPressedName("pyplrz", "ppyypllr") is False
assert s.isLongPressedName("vtkgn", "vttkgnn") is True
assert s.isLongPressedName("alex", "ale") is False
print("all tests passed")
run_tests()| Test | Why |
|---|---|
"alex", "aaleex" | Basic valid long press |
"saeed", "ssaaedd" | Missing required repeated character |
"leelee", "lleeelee" | Multiple long-pressed groups |
"laiden", "laiden" | No long press |
"pyplrz", "ppyypllr" | Ends before matching all of name |
"vtkgn", "vttkgnn" | Valid repeated middle and final characters |
"alex", "ale" | Missing final character |