Skip to content
← All posts

Replace All ?'s to Avoid Consecutive Repeating Characters

5 min read
leetcodeproblemeasystrings

LeetCode Replace All ?'s to Avoid Consecutive Repeating Characters (Problem 1576) asks you to replace every '?' character in a string so that no two adjacent characters are the same.

The problem

You are given a string s containing only lowercase English letters and '?' characters. Replace every '?' with any lowercase letter so that the resulting string has no two adjacent characters that are equal.

You can choose any valid replacement. It is guaranteed that a solution always exists.

Examples:

  • s = "?zs" returns "azs" (the '?' at index 0 becomes 'a', which does not match 'z' to its right)
  • s = "ubv?w" returns "ubvaw" (the '?' at index 3 becomes 'a', which differs from both 'v' and 'w')
  • s = "j?qg??b" returns "jaqgacb" (each '?' gets a letter that avoids clashing with its neighbors)
input?i=0z1s2outputazsinputui=0b1v2?3w4outputubvawinputji=0?1q2g3?4?5b6outputjaqgacb? to replacechosen replacement
Each '?' is replaced with a letter that does not match its left or right neighbor. Yellow cells are the original question marks; green cells show the chosen replacements.

The approach

The key insight is that you only need three letters to guarantee a valid choice. At any position, there are at most two neighbors (one on the left, one on the right). With three candidates, 'a', 'b', and 'c', at least one of them will differ from both neighbors.

The algorithm is a single left-to-right scan:

  1. Convert the string to a mutable list of characters.
  2. For each index i, if s[i] is '?', check the left neighbor (s[i-1] if it exists) and the right neighbor (s[i+1] if it exists).
  3. Pick the first character from 'a', 'b', 'c' that does not match either neighbor.
  4. Replace the '?' with that character and move on.

By the time you reach a '?', any earlier '?' to its left has already been replaced. So s[i-1] is always a concrete letter, not another '?'. The right neighbor might still be '?', but that is fine. You only need to avoid matching it, and '?' is not a lowercase letter, so it will never equal 'a', 'b', or 'c'.

You only need three candidate letters because each position has at most two neighbors. This is the same principle behind graph coloring: a path graph (which is what a string is) can always be colored with just two colors, but three makes the greedy scan trivial.

Step-by-step walkthrough

Here is the algorithm running on "j?qg??b". Watch how each '?' gets replaced as the scan moves left to right.

Step 0: Start

j0?1q2g3?4?5b6

Input string: "j?qg??b". Scan left to right, replacing each '?' with 'a', 'b', or 'c' that avoids matching neighbors.

Step 1: Index 0

j0?1q2g3?4?5b6

Index 0 is 'j', not a '?'. Skip it.

Step 2: Index 1

j0a1q2g3?4?5b6

Index 1 is '?'. Left = 'j', right = 'q'. Pick 'a' (first of a/b/c that avoids both neighbors).

Step 3: Index 2

j0a1q2g3?4?5b6

Index 2 is 'q', not a '?'. Skip it.

Step 4: Index 3

j0a1q2g3?4?5b6

Index 3 is 'g', not a '?'. Skip it.

Step 5: Index 4

j0a1q2g3a4?5b6

Index 4 is '?'. Left = 'g', right = '?'. Pick 'a' (first of a/b/c that avoids both neighbors).

Step 6: Index 5

j0a1q2g3a4c5b6

Index 5 is '?'. Left = 'a', right = 'b'. Pick 'c' (first of a/b/c that avoids both neighbors).

Step 7: Index 6

j0a1q2g3a4c5b6

Index 6 is 'b', not a '?'. Skip it.

Step 8: Done

j0a1q2g3a4c5b6

Result: "jaqgacb". No two adjacent characters are the same.

Notice that when the scan reaches index 5, the left neighbor (index 4) has already been resolved to 'a'. The right neighbor (index 6) is 'b'. So the algorithm picks 'c', the first candidate that avoids both.

The code

def modify_string(s: str) -> str:
    chars = list(s)
    n = len(chars)
    for i in range(n):
        if chars[i] == "?":
            left = chars[i - 1] if i > 0 else ""
            right = chars[i + 1] if i < n - 1 else ""
            for c in "abc":
                if c != left and c != right:
                    chars[i] = c
                    break
    return "".join(chars)

Here is what each part does:

  • Convert to list. Python strings are immutable, so you work with a list of characters instead.
  • Scan left to right. For each index i, check if chars[i] is '?'.
  • Check neighbors. Grab the left and right neighbors. If the index is out of bounds, use an empty string (which will never match any candidate).
  • Pick a candidate. Loop through 'a', 'b', 'c'. The first one that differs from both neighbors wins.
  • Join and return. Convert the list back to a string.

Complexity analysis

ApproachTimeSpace
Left-to-right greedy scanO(n)O(n)

Time: O(n) where n is the length of the string. You visit each character exactly once. The inner loop over 'a', 'b', 'c' runs at most 3 iterations per '?', which is constant.

Space: O(n) for the character list. If the language allows in-place mutation (like C++ or Java with a char array), you can do this in O(1) extra space.

The building blocks

Greedy left-to-right scan

The idea of processing elements one at a time from left to right, making a locally optimal choice at each step, is the greedy pattern. Here, the "locally optimal" choice is any letter that avoids matching neighbors. There is no need for backtracking because three candidates always suffice for two constraints.

This same scan-and-decide approach appears in problems like gas station (greedy circular scan), jump game (greedy reach), and candy distribution (greedy pass).

Neighbor-aware replacement

Checking the left and right neighbors before choosing a value is a common string and array technique. You will see it in problems that involve adjacent constraints, such as wiggle sort (each element must alternate between greater and less than its neighbors) and painting houses (no two adjacent houses share a color).

Edge cases

All question marks. A string like "????" works fine. The first '?' picks 'a' (no left neighbor, right is '?'). The second picks 'b' (left is 'a', right is '?'). The pattern alternates: "abab...".

No question marks. If the string has no '?' characters, the loop skips every index and returns the original string unchanged.

Single character. The string "?" becomes "a". There are no neighbors, so the first candidate always works.

Question mark at the edges. When '?' appears at index 0 or the last index, there is only one neighbor to avoid. The algorithm handles this naturally because the missing neighbor defaults to an empty string.

Adjacent question marks. Two or more '?' characters in a row are fine. Each one is resolved in order, and by the time you reach the next '?', its left neighbor is already a concrete letter.

From understanding to recall

This problem is easy to understand: scan left to right, pick a letter that avoids matching neighbors. But will you remember the trick of using just three candidates ('a', 'b', 'c') when you see a similar constraint problem in an interview? Will the neighbor-checking pattern come to you automatically?

Spaced repetition helps lock in the building blocks. Instead of re-reading this solution, you drill the greedy scan pattern and the neighbor-aware replacement technique at increasing intervals. After a few reps spread across days, the pieces become automatic. When a problem says "fill in blanks subject to adjacent constraints," you already know the shape of the solution.

Related posts