Facebook Pixel

3227. Vowels Game in a String

MediumBrainteaserMathStringGame Theory
Leetcode Link

Problem Description

Alice and Bob are playing a turn-based game on a string s. The game follows these rules:

Game Rules:

  • Alice always goes first
  • On Alice's turn: She must remove any non-empty substring that contains an odd number of vowels
  • On Bob's turn: He must remove any non-empty substring that contains an even number of vowels
  • The first player who cannot make a valid move loses the game
  • Both players play optimally (make the best possible moves)

Vowels: The English vowels are a, e, i, o, and u.

Objective: Determine if Alice wins the game. Return true if Alice wins, and false if Bob wins.

Key Insight: The solution reveals that this is actually a brain teaser. Let's analyze based on the total number of vowels k in the string:

  1. If k = 0 (no vowels): Alice cannot remove any substring (since she needs odd vowels), so Bob wins immediately.

  2. If k is odd: Alice can remove the entire string in one move (since it contains an odd number of vowels), winning immediately.

  3. If k is even and greater than 0: Alice can strategically remove a substring containing k - 1 vowels (which is odd), leaving exactly 1 vowel. Bob then cannot make a move (1 is odd, not even), so Alice wins.

Conclusion: Alice wins if and only if the string contains at least one vowel. This explains why the solution simply checks if any vowel exists in the string.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

At first glance, this looks like a complex game theory problem where we need to analyze all possible moves and counter-moves. However, let's think about what each player can actually do based on the vowel count.

The key realization comes from examining Alice's advantage as the first player. Since Alice moves first and needs to remove substrings with odd vowel counts, we should consider what options she has based on the total vowel count in the string.

Let's think through the scenarios:

When there are no vowels: Alice is stuck immediately. She needs a substring with an odd number of vowels, but there are zero vowels total. She can't make any move, so Bob wins without playing.

When there's at least one vowel: This is where it gets interesting. Alice can be strategic about how many vowels she removes.

  • If the total count is odd, Alice can simply remove the entire string (odd vowels) and win instantly.
  • If the total count is even (say k vowels), Alice can cleverly remove k-1 vowels. Why k-1? Because k is even, so k-1 is odd - a valid move for Alice!

After Alice removes k-1 vowels, only 1 vowel remains. Now Bob is in trouble - he needs to remove a substring with an even number of vowels, but there's only 1 vowel left in the string. Since 1 is odd, Bob cannot make any valid move and loses.

This reveals the beautiful simplicity: Alice has a winning strategy whenever vowels exist. She can always force Bob into a position where he cannot move, either by removing all vowels at once (if odd total) or by leaving exactly one vowel (if even total).

Therefore, the problem reduces to a simple check: Does the string contain any vowel at all? If yes, Alice wins. If no, Bob wins.

Learn more about Math patterns.

Solution Approach

Based on our intuition that Alice wins if and only if there are vowels in the string, the implementation becomes remarkably simple.

Implementation Strategy:

  1. Define the vowels: Create a set containing all English vowels: {'a', 'e', 'i', 'o', 'u'}. Using a set gives us O(1) lookup time for checking if a character is a vowel.

  2. Check for any vowel: Iterate through the string and check if any character is a vowel. As soon as we find one vowel, we know Alice wins.

The Code:

class Solution:
    def doesAliceWin(self, s: str) -> bool:
        vowels = set("aeiou")
        return any(c in vowels for c in s)

Breaking down the implementation:

  • vowels = set("aeiou"): Creates a set of vowels for efficient O(1) membership checking.

  • any(c in vowels for c in s): This is a Python generator expression that:

    • Iterates through each character c in string s
    • Checks if c is in the vowels set
    • Returns True as soon as it finds the first vowel (short-circuits)
    • Returns False if no vowels are found after checking all characters

Time and Space Complexity:

  • Time Complexity: O(n) where n is the length of the string, as we potentially need to check each character once.
  • Space Complexity: O(1) as we only use a fixed-size set of 5 vowels regardless of input size.

The elegance of this solution lies in its simplicity - we've reduced what appears to be a complex game theory problem to a single line check: "Does the string contain at least one vowel?"

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a few examples to illustrate why checking for any vowel gives us the answer.

Example 1: s = "leetcode"

First, let's count the vowels: 'e', 'e', 'o', 'e' → 4 vowels total (even number)

Following the game logic:

  • Alice goes first and needs to remove a substring with odd vowels
  • Since there are 4 vowels (even), Alice can strategically remove a substring with 3 vowels
  • For instance, Alice removes "leetcod" (contains 3 vowels: 'e', 'e', 'o')
  • This leaves Bob with just "e" (1 vowel)
  • Bob needs to remove a substring with even vowels, but only 1 vowel remains (odd number)
  • Bob cannot make a valid move → Alice wins!

Using our solution: The string contains vowels ('e', 'o'), so we return true. ✓

Example 2: s = "bcdf"

Counting vowels: No vowels present (0 vowels)

Following the game logic:

  • Alice goes first and needs to remove a substring with odd vowels
  • There are 0 vowels total, so no substring can have odd vowels
  • Alice cannot make any move → Bob wins immediately!

Using our solution: The string contains no vowels, so we return false. ✓

Example 3: s = "aeiou"

Counting vowels: All 5 characters are vowels (odd number)

Following the game logic:

  • Alice goes first and needs to remove a substring with odd vowels
  • The entire string has 5 vowels (odd), so Alice removes the whole string "aeiou"
  • The string is now empty, Bob cannot make a move → Alice wins!

Using our solution: The string contains vowels, so we return true. ✓

