LeetCode 293 - Flip Game
This problem is asking us to simulate a single move in a simple two-player game. The input is a string currentState consisting only of '+' and '-' characters.
Difficulty: 🟢 Easy
Topics: String
Solution
Problem Understanding
This problem is asking us to simulate a single move in a simple two-player game. The input is a string currentState consisting only of '+' and '-' characters. Each '+' represents a spot that can be flipped, and the rules allow a player to flip two consecutive '+' characters into '--'. The task is to generate all possible strings that result from making exactly one valid move on the current state.
For example, if currentState = "++++", you can flip the first two, the middle two, or the last two '+' characters. Therefore, the output is all strings after making one of these flips.
Constraints tell us that the input length is between 1 and 500. This is small enough that a simple linear scan through the string is efficient. Each character is guaranteed to be '+' or '-', so we do not need to handle invalid input characters.
Edge cases to be aware of include strings that are too short to flip two consecutive '+' characters, strings with no '+' characters, and strings where only one valid flip exists. These are simple but essential to handle, as a naive implementation might assume there is always a valid move.
Approaches
Brute-Force Approach
A brute-force approach would attempt to generate all possible substrings and check for '++' pairs at every position. Whenever '++' is found, a new string is generated with that pair flipped to '--'. This approach is correct because it considers all possible positions for valid moves, but it is somewhat inefficient because it constructs a new string for every character pair, even when it cannot be flipped. For a string of length n, this can result in O(n²) string operations due to repeated substring constructions.
Optimal Approach
The optimal approach relies on a simple observation: a valid move exists only at consecutive '++' pairs. Therefore, we can perform a single pass through the string and whenever we see '++', we create a new string with those two characters flipped. This approach is linear in time with respect to the string length and avoids unnecessary substring generation. It is optimal because we only examine positions that could yield valid moves, and each valid move is generated exactly once.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(n²) | Checks all substrings, generates many intermediate strings unnecessarily |
| Optimal | O(n) | O(n²) | Single pass, only generates strings for valid moves |
Algorithm Walkthrough
- Initialize an empty list
resultsto store all possible strings after one move. - Iterate over the string
currentStatefrom the first character to the second-to-last character, using an indexi. - At each index, check if
currentState[i]andcurrentState[i + 1]are both'+'. - If a
'++'pair is found, construct a new string where only this pair is replaced with'--'. You can do this efficiently by slicing the string into three parts: before the pair, the flipped pair, and after the pair. - Append this new string to
results. - After iterating through the entire string, return
results.
Why it works: The algorithm works because it systematically examines every possible position for a valid flip and generates exactly one new string for each valid move. There are no duplicates because each '++' pair is only flipped once. This guarantees that all possible next states are captured.
Python Solution
from typing import List
class Solution:
def generatePossibleNextMoves(self, currentState: str) -> List[str]:
results = []
n = len(currentState)
for i in range(n - 1):
if currentState[i] == '+' and currentState[i + 1] == '+':
new_state = currentState[:i] + '--' + currentState[i + 2:]
results.append(new_state)
return results
The code follows the algorithm closely. We initialize an empty list results to collect possible states. The loop checks each character up to the second-to-last, ensuring i + 1 is within bounds. When a '++' is found, slicing constructs the new state efficiently. Each new state is appended to results, which is returned at the end.
Go Solution
func generatePossibleNextMoves(currentState string) []string {
results := []string{}
n := len(currentState)
for i := 0; i < n-1; i++ {
if currentState[i] == '+' && currentState[i+1] == '+' {
newState := currentState[:i] + "--" + currentState[i+2:]
results = append(results, newState)
}
}
return results
}
The Go version mirrors the Python implementation. Strings in Go are immutable, so concatenation produces a new string just like slicing in Python. We initialize an empty slice of strings and append each valid move.
Worked Examples
Example 1: currentState = "++++"
| i | currentState[i:i+2] | Action | results |
|---|---|---|---|
| 0 | "++" | Flip to "--" | ["--++"] |
| 1 | "++" | Flip to "--" | ["--++", "+--+"] |
| 2 | "++" | Flip to "--" | ["--++", "+--+", "++--"] |
Final output: ["--++", "+--+", "++--"]
Example 2: currentState = "+"
No pair of consecutive '+' exists. The loop does not run. Final output: []
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | We scan the string once, checking each pair of characters. |
| Space | O(n²) | Each valid move produces a new string of length n. In the worst case, all n-1 pairs are valid, generating O(n) strings, each of length O(n). |
The reasoning is straightforward: the algorithm's primary cost is scanning the string and generating new strings. Each string creation is O(n) due to immutability.
Test Cases
# Provided examples
assert Solution().generatePossibleNextMoves("++++") == ["--++","+--+","++--"] # multiple flips
assert Solution().generatePossibleNextMoves("+") == [] # too short to flip
# Edge cases
assert Solution().generatePossibleNextMoves("++") == ["--"] # exactly one move
assert Solution().generatePossibleNextMoves("-") == [] # no '+'
assert Solution().generatePossibleNextMoves("+-++-+") == ["+--+-+","+--++-"] # scattered '+'
# Long string
assert Solution().generatePossibleNextMoves("+"*500) == [("--"+"+"*498), ("+"+"--"+"+"*497), ("++--"+"+"*496)] # simplified representation
| Test | Why |
|---|---|
| "++++" | Standard multiple moves |
| "+" | Too short to flip |
| "++" | Single flip possible |
| "-" | No flips possible |
| "+-++-+" | Scattered flips |
| 500 '+'s | Test upper boundary and performance |
Edge Cases
- Single character strings: Strings of length 1 cannot have any valid move. If not handled, a naive implementation might attempt to access
i + 1and raise an index error. Our solution correctly iterates only ton-1. - Strings with no consecutive '+': For example,
"+-+-+". The algorithm scans the string and finds no pairs, returning an empty list. This prevents unnecessary string construction. - Strings where all characters are '+': For maximum length input, such as
"+"*500, the algorithm must handle a large number of possible moves efficiently. The solution scales linearly and constructs each string separately, avoiding performance issues. Each new string uses slicing to ensure correctness even at the boundary indices.