1576. Replace All 's to Avoid Consecutive Repeating Characters
Problem Description
You are given a string s
that contains only lowercase English letters and the character '?'
. Your task is to replace all '?'
characters with lowercase letters following these rules:
-
No consecutive repeating characters: After replacing all
'?'
characters, the resulting string should not have any two adjacent characters that are the same. -
Cannot modify non-
'?'
characters: You can only change'?'
characters; all other lowercase letters must remain unchanged. -
Guaranteed solvability: The input guarantees that there are no consecutive repeating characters among the existing lowercase letters (excluding
'?'
characters).
The solution works by iterating through each character in the string. When a '?'
is encountered, the algorithm tries to replace it with one of the letters from "abc"
. For each candidate letter, it checks:
- If there's a previous character (at position
i-1
), ensure the candidate letter is different from it - If there's a next character (at position
i+1
), ensure the candidate letter is different from it
Since we only need to avoid matching the immediate neighbors, using just three letters ('a'
, 'b'
, 'c'
) is sufficient - at most two letters will be blocked by neighbors, leaving at least one valid choice.
For example:
- Input:
"?zs"
→ Output:"azs"
(or"bzs"
or"czs"
) - Input:
"ubv?w"
→ Output:"ubvaw"
(cannot use'v'
or'w'
) - Input:
"j?qg??b"
→ Output:"jaqgacb"
(each'?'
is replaced to avoid consecutive repeats)
Intuition
The key insight is that we only need to worry about immediate neighbors when replacing a '?'
character. Since we want to avoid consecutive repeating characters, each '?'
just needs to be different from the character immediately before it and immediately after it.
Why can we use just three letters ('a'
, 'b'
, 'c'
)? Consider the worst-case scenario: a '?'
is sandwiched between two different characters, like "a?b"
. In this case, we cannot use 'a'
(conflicts with left neighbor) or 'b'
(conflicts with right neighbor), but we can always use 'c'
. Even if both neighbors were the same character, like "a?a"
, we'd have two available choices ('b'
or 'c'
).
The greedy approach works perfectly here - we can process each '?'
independently from left to right without worrying about future decisions. This is because:
- Each
'?'
only affects its immediate neighbors - Once we replace a
'?'
, it becomes a fixed character that won't change - Future
'?'
replacements will respect the already-fixed characters
The algorithm naturally handles edge cases:
- If
'?'
is at the beginning, we only check the right neighbor - If
'?'
is at the end, we only check the left neighbor - Multiple consecutive
'?'
characters are handled one by one, and each replacement creates a new constraint for the next'?'
This local decision-making strategy guarantees a valid solution because we always have at least one valid character choice out of our three options, regardless of what the neighbors are.
Solution Approach
The implementation uses a straightforward iterative approach with character replacement:
-
Convert string to list: Since strings are immutable in Python, we first convert the string
s
to a list of characters to allow in-place modifications. -
Iterate through each character: We traverse the string from left to right using index
i
from0
ton-1
. -
Check for '?' character: When we encounter a
'?'
at positioni
, we need to replace it with a valid lowercase letter. -
Try candidate letters: We iterate through a small set of candidate letters
"abc"
. For each candidate letterc
:- Check left neighbor: If
i > 0
(not the first character), verify thats[i-1]
is not equal to the candidatec
- Check right neighbor: If
i+1 < n
(not the last character), verify thats[i+1]
is not equal to the candidatec
- If either neighbor matches the candidate, we
continue
to try the next letter - If the candidate doesn't conflict with either neighbor, we assign
s[i] = c
andbreak
out of the loop
- Check left neighbor: If
-
Join and return: After processing all characters, we join the list back into a string and return the result.
The key algorithmic choices:
- Why only "abc"? At most two neighbors can block two letters, leaving at least one valid choice from three options
- Why left-to-right? This ensures previously replaced
'?'
characters are already fixed when we check them as neighbors - Why break after finding valid character? Once we find a valid replacement, we don't need to check other options since any valid solution is acceptable
Time Complexity: O(n)
where n
is the length of the string (we visit each character once, and for each '?'
, we try at most 3 candidates)
Space Complexity: O(n)
for the list conversion (required for string mutation in Python)
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the example "j?qg??b"
step by step to see how the algorithm works:
Initial string: "j?qg??b"
(converted to list: ['j','?','q','g','?','?','b']
)
Step 1 - Index 0: Character is 'j'
, not a '?'
, so we skip it.
Step 2 - Index 1: Character is '?'
- Try
'a'
: Check left neighbor ('j'
) ✓ different, check right neighbor ('q'
) ✓ different - Replace with
'a'
:['j','a','q','g','?','?','b']
Step 3 - Index 2: Character is 'q'
, not a '?'
, so we skip it.
Step 4 - Index 3: Character is 'g'
, not a '?'
, so we skip it.
Step 5 - Index 4: Character is '?'
- Try
'a'
: Check left neighbor ('g'
) ✓ different, check right neighbor ('?'
) ✓ different - Replace with
'a'
:['j','a','q','g','a','?','b']
Step 6 - Index 5: Character is '?'
- Try
'a'
: Check left neighbor ('a'
) ✗ same, continue to next candidate - Try
'b'
: Check left neighbor ('a'
) ✓ different, check right neighbor ('b'
) ✗ same, continue - Try
'c'
: Check left neighbor ('a'
) ✓ different, check right neighbor ('b'
) ✓ different - Replace with
'c'
:['j','a','q','g','a','c','b']
Step 7 - Index 6: Character is 'b'
, not a '?'
, so we skip it.
Final result: Join the list back to string: "jaqgacb"
Notice how:
- Each
'?'
is processed independently - We only need to check immediate neighbors
- The third
'?'
(at index 5) needed to try multiple candidates before finding'c'
that didn't conflict with either'a'
on the left or'b'
on the right - Using just three letters (
'a'
,'b'
,'c'
) was sufficient for all replacements
Solution Implementation
1class Solution:
2 def modifyString(self, s: str) -> str:
3 """
4 Replace all '?' characters in the string with lowercase letters
5 such that no two adjacent characters are the same.
6
7 Args:
8 s: Input string containing lowercase letters and '?' characters
9
10 Returns:
11 Modified string with all '?' replaced by valid lowercase letters
12 """
13 # Convert string to list for in-place modification
14 char_list = list(s)
15 n = len(char_list)
16
17 # Iterate through each character in the string
18 for i in range(n):
19 # Check if current character is a question mark
20 if char_list[i] == "?":
21 # Try each character from 'a', 'b', 'c'
22 for replacement_char in "abc":
23 # Check if the replacement character conflicts with neighbors
24 has_left_conflict = (i > 0 and char_list[i - 1] == replacement_char)
25 has_right_conflict = (i + 1 < n and char_list[i + 1] == replacement_char)
26
27 # Skip this character if it matches either neighbor
28 if has_left_conflict or has_right_conflict:
29 continue
30
31 # Valid character found, replace the '?' and break
32 char_list[i] = replacement_char
33 break
34
35 # Convert list back to string and return
36 return "".join(char_list)
37
1class Solution {
2 public String modifyString(String s) {
3 // Convert string to character array for in-place modification
4 char[] charArray = s.toCharArray();
5 int length = charArray.length;
6
7 // Iterate through each character in the array
8 for (int i = 0; i < length; ++i) {
9 // Check if current character is a placeholder '?'
10 if (charArray[i] == '?') {
11 // Try replacing '?' with characters 'a', 'b', or 'c'
12 for (char replacement = 'a'; replacement <= 'c'; ++replacement) {
13 // Check if the replacement character conflicts with adjacent characters
14 boolean hasConflictWithPrevious = (i > 0 && charArray[i - 1] == replacement);
15 boolean hasConflictWithNext = (i + 1 < length && charArray[i + 1] == replacement);
16
17 // Skip this replacement if it matches either adjacent character
18 if (hasConflictWithPrevious || hasConflictWithNext) {
19 continue;
20 }
21
22 // Valid replacement found, assign it and move to next position
23 charArray[i] = replacement;
24 break;
25 }
26 }
27 }
28
29 // Convert the modified character array back to string and return
30 return String.valueOf(charArray);
31 }
32}
33
1class Solution {
2public:
3 string modifyString(string s) {
4 int stringLength = s.size();
5
6 // Iterate through each character in the string
7 for (int i = 0; i < stringLength; ++i) {
8 // Check if current character is a placeholder '?'
9 if (s[i] == '?') {
10 // Try replacing '?' with 'a', 'b', or 'c'
11 for (char candidateChar : "abc") {
12 // Check if the candidate character conflicts with adjacent characters
13 bool hasLeftConflict = (i > 0 && s[i - 1] == candidateChar);
14 bool hasRightConflict = (i + 1 < stringLength && s[i + 1] == candidateChar);
15
16 // Skip this candidate if it matches either adjacent character
17 if (hasLeftConflict || hasRightConflict) {
18 continue;
19 }
20
21 // Valid candidate found, replace '?' and move to next position
22 s[i] = candidateChar;
23 break;
24 }
25 }
26 }
27
28 return s;
29 }
30};
31
1/**
2 * Modifies a string by replacing '?' characters with lowercase letters
3 * ensuring no two adjacent characters are the same
4 * @param s - Input string containing letters and '?' characters
5 * @returns Modified string with all '?' replaced by appropriate letters
6 */
7function modifyString(s: string): string {
8 // Convert string to character array for easier manipulation
9 const characters: string[] = s.split('');
10 const length: number = s.length;
11
12 // Iterate through each character in the string
13 for (let i = 0; i < length; i++) {
14 // Check if current character is a placeholder
15 if (characters[i] === '?') {
16 // Try replacing with 'a', 'b', or 'c'
17 for (const candidate of 'abc') {
18 // Check if candidate conflicts with previous character
19 const conflictsWithPrevious: boolean = i > 0 && characters[i - 1] === candidate;
20
21 // Check if candidate conflicts with next character
22 const conflictsWithNext: boolean = i + 1 < length && characters[i + 1] === candidate;
23
24 // Skip this candidate if it conflicts with adjacent characters
25 if (conflictsWithPrevious || conflictsWithNext) {
26 continue;
27 }
28
29 // Valid candidate found, replace the '?' and move to next position
30 characters[i] = candidate;
31 break;
32 }
33 }
34 }
35
36 // Join the character array back into a string
37 return characters.join('');
38}
39
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the string.
The algorithm iterates through each character in the string exactly once using the outer for loop. For each character that is a "?"
, it checks at most 3 characters ('a', 'b', 'c') in the inner loop. The inner loop performs constant time operations - checking the previous character (if exists) and the next character (if exists). Since we check at most 3 characters for each position and this is a constant, the inner loop contributes O(1)
time. Therefore, the overall time complexity is O(n) * O(1) = O(n)
.
Space Complexity: O(n)
where n
is the length of the string.
The algorithm converts the input string to a list of characters with s = list(s)
, which creates a new list that requires O(n)
space to store all the characters. The remaining variables (n
, i
, c
) use constant space O(1)
. The final "".join(s)
operation creates a new string, but this is part of the return value and typically not counted in auxiliary space complexity. Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting to Modify String Directly
A frequent mistake is trying to modify the string in-place without converting it to a list first:
# WRONG - This will raise TypeError: 'str' object does not support item assignment
def modifyString(self, s: str) -> str:
for i in range(len(s)):
if s[i] == "?":
s[i] = 'a' # ERROR: strings are immutable in Python
Solution: Always convert the string to a list first, modify the list, then join it back:
char_list = list(s)
# ... modifications ...
return "".join(char_list)
2. Not Checking Both Neighbors Before Replacement
Some implementations check neighbors sequentially rather than together, leading to incorrect replacements:
# WRONG - Replaces without checking if it conflicts with the right neighbor
def modifyString(self, s: str) -> str:
char_list = list(s)
for i in range(len(char_list)):
if char_list[i] == "?":
if i > 0 and char_list[i-1] == 'a':
char_list[i] = 'b'
else:
char_list[i] = 'a' # Might conflict with char_list[i+1]
Solution: Check both neighbors before deciding on a replacement character, using a loop to try different options.
3. Using Insufficient Candidate Characters
Trying to use only two candidate characters (like 'a' and 'b') can lead to impossible situations:
# WRONG - Only using 'a' and 'b' can fail for input like "a?b" for replacement_char in "ab": # Not enough options # Both 'a' and 'b' would be blocked by neighbors
Solution: Use at least three candidate characters. Since a position can have at most two neighbors, three options guarantee at least one valid choice.
4. Incorrect Boundary Checking
Failing to properly check array boundaries can cause IndexError:
# WRONG - Always checks both neighbors without boundary validation if char_list[i-1] == replacement_char or char_list[i+1] == replacement_char: continue # IndexError when i=0 or i=n-1
Solution: Always validate indices before accessing array elements:
has_left_conflict = (i > 0 and char_list[i - 1] == replacement_char) has_right_conflict = (i + 1 < n and char_list[i + 1] == replacement_char)
5. Forgetting to Break After Finding Valid Replacement
Continuing to iterate through candidates after finding a valid one wastes computation and might lead to logical errors in more complex variations:
# INEFFICIENT - Continues checking even after finding valid replacement for replacement_char in "abc": if not has_conflict: char_list[i] = replacement_char # Missing break - will keep iterating unnecessarily
Solution: Add a break
statement immediately after assigning the valid character to exit the inner loop early.
What is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!