Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
You never need to decide which character to delete — you only need to count how many adjacent pairs are equal. Walk the string once and count every position i where s[i] == s[i-1]. That count is the minimum number of deletions, because each equal-neighbour collision forces exactly one removal. O(n) time, O(1) space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you realise you don't have to simulate deletions at all — that the answer is a pure count of adjacent equal pairs, derivable from a single left-to-right scan.
Where candidates slip
Building a new string by skipping duplicates and then computing the difference in lengths — correct but more work and more bug surface than just counting collisions.
How to stand out
Argue why counting is optimal: in any maximal run of k identical characters you are forced to delete exactly k − 1, and the run contributes exactly k − 1 collisions. Sum over runs = total collisions.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewString s of A/BScan i = 1 .. n-1YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Rebuild the Alternating String (Naive)
1Hint 1Explore hintHide hint
Imagine literally producing the alternating string: scan left to right and keep a character only if it differs from the last one you kept.
2Hint 2Explore hintHide hint
Append each kept character to a builder. The number of deletions is then |s| minus the length of what you kept.
3Hint 3Explore hintHide hint
This is correct, but you're allocating an output buffer just to subtract two lengths — can you skip building it?
A · Core idea
The literal reading is: construct the alternating result, then report how many characters you dropped. Walk the string, track the last character you decided to keep, and append the current character only when it differs.
Why it is correct. Keeping a character only when it differs from the previous kept one is exactly the greedy rule that produces a maximal alternating subsequence. Two equal neighbours can never both survive, so every survivor differs from its predecessor — the result is alternating by construction.
Why the deletion count falls out. Every input character is either kept or dropped. If kept characters survive, then |s| − kept were deleted. Subtracting the two lengths gives the answer.
Why we don't ship it. It is correct but wasteful: you allocate an O(n) buffer purely to throw it away after measuring its length. The problem only wants the count, and the count is fully determined by how many times two equal characters touch — which you can tally without storing anything. Use this version to explain the mechanics, then collapse it into the counting solution.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "AABAAB"i=0 'A'
nothing kept yet → keep
kept = "A"|s| − kept = 6 − 4 = 2C · Code with teaching comments
Reference implementation previewpublic int alternatingCharacters(String s) { StringBuilder kept = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (kept.length() == 0 || kept.charAt(kept.length() - 1) != c) {Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Building the survivors and then doing |s| − kept is the same arithmetic as counting collisions: every dropped character corresponds to exactly one collision with the character kept before it.
ComplexityO(n) time, O(n) space — One pass to build the filtered string is O(n) time, but the StringBuilder / list holding the survivors costs O(n) extra space.
E · Edge cases and pitfalls3Allocating an O(n) buffer when only a counter is needed.Explore edge casesHide edge cases
Pitfalls
- Allocating an O(n) buffer when only a counter is needed.
- Comparing against the previous INPUT character instead of the previous KEPT character — they coincide here, but the framing invites bugs.
- Forgetting the empty-buffer guard before reading the last kept character.
Approach 2 of 2 · Optimal
Count Adjacent Equal Pairs (Optimal)
1Hint 1Explore hintHide hint
You don't need the resulting string — only its size relative to the input. What single quantity captures every forced deletion?
2Hint 2Explore hintHide hint
A character must be deleted precisely when it equals the character immediately before it. Count those positions.
3Hint 3Explore hintHide hint
Loop i from 1 to n−1; whenever s[i] == s[i−1], increment a counter. Return the counter. No extra memory.
A · Core idea
Walk the string from index 1 and increment a counter every time the current character equals the one before it. That counter is the answer.
Why a collision forces exactly one deletion. Consider a maximal run of identical characters — say AAAA. To make it valid, only one of those As may remain, so you must delete k − 1 of them for a run of length k. Now count the adjacent equal pairs inside that run: positions (0,1), (1,2), (2,3) — exactly k − 1 of them. Run length minus one equals the number of internal collisions. So counting collisions counts forced deletions, run by run.
Why summing over the whole string is still exact. Distinct runs are separated by a character change, and a character change is not a collision, so runs never share or double-count pairs. Adding up (k − 1) across every run is the same as adding 1 for every adjacent equal pair anywhere in the string. The grand total is the minimum number of deletions.
Why this is optimal, not just correct. You can never do better than k − 1 deletions in a run of k equal characters — fewer would leave two equal neighbours somewhere. And you never need more, because deleting down to one survivor per run already makes the string alternating. So the collision count is simultaneously a lower bound and an achievable upper bound — it is the optimum.
Why no extra space. You never store the result string; you only compare each character with its predecessor and bump an integer. That's O(1) auxiliary space regardless of input size.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "AABAAB"Array state
i=1
'A' == 'A' → collision (delete 1)
2C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Reframing 'minimum deletions' as 'count of local collisions' removes the entire question of which character to delete. Whenever the cost is uniform and the constraint is purely between neighbours, counting violations is the optimum — you never simulate the fix.
ComplexityO(n) time, O(1) space — A single left-to-right scan touching each character once is O(n) time. Only an integer counter is stored → O(1) extra space.
E · Edge cases and pitfalls6s = "A" — Loop never runs (no index 1) → returns 0. Single characters are already alternating.Explore edge casesHide edge cases
Edge cases handled
s = "A"Loop never runs (no index 1) → returns 0. Single characters are already alternating.s = "AAAAA"Four collisions → 4 deletions, collapsing a run of 5 to a single A.s = "ABABAB"No two neighbours equal → 0 deletions.
Pitfalls
- Off-by-one: starting the loop at 0 and reading s[i-1] = s[-1] (Python wraps to the last char; Java throws).
- Trying to decide which character to delete — unnecessary; the count alone is the answer.
- Assuming the alphabet matters; the collision count works for any characters.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int alternatingCharacters(String s) { int deletions = 0; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == s.charAt(i - 1)) { deletions++;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Repeated-scan removal until stable
Removing one character per collision and rescanning the whole string each time is O(n²). A single pass already counts every forced deletion.
Explore the code comparisonHide the code comparison
Repeated-scan removal until stable
Removing one character per collision and rescanning the whole string each time is O(n²). A single pass already counts every forced deletion.
Don't write this
count = 0
while any(s[i] == s[i+1] for i in range(len(s)-1)):
for i in range(len(s)-1):
if s[i] == s[i+1]:
s = s[:i] + s[i+1:]
count += 1
break
return countWrite this instead
return sum(1 for i in range(1, len(s)) if s[i] == s[i-1])2Comparing to a stale 'previous' value
If you cache prev but forget to update it on a non-deletion, you compare against the wrong character and miscount.
Explore the code comparisonHide the code comparison
Comparing to a stale 'previous' value
If you cache prev but forget to update it on a non-deletion, you compare against the wrong character and miscount.
Don't write this
char prev = s.charAt(0);
int del = 0;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == prev) del++;
// forgot: prev = s.charAt(i);
}Write this instead
int del = 0;
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == s.charAt(i - 1)) del++;
}3Off-by-one on the left neighbour
Starting at i = 0 and reading s[i-1] reads index -1; in Python that silently wraps to the last character and corrupts the count.
Explore the code comparisonHide the code comparison
Off-by-one on the left neighbour
Starting at i = 0 and reading s[i-1] reads index -1; in Python that silently wraps to the last character and corrupts the count.
Don't write this
for i in range(len(s)):
if s[i] == s[i - 1]:
deletions += 1Write this instead
for i in range(1, len(s)):
if s[i] == s[i - 1]:
deletions += 1Revise
Carry the pattern into the interview
Pattern to remember
Single-pass adjacent-pair collision count
Code pattern to memorize
deletions = 0
for i in 1 .. n-1:
if s[i] == s[i-1]:
deletions += 1
return deletionsRules to remember
- 1The answer is exactly the number of indices
iwheres[i] == s[i-1]— no more, no less. - 2You don't choose which duplicate to delete; in a run of
kequal characters you must deletek − 1, and counting collisions counts those automatically. - 3Compare each character to its left neighbour, never to a fixed previous-kept variable you forgot to update.
- 4A single character or an already-alternating string yields
0— the loop simply never fires. - 5This is greedy and optimal in one pass; there is no need to rebuild the string.
Use this pattern when
- You must make a string 'alternating' or 'no two adjacent equal' with minimum edits
- Deletions only (not replacements) and the cost is uniform per deletion
- The decision at each position depends only on the immediate neighbour
- You can count collisions instead of simulating the removals
Choose another pattern when
- Replacements or insertions are allowed too → it becomes an edit-distance / DP problem
- You must output the resulting string, not just the count → still O(n) but you build output
- 'Alternating' means a strict A/B/A/B alphabet, not merely 'no two adjacent equal' → different rule
- Costs differ per character → greedy collision count no longer equals the optimum
Interview takeaway
When deletions are uniform and the constraint is purely local ('no two adjacent equal'), the minimum number of deletions equals the number of adjacent equal pairs. Counting collisions beats simulating removals — the same idea powers 'remove adjacent duplicates' and run-length compression problems.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternLocal-constraint collision counting. When deletions are uniform and the only rule is 'no two adjacent equal', the minimum number of deletions equals the number of adjacent equal pairs — count violations in one pass instead of simulating fixes. The same run-length insight underlies adjacent-duplicate removal and string compression.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Remove All Adjacent Duplicates In StringComing soonSame collision idea but you must output the reduced string — use a stack.
- Remove All Adjacent Duplicates in String IIComing soonCollapse runs of length k with a stack of (char, count) pairs.
- Minimum deletions for a strict A/B alternationComing soonIf 'alternating' means a fixed A/B/A/B template, compare to both templates and take the cheaper.
- String CompressionComing soonRun-length thinking again: count each maximal run instead of just collisions.