Replace All Digits with Characters
LeetCode 1844, Replace All Digits with Characters, gives you a string s where characters at even indices are lowercase English letters, and characters at odd indices are digits. You need to replace every digit with the result of a shift operation: shift(c, x) returns the character that is x positions after c in the alphabet.
For example, shift('a', 1) = 'b' and shift('c', 3) = 'f'.
Given s = "a1c1e1", the output is "abcdef" because each digit gets replaced by shifting the preceding letter forward by that many positions.
The approach: iterate and shift
The structure of the string makes this problem very approachable. Every even index holds a letter. Every odd index holds a digit. For each odd index i, the replacement is shift(s[i-1], int(s[i])), which means "take the letter just before this digit and move it forward by the digit's value."
You can build the result by walking through the string index by index:
- If the index is even, the character is already a letter. Keep it as-is.
- If the index is odd, the character is a digit. Replace it with
chr(ord(s[i-1]) + int(s[i])).
That is the entire algorithm. One pass through the string, applying character arithmetic at odd positions.
The code
def replace_digits(s: str) -> str:
result = list(s)
for i in range(1, len(s), 2):
result[i] = chr(ord(s[i - 1]) + int(s[i]))
return "".join(result)
Here is what each part does:
- Convert to list. Strings in Python are immutable, so converting to a list lets you modify individual characters in place.
- Iterate odd indices.
range(1, len(s), 2)visits indices 1, 3, 5, and so on. These are all the digit positions. - Apply the shift.
ord(s[i - 1])gets the ASCII value of the preceding letter. Addingint(s[i])shifts it forward.chr(...)converts back to a character. - Join and return. Combine the list back into a string.
Visual walkthrough
Let's trace through s = "a1c1e1" step by step to see how each position gets processed.
Step 1: Index 0 (even) - keep letter
Index 0 is even, so 'a' is a letter. Copy it directly to the result.
Step 2: Index 1 (odd) - replace digit
Index 1 is odd, so '1' is a digit. shift('a', 1) = chr(ord('a') + 1) = 'b'. Place 'b' in the result.
Step 3: Index 2 (even) - keep letter
Index 2 is even, so 'c' is a letter. Copy it directly to the result.
Step 4: Index 3 (odd) - replace digit
Index 3 is odd, so '1' is a digit. shift('c', 1) = chr(ord('c') + 1) = 'd'. Place 'd' in the result.
Step 5: Index 4 (even) - keep letter
Index 4 is even, so 'e' is a letter. Copy it directly to the result.
Step 6: Index 5 (odd) - replace digit
Index 5 is odd, so '1' is a digit. shift('e', 1) = chr(ord('e') + 1) = 'f'. Place 'f' in the result. Done!
Every even index is copied directly. Every odd index gets replaced by shifting the preceding letter. After processing all six characters, the result is "abcdef".
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(n), where n is the length of the string |
| Space | O(n), for the result list |
You visit each character exactly once. The chr, ord, and int operations are all O(1). The list-to-string join at the end is O(n). There is no nested iteration, no sorting, and no extra data structures beyond the result.
Building blocks
This problem uses two techniques that appear across many string problems.
Character arithmetic with ord/chr
The ord() function converts a character to its ASCII integer value, and chr() converts back. This lets you do arithmetic on characters. chr(ord('a') + 3) gives you 'd'. This pattern shows up in Caesar ciphers, character frequency counting with arrays, and any problem where you need to map letters to numeric positions.
The template looks like this:
shifted = chr(ord(char) + offset)
You will see this in problems like mapping characters to array indices (ord(ch) - ord('a')) or generating character sequences.
String building from a mutable list
Python strings are immutable, so modifying characters in place requires converting to a list first. The pattern is:
chars = list(s)
chars[i] = new_value
result = "".join(chars)
This avoids the O(n^2) cost of building a string through repeated concatenation. Any time you need to modify specific positions in a string, convert to a list, make your changes, and join at the end.
Edge cases
Single character. If s = "a", the string has length 1 with only an even index. No digits to replace. The function returns "a" unchanged.
Shift of zero. If the digit is 0, then shift('a', 0) = 'a'. The character stays the same. The algorithm handles this naturally since chr(ord('a') + 0) = 'a'.
Large shift. A digit can be 0 through 9. The problem guarantees that shift(s[i-1], s[i]) never goes past 'z', so you do not need to worry about wrapping around the alphabet.
Two characters. The smallest input with a replacement is s = "a1", which becomes "ab". The loop runs once for index 1.
From understanding to recall
This problem is simple enough that you might think there is nothing to memorize. But the real value is in the building blocks: character arithmetic with ord/chr and the convert-to-list-then-join pattern for string mutation.
These micro-patterns show up in problems that are much harder. Two weeks from now, when you encounter a problem that requires shifting characters or modifying specific positions in a string, you want chr(ord(c) + offset) and the list conversion pattern to come automatically.
Spaced repetition locks these building blocks in. You practice typing them from scratch at increasing intervals until the patterns are in muscle memory. When a problem requires character arithmetic, you do not have to think about it. You just write it.
Related posts
- Valid Anagram - Another easy string problem that uses character frequency counting, a close cousin of character arithmetic
- Find the Index of the First Occurrence in a String - A string problem focused on substring matching and positional comparison
- Length of Last Word - A simple string traversal problem that drills index management