The Pattern: Notice that in every case where vowels exist, Alice can manipulate how many vowels she removes to either win immediately (odd total) or leave Bob in an impossible position (leaving 1 vowel when starting with even total). This confirms our insight that Alice wins if and only if vowels exist in the string.

Solution Implementation

1class Solution:
2    def doesAliceWin(self, s: str) -> bool:
3        # Define the set of vowels for efficient lookup
4        vowels = {'a', 'e', 'i', 'o', 'u'}
5      
6        # Check if any character in the string is a vowel
7        # Returns True if at least one vowel exists, False otherwise
8        return any(char in vowels for char in s)
9
1class Solution {
2    /**
3     * Determines if Alice wins the game based on the presence of vowels in the string.
4     * Alice wins if there is at least one vowel in the string.
5     * 
6     * @param s the input string to check
7     * @return true if Alice wins (at least one vowel exists), false otherwise
8     */
9    public boolean doesAliceWin(String s) {
10        // Define vowels as a constant string
11        String vowels = "aeiou";
12      
13        // Iterate through each character in the input string
14        for (int i = 0; i < s.length(); i++) {
15            char currentChar = s.charAt(i);
16          
17            // Check if the current character is a vowel
18            if (vowels.indexOf(currentChar) != -1) {
19                // If a vowel is found, Alice wins
20                return true;
21            }
22        }
23      
24        // No vowels found, Alice does not win
25        return false;
26    }
27}
28
1class Solution {
2public:
3    bool doesAliceWin(string s) {
4        // Define the set of vowels to check against
5        const string VOWELS = "aeiou";
6      
7        // Iterate through each character in the input string
8        for (char c : s) {
9            // Check if the current character is a vowel
10            // find() returns npos if the character is not found
11            if (VOWELS.find(c) != string::npos) {
12                // If any vowel is found, Alice wins
13                return true;
14            }
15        }
16      
17        // If no vowels are found in the string, Alice doesn't win
18        return false;
19    }
20};
21
1/**
2 * Determines if Alice wins the game based on the presence of vowels in the string.
3 * Alice wins if there is at least one vowel in the string.
4 * 
5 * @param s - The input string to check for vowels
6 * @returns true if Alice wins (string contains at least one vowel), false otherwise
7 */
8function doesAliceWin(s: string): boolean {
9    // Define the set of vowels to check against
10    const VOWELS: string = 'aeiou';
11  
12    // Iterate through each character in the input string
13    for (const character of s) {
14        // Check if the current character is a vowel
15        if (VOWELS.includes(character)) {
16            // Alice wins if any vowel is found
17            return true;
18        }
19    }
20  
21    // No vowels found, Alice does not win
22    return false;
23}
24

Time and Space Complexity

The time complexity is O(n), where n is the length of the string s. In the worst case, the any() function with the generator expression needs to check every character in the string to determine if any vowel exists. Each character check against the vowel set is O(1), and we potentially check all n characters, resulting in O(n) time complexity.

The space complexity is O(1). The algorithm uses a fixed-size set containing 5 vowels ("aeiou"), which requires constant space regardless of the input size. The generator expression in any() doesn't create additional data structures proportional to the input size - it processes characters one at a time. Therefore, the space used remains constant.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Overthinking the Game Theory

Many developers initially approach this as a complex game theory problem requiring dynamic programming or recursive game state analysis. They might try to:

  • Track all possible moves for each player
  • Build a game tree to determine winning positions
  • Implement minimax algorithms

Why it's wrong: This leads to unnecessarily complex solutions that miss the elegant mathematical insight that the game's outcome depends solely on whether vowels exist.

2. Incorrectly Counting Vowels

Some might think they need to count the total number of vowels and apply complex logic based on whether that count is odd or even:

# Incorrect approach
def doesAliceWin(self, s: str) -> bool:
    vowels = {'a', 'e', 'i', 'o', 'u'}
    count = sum(1 for c in s if c in vowels)
    # Wrong logic: checking if count is odd
    return count % 2 == 1  # This is WRONG!

Why it's wrong: While the total count matters for understanding the game strategy, Alice wins with ANY positive number of vowels (not just odd counts). She can always force a win if at least one vowel exists.

3. Case Sensitivity Issues

Forgetting to handle uppercase vowels if the problem allows mixed case strings:

# Potential issue if input can have uppercase letters
def doesAliceWin(self, s: str) -> bool:
    vowels = {'a', 'e', 'i', 'o', 'u'}  # Missing uppercase
    return any(c in vowels for c in s)

Solution: If the problem allows uppercase letters, convert to lowercase or include both cases:

def doesAliceWin(self, s: str) -> bool:
    vowels = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'}
    return any(c in vowels for c in s)
    # OR
    vowels = {'a', 'e', 'i', 'o', 'u'}
    return any(c.lower() in vowels for c in s)

4. Using Inefficient Data Structures

Using a list instead of a set for vowel checking:

# Less efficient
def doesAliceWin(self, s: str) -> bool:
    vowels = ['a', 'e', 'i', 'o', 'u']  # O(5) lookup time
    return any(c in vowels for c in s)

Why it matters: While the difference is minimal with only 5 vowels, using a set provides O(1) lookup time and demonstrates better coding practices for similar problems with larger lookup sets.

5. Misunderstanding "Substring" Requirements

Some might think they need to validate that substrings are contiguous or implement substring extraction logic:

# Unnecessary complexity
def doesAliceWin(self, s: str) -> bool:
    # Trying to generate all possible substrings
    for i in range(len(s)):
        for j in range(i+1, len(s)+1):
            substring = s[i:j]
            # Complex logic here...

Why it's wrong: The game rules about substrings are already considered in the mathematical analysis. The final solution doesn't need to generate or validate substrings at all.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More