293. Flip Game 🔒
Problem Description
You're playing a Flip Game where you start with a string containing only '+'
and '-'
characters. In this game, players take turns flipping two consecutive "++"
characters into "--"
. The game continues until no more moves are possible, and the player who cannot make a move loses.
Your task is to find all possible states of the string after making one valid move from the current state. A valid move consists of finding two consecutive '+'
characters and flipping them both to '-'
.
For example:
- If
currentState = "+++++"
, you can flip positions (0,1), (1,2), (2,3), or (3,4), resulting in["--+++", "+--++", "++--+", "+++--"]
- If
currentState = "---"
, no moves are possible, so return[]
The solution works by:
- Converting the string to a list for easier manipulation
- Iterating through consecutive pairs of characters using
pairwise()
- When finding two consecutive
'+'
characters:- Temporarily change them to
'-'
- Add the resulting string to the answer list
- Change them back to
'+'
to continue checking other positions
- Temporarily change them to
- Returning all possible next states
The time complexity is O(n²)
where n
is the length of the string (iterating through pairs takes O(n)
and creating each new string takes O(n)
). The space complexity is O(n)
for storing the character list.
Intuition
The key insight is that we need to find all possible outcomes after making exactly one move. Since a move involves flipping two consecutive "++"
to "--"
, we need to identify all positions where this pattern exists.
Think of it like scanning through the string with a sliding window of size 2. At each position, we check if both characters in our window are '+'
. If they are, that represents one valid move we can make.
The natural approach is to:
- Check every consecutive pair of characters
- When we find a
"++"
pattern, we know we can make a move there - Generate the new state by flipping those specific characters
- Continue checking the rest of the string for other possible moves
Why do we need to flip back after recording each state? Because we want to find ALL possible moves from the original state, not from a modified state. If we didn't flip back, we'd miss other valid positions. For example, in "++++"
, after flipping position (0,1) to get "--++"
, if we didn't restore it back to "++++"
, we'd miss the opportunity to find the move at position (1,2).
The use of pairwise()
is a clean way to iterate through consecutive pairs without manually managing indices like (i, i+1)
. Converting to a list makes the string mutable, allowing us to efficiently modify and restore characters without creating new strings each time we want to test a position.
This exhaustive search guarantees we find all possible next states, which is exactly what the problem asks for.
Solution Approach
The solution uses a traversal and simulation approach to generate all possible next states.
Step-by-step implementation:
-
Convert string to list: We first convert the input string
currentState
to a lists
because strings in Python are immutable. This allows us to modify characters in-place without creating new strings repeatedly.s = list(currentState)
-
Initialize result list: Create an empty list
ans
to store all possible next states.ans = []
-
Traverse consecutive pairs: Use Python's
pairwise()
function to iterate through consecutive pairs of characters. Theenumerate()
gives us the indexi
of the first character in each pair.for i, (a, b) in enumerate(pairwise(s)):
-
Check for valid move: For each pair
(a, b)
, check if both characters are'+'
. This represents a valid position where we can make a move.if a == b == "+":
-
Simulate the move: When we find a valid position:
- Temporarily flip the two
'+'
characters to'-'
- Create the new state string and add it to our result list
- Restore the characters back to
'+'
for the next iteration
s[i] = s[i + 1] = "-" # Make the move ans.append("".join(s)) # Record the new state s[i] = s[i + 1] = "+" # Restore for next iteration
- Temporarily flip the two
-
Return all states: After checking all consecutive pairs, return the list containing all possible next states.
return ans
The algorithm efficiently explores all positions in a single pass through the string, making temporary modifications and restoring them to ensure we capture every possible move from the original state. The pattern of "modify-record-restore" ensures we don't miss any valid moves while keeping the implementation clean and straightforward.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with currentState = "+++-++"
:
Initial Setup:
- Convert to list:
s = ['+', '+', '+', '-', '+', '+']
- Initialize empty result:
ans = []
Iteration Process:
Step 1: Check position (0,1): ('+', '+')
- Both are '+', so this is a valid move
- Flip:
s = ['-', '-', '+', '-', '+', '+']
- Add to result:
ans = ["--+-++"]
- Restore:
s = ['+', '+', '+', '-', '+', '+']
Step 2: Check position (1,2): ('+', '+')
- Both are '+', so this is a valid move
- Flip:
s = ['+', '-', '-', '-', '+', '+']
- Add to result:
ans = ["--+-++", "+---++"]
- Restore:
s = ['+', '+', '+', '-', '+', '+']
Step 3: Check position (2,3): ('+', '-')
- Not both '+', skip this position
Step 4: Check position (3,4): ('-', '+')
- Not both '+', skip this position
Step 5: Check position (4,5): ('+', '+')
- Both are '+', so this is a valid move
- Flip:
s = ['+', '+', '+', '-', '-', '-']
- Add to result:
ans = ["--+-++", "+---++", "+++---"]
- Restore:
s = ['+', '+', '+', '-', '+', '+']
Final Result: ["--+-++", "+---++", "+++---"]
The algorithm found all three possible moves: flipping positions (0,1), (1,2), and (4,5). Notice how after each flip operation, we restore the original state before checking the next position. This ensures we're always making moves from the original "+++-++"
state, not from a modified version.
Solution Implementation
1from typing import List
2
3class Solution:
4 def generatePossibleNextMoves(self, currentState: str) -> List[str]:
5 """
6 Generate all possible next moves by flipping consecutive "++" to "--".
7
8 Args:
9 currentState: A string containing only '+' and '-' characters
10
11 Returns:
12 List of all possible next states after one valid move
13 """
14 # Convert string to list for easier manipulation
15 state_list = list(currentState)
16 result = []
17
18 # Iterate through consecutive pairs of characters
19 for i in range(len(state_list) - 1):
20 # Check if current pair is "++"
21 if state_list[i] == '+' and state_list[i + 1] == '+':
22 # Flip "++" to "--"
23 state_list[i] = '-'
24 state_list[i + 1] = '-'
25
26 # Add the new state to result
27 result.append(''.join(state_list))
28
29 # Restore original state for next iteration
30 state_list[i] = '+'
31 state_list[i + 1] = '+'
32
33 return result
34
1class Solution {
2 /**
3 * Generates all possible next moves for the Flip Game.
4 * In each move, two consecutive '+' characters can be flipped to '--'.
5 *
6 * @param currentState The current state of the game as a string of '+' and '-' characters
7 * @return A list of all possible next states after making one valid move
8 */
9 public List<String> generatePossibleNextMoves(String currentState) {
10 // Initialize result list to store all possible next states
11 List<String> possibleMoves = new ArrayList<>();
12
13 // Convert string to character array for easier manipulation
14 char[] stateArray = currentState.toCharArray();
15
16 // Iterate through the array to find consecutive '++' pairs
17 for (int i = 0; i < stateArray.length - 1; i++) {
18 // Check if current and next characters form a valid '++' pair
19 if (stateArray[i] == '+' && stateArray[i + 1] == '+') {
20 // Flip the consecutive '+' characters to '-'
21 stateArray[i] = '-';
22 stateArray[i + 1] = '-';
23
24 // Add the new state to the result list
25 possibleMoves.add(new String(stateArray));
26
27 // Restore the original state for next iteration
28 stateArray[i] = '+';
29 stateArray[i + 1] = '+';
30 }
31 }
32
33 return possibleMoves;
34 }
35}
36
1class Solution {
2public:
3 vector<string> generatePossibleNextMoves(string s) {
4 // Vector to store all possible next move states
5 vector<string> result;
6
7 // Iterate through the string, checking consecutive pairs
8 // Stop at size()-1 to avoid out of bounds when checking i+1
9 for (int i = 0; i < s.size() - 1; ++i) {
10 // Check if current and next characters form a valid flip pair "++"
11 if (s[i] == '+' && s[i + 1] == '+') {
12 // Flip the consecutive "++" to "--"
13 s[i] = '-';
14 s[i + 1] = '-';
15
16 // Add the modified string to the result vector
17 result.emplace_back(s);
18
19 // Restore the original state for next iteration
20 s[i] = '+';
21 s[i + 1] = '+';
22 }
23 }
24
25 // Return all possible next moves
26 return result;
27 }
28};
29
1/**
2 * Generates all possible next moves in a Flip Game
3 * where consecutive "++" can be flipped to "--"
4 *
5 * @param currentState - The current game state string containing '+' and '-' characters
6 * @returns An array of all possible next states after making one valid move
7 */
8function generatePossibleNextMoves(currentState: string): string[] {
9 // Convert the string to an array of characters for easier manipulation
10 const stateArray: string[] = currentState.split('');
11
12 // Store all possible next moves
13 const possibleMoves: string[] = [];
14
15 // Iterate through the string to find consecutive "++" pairs
16 for (let i = 0; i < stateArray.length - 1; i++) {
17 // Check if current and next characters form a "++" pair
18 if (stateArray[i] === '+' && stateArray[i + 1] === '+') {
19 // Flip the "++" pair to "--"
20 stateArray[i] = '-';
21 stateArray[i + 1] = '-';
22
23 // Add the new state to possible moves
24 possibleMoves.push(stateArray.join(''));
25
26 // Restore the original "++" pair for next iteration
27 stateArray[i] = '+';
28 stateArray[i + 1] = '+';
29 }
30 }
31
32 return possibleMoves;
33}
34
Time and Space Complexity
Time Complexity: O(n^2)
, where n
is the length of the string.
The analysis breaks down as follows:
- The
pairwise
function iterates through the string once, creatingn-1
pairs inO(n)
time - For each valid pair of consecutive
"++"
, we perform:- Two character assignments to change
"++"
to"--"
:O(1)
- Join operation on the list to create a string:
O(n)
- Append to result list:
O(1)
- Two character assignments to restore
"++"
:O(1)
- Two character assignments to change
- In the worst case, all
n-1
pairs could be"++"
, resulting in(n-1) * O(n) = O(n^2)
Space Complexity: O(n)
(excluding the output array)
The space analysis:
- Converting the input string to a list:
O(n)
- The
pairwise
iterator uses constant extra space:O(1)
- Temporary string creation during
join
operation:O(n)
- If we include the output array, it would be
O(n^2)
in the worst case (up ton-1
strings, each of lengthn
)
The reference mentions O(1)
space complexity as an alternative, which would apply if the implementation modified the input string directly without creating a copy, though this isn't possible with Python's immutable strings without the list conversion.
Common Pitfalls
1. Modifying the Original String Without Restoration
A common mistake is forgetting to restore the characters back to their original state after recording each move. This would cause subsequent iterations to work with a modified string rather than the original state.
Incorrect approach:
def generatePossibleNextMoves(self, currentState: str) -> List[str]:
state_list = list(currentState)
result = []
for i in range(len(state_list) - 1):
if state_list[i] == '+' and state_list[i + 1] == '+':
state_list[i] = '-'
state_list[i + 1] = '-'
result.append(''.join(state_list))
# MISSING: Restoration of original characters!
return result
This would only find the first valid move and then work with an already modified string, missing other possible moves from the original state.
2. Creating Multiple String Copies Inefficiently
Another pitfall is creating a new copy of the string for each potential move, which is inefficient:
Inefficient approach:
def generatePossibleNextMoves(self, currentState: str) -> List[str]:
result = []
for i in range(len(currentState) - 1):
if currentState[i:i+2] == '++':
# Creating a new string copy for each move
new_state = currentState[:i] + '--' + currentState[i+2:]
result.append(new_state)
return result
While this works correctly, it creates multiple substring operations and concatenations, which can be less efficient than the in-place modification approach.
3. Off-by-One Error in Loop Range
Using range(len(state_list))
instead of range(len(state_list) - 1)
would cause an index out of bounds error:
Incorrect range:
for i in range(len(state_list)): # Wrong! Goes up to last index
if state_list[i] == '+' and state_list[i + 1] == '+': # i+1 out of bounds!
4. Not Handling Edge Cases
Forgetting to handle edge cases like empty strings or single-character strings:
Solution with proper edge case handling:
def generatePossibleNextMoves(self, currentState: str) -> List[str]:
# Handle edge cases
if len(currentState) < 2:
return []
state_list = list(currentState)
result = []
for i in range(len(state_list) - 1):
if state_list[i] == '+' and state_list[i + 1] == '+':
state_list[i] = state_list[i + 1] = '-'
result.append(''.join(state_list))
state_list[i] = state_list[i + 1] = '+'
return result
Which of the following is a good use case for backtracking?
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!