2038. Remove Colored Pieces if Both Neighbors are the Same Color
Problem Description
You have a line of n
colored pieces, where each piece is colored either 'A'
or 'B'
. The colors are represented by a string colors
of length n
, where colors[i]
indicates the color of the i
-th piece.
Alice and Bob play a game taking turns removing pieces from this line, with Alice going first.
Game Rules:
- Alice's moves: She can only remove a piece colored
'A'
if both of its neighbors are also colored'A'
. She cannot remove'B'
pieces. - Bob's moves: He can only remove a piece colored
'B'
if both of its neighbors are also colored'B'
. He cannot remove'A'
pieces. - Edge restriction: Neither player can remove pieces from the edges (first or last position) of the line.
- Winning condition: The first player who cannot make a valid move on their turn loses, and the other player wins.
Both players play optimally (making the best possible moves). Your task is to determine who wins the game - return true
if Alice wins, or false
if Bob wins.
Example interpretation: If we have "AAABABB"
, Alice can remove the middle 'A'
from the first group (since it has 'A'
neighbors on both sides), but Bob cannot remove any 'B'
initially since no 'B'
has two 'B'
neighbors. The game continues with players making optimal moves until one cannot move.
Intuition
The key insight is recognizing that once a player removes a piece, they don't create new opportunities for their opponent - they only potentially create more opportunities for themselves.
When Alice removes an 'A'
from a sequence like "AAAA"
, it becomes "A_AA"
(where _
represents the removed piece). This doesn't help Bob at all since Bob needs consecutive 'B'
s. However, it might help Alice in future turns if the gap gets filled or connected somehow.
More importantly, we need to count how many moves each player can potentially make. For a consecutive sequence of the same color, we can determine the number of removable pieces:
- In a sequence of 3 same-colored pieces: 1 piece can be removed (the middle one)
- In a sequence of 4 same-colored pieces: 2 pieces can be removed
- In a sequence of
k
same-colored pieces:k - 2
pieces can be removed (since edge pieces cannot be removed)
The crucial realization is that the game is essentially a race - whoever runs out of moves first loses. Since the players can only remove their own color and removing pieces doesn't create opportunities for the opponent, we can simply count the total number of possible moves for each player at the start of the game.
For each group of consecutive 'A'
s of length k
where k ≥ 3
, Alice can make k - 2
moves from that group. Similarly, for each group of consecutive 'B'
s of length k
where k ≥ 3
, Bob can make k - 2
moves.
Since Alice goes first and both play optimally, Alice wins if and only if she has more total available moves than Bob. If Alice has a
total moves and Bob has b
total moves, Alice wins when a > b
.
Solution Approach
The solution uses a grouping technique to identify consecutive sequences of the same color and count the available moves for each player.
Implementation Steps:
-
Group consecutive colors: We use
groupby()
function to group consecutive identical characters in the string. This gives us groups like['A', 'A', 'A']
or['B', 'B']
. -
Count available moves for each group: For each group:
- Convert the group to a list and get its length
- Calculate the number of removable pieces as
length - 2
- Only count groups where
length - 2 > 0
(groups of 3 or more pieces)
-
Accumulate moves by player:
- If the group color is
'A'
and has 3+ pieces, addlength - 2
to Alice's move counta
- If the group color is
'B'
and has 3+ pieces, addlength - 2
to Bob's move countb
- If the group color is
-
Determine the winner: Compare the total moves:
- Return
true
ifa > b
(Alice has more moves and wins) - Return
false
otherwise (Bob has equal or more moves and wins)
- Return
Example walkthrough:
For colors = "AAABABB"
:
- Groups:
['AAA']
,['B']
,['A']
,['BB']
'AAA'
: length 3, Alice gets 3-2 = 1 move'B'
: length 1, no moves (less than 3)'A'
: length 1, no moves (less than 3)'BB'
: length 2, no moves (less than 3)- Result:
a = 1
,b = 0
, soa > b
returnstrue
(Alice wins)
The algorithm runs in O(n)
time where n
is the length of the string, as we iterate through the string once to form groups and count moves. The space complexity is O(1)
as we only store the move counts.
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 solution with colors = "AABBBBA"
:
Step 1: Identify consecutive groups
- Group 1:
"AA"
(2 A's) - Group 2:
"BBBB"
(4 B's) - Group 3:
"A"
(1 A)
Step 2: Calculate available moves for each group
- Group 1 (
"AA"
): Length = 2, moves = 2 - 2 = 0 (too short, need at least 3) - Group 2 (
"BBBB"
): Length = 4, moves = 4 - 2 = 2 (Bob can remove 2 pieces) - Group 3 (
"A"
): Length = 1, moves = 1 - 2 = -1 (too short, no moves)
Step 3: Count total moves per player
- Alice's total moves (from A groups): 0
- Bob's total moves (from B groups): 2
Step 4: Determine winner
- Compare: Alice has 0 moves, Bob has 2 moves
- Since 0 is not greater than 2, return
false
(Bob wins)
Why Bob wins: Bob can make 2 moves from the "BBBB"
group. He could remove the piece at index 3 (turning "BBBB"
into "BB_B"
), and later remove the piece at index 2 (if positions allow). Alice cannot make any moves since she has no group of 3 or more consecutive A's. Since Alice goes first but has no valid moves, she loses immediately.
Solution Implementation
1from itertools import groupby
2
3class Solution:
4 def winnerOfGame(self, colors: str) -> bool:
5 """
6 Determines the winner of a game where Alice removes 'A' and Bob removes 'B'.
7 Players can only remove their letter if it's between two of the same letter.
8
9 Args:
10 colors: A string consisting of 'A' and 'B' characters
11
12 Returns:
13 True if Alice wins (has more moves), False otherwise
14 """
15 # Initialize counters for valid moves for Alice and Bob
16 alice_moves = 0
17 bob_moves = 0
18
19 # Group consecutive identical characters together
20 for character, group in groupby(colors):
21 # Calculate possible moves from this group (length - 2)
22 # We subtract 2 because we can't remove edge characters
23 group_length = len(list(group))
24 possible_moves = group_length - 2
25
26 # Add valid moves to respective player's counter
27 if possible_moves > 0:
28 if character == 'A':
29 alice_moves += possible_moves
30 elif character == 'B':
31 bob_moves += possible_moves
32
33 # Alice wins if she has more valid moves than Bob
34 return alice_moves > bob_moves
35
1class Solution {
2 public boolean winnerOfGame(String colors) {
3 int stringLength = colors.length();
4 int aliceMovableCount = 0; // Count of 'A's that Alice can remove
5 int bobMovableCount = 0; // Count of 'B's that Bob can remove
6
7 // Iterate through the string to find consecutive segments of same color
8 int segmentStart = 0;
9 while (segmentStart < stringLength) {
10 // Find the end of current consecutive segment
11 int segmentEnd = segmentStart;
12 while (segmentEnd < stringLength &&
13 colors.charAt(segmentEnd) == colors.charAt(segmentStart)) {
14 segmentEnd++;
15 }
16
17 // Calculate the number of removable pieces in this segment
18 // For a segment of length L, we can remove (L - 2) pieces
19 // Example: "AAA" has 1 removable A, "AAAA" has 2 removable As
20 int segmentLength = segmentEnd - segmentStart;
21 int removablePieces = segmentLength - 2;
22
23 // Only count if there are removable pieces (segment length >= 3)
24 if (removablePieces > 0) {
25 if (colors.charAt(segmentStart) == 'A') {
26 aliceMovableCount += removablePieces;
27 } else {
28 bobMovableCount += removablePieces;
29 }
30 }
31
32 // Move to the next segment
33 segmentStart = segmentEnd;
34 }
35
36 // Alice wins if she has more moves available than Bob
37 // Since Alice goes first, she needs strictly more moves to win
38 return aliceMovableCount > bobMovableCount;
39 }
40}
41
1class Solution {
2public:
3 bool winnerOfGame(string colors) {
4 int length = colors.size();
5 int aliceMoves = 0; // Count of possible moves for Alice (removing 'A')
6 int bobMoves = 0; // Count of possible moves for Bob (removing 'B')
7
8 // Iterate through the string to find consecutive segments of same color
9 for (int start = 0, end = 0; start < length; start = end) {
10 // Find the end of current consecutive segment
11 while (end < length && colors[end] == colors[start]) {
12 ++end;
13 }
14
15 // Calculate possible moves in this segment
16 // A player can remove (segmentLength - 2) pieces from a segment
17 // since they need to keep at least one piece on each end
18 int segmentLength = end - start;
19 int possibleMoves = segmentLength - 2;
20
21 // Only count if there are valid moves (segment length >= 3)
22 if (possibleMoves > 0) {
23 if (colors[start] == 'A') {
24 aliceMoves += possibleMoves;
25 } else {
26 bobMoves += possibleMoves;
27 }
28 }
29 }
30
31 // Alice wins if she has more possible moves than Bob
32 // since Alice goes first
33 return aliceMoves > bobMoves;
34 }
35};
36
1/**
2 * Determines the winner of a game based on consecutive color sequences.
3 * Players can remove pieces of their color ('A' or 'B') that have at least
4 * one piece of the same color on both sides.
5 *
6 * @param colors - A string consisting of 'A' and 'B' characters representing colored pieces
7 * @returns true if player A wins (can make more moves), false if player B wins
8 */
9function winnerOfGame(colors: string): boolean {
10 const length: number = colors.length;
11
12 // Track the number of valid moves for each player
13 let aliceMoves: number = 0;
14 let bobMoves: number = 0;
15
16 // Iterate through consecutive segments of the same color
17 let segmentStart: number = 0;
18 let segmentEnd: number = 0;
19
20 while (segmentStart < length) {
21 // Find the end of the current color segment
22 while (segmentEnd < length && colors[segmentEnd] === colors[segmentStart]) {
23 segmentEnd++;
24 }
25
26 // Calculate the number of removable pieces in this segment
27 // A piece is removable if it has neighbors of the same color
28 // So in a segment of length L, there are (L - 2) removable pieces
29 const removablePieces: number = segmentEnd - segmentStart - 2;
30
31 // Only count segments with at least 3 consecutive pieces
32 if (removablePieces > 0) {
33 // Add removable pieces to the respective player's count
34 if (colors[segmentStart] === 'A') {
35 aliceMoves += removablePieces;
36 } else {
37 bobMoves += removablePieces;
38 }
39 }
40
41 // Move to the next segment
42 segmentStart = segmentEnd;
43 }
44
45 // Alice wins if she can make more moves than Bob
46 return aliceMoves > bobMoves;
47}
48
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string colors
. The groupby
function iterates through the entire string once to create groups of consecutive identical characters. For each group, we convert the iterator v
to a list using list(v)
, which takes time proportional to the size of that group. Since the sum of all group sizes equals n
, the total time across all groups is O(n)
.
The space complexity is O(n)
rather than O(1)
. While variables a
and b
use constant space, the critical issue is the list(v)
operation inside the loop. When we convert the iterator v
to a list, we create a list that can be as large as n
in the worst case (when all characters in the string are the same). Therefore, the space complexity is O(n)
, not O(1)
as stated in the reference answer.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Consuming the Iterator Multiple Times
A critical pitfall in the provided code is calling len(list(group))
on the groupby iterator. The group
object from groupby()
is an iterator that can only be consumed once. After converting it to a list to get the length, the iterator is exhausted and cannot be reused.
Why this happens: groupby()
returns an iterator of (key, group) pairs where group
is also an iterator. Once you consume it (like converting to a list), it's empty.
Solution: Store the list conversion result in a variable:
for character, group in groupby(colors):
group_list = list(group) # Convert once and store
group_length = len(group_list)
possible_moves = group_length - 2
# ... rest of the code
2. Misunderstanding the Game Rules
A common misconception is thinking that once a player removes a piece, the neighboring pieces immediately become new neighbors. This could lead to incorrect move counting by trying to dynamically recalculate available moves after each removal.
Why this is wrong: The problem asks for the total number of moves each player can make, not a simulation of the actual game. The key insight is that removing a piece from a group of size n
still leaves n-3
pieces that can be removed (if any remain).
Solution: Count all possible moves at once for each group without simulating the actual game:
# Correct: Count all moves for a group at once
possible_moves = max(0, group_length - 2)
# Incorrect: Trying to simulate piece-by-piece removal
# This overcomplicates and likely miscounts
3. Off-by-One Errors in Move Calculation
It's easy to make mistakes when calculating the number of removable pieces. Some might think it's length - 1
or just length
for groups of 3 or more.
Why length - 2
is correct: In a group of n
identical characters, the first and last cannot be removed (edge restriction). So only the middle n - 2
pieces are removable.
Solution: Always remember the formula: removable_pieces = max(0, length - 2)
4. Not Handling Edge Cases Properly
Forgetting to check if possible_moves > 0
before adding to the counters could lead to negative move counts for small groups.
Example problem case:
- Group "AA" has length 2, so
2 - 2 = 0
moves (correct) - Group "A" has length 1, so
1 - 2 = -1
moves (incorrect if added)
Solution: Always validate before adding:
if possible_moves > 0: if character == 'A': alice_moves += possible_moves # ...
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!