Skip to content

Latest commit

 

History

History

294

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

You are playing a Flip Game with your friend.

You are given a string currentState that contains only '+' and '-'. You and your friend take turns to flip two consecutive "++" into "--". The game ends when a person can no longer make a move, and therefore the other person will be the winner.

Return true if the starting player can guarantee a win, and false otherwise.

 

Example 1:

Input: currentState = "++++"
Output: true
Explanation: The starting player can guarantee a win by flipping the middle "++" to become "+--+".

Example 2:

Input: currentState = "+"
Output: false

 

Constraints:

  • 1 <= currentState.length <= 60
  • currentState[i] is either '+' or '-'.

 

Follow up: Derive your algorithm's runtime complexity.

Companies:
Google

Related Topics:
Math, Dynamic Programming, Backtracking, Memoization, Game Theory

Similar Questions:

Solution 1.

At first I tried finding some greedy solution but I failed. Then I stepped back to think about the brute force solution which would be DFS. On top of that we can reduce repetitive computation using Memoization, i.e. Top-down DP.

Time Complexity:

In the worst case, all the N characters are + signs. To get a intermediate state, we can flip some of the +s to -s. The first ++ to flip has N-1 options. The second ++ to flip at most has N-3 options. And so on. So in total there could be (N-1)(N-3)(N-5)... total states. Saving a state into the map takes O(N) time, so in total it's at most O(N!) time.

// OJ: https://leetcode.com/problems/flip-game-ii/
// Author: github.com/lzl124631x
// Time: O(N!)
// Space: O(N!)
class Solution {
public:
    bool canWin(string s) {
        bitset<60> bs;
        int cnt = 0;
        int N = s.size();
        for (int i = 0; i < N; ++i) {
            if (s[i] == '+') {
                bs.set(i);
                ++cnt;
            }
        }
        unordered_map<bitset<60>, bool> m;
        function<bool()> dp = [&]() {
            if (cnt == 0 || cnt == 1) return false;
            if (m.count(bs)) return m[bs];
            bool ans = false;
            for (int i = 0; i + 1 < N && !ans; ++i) {
                if (bs.test(i) && bs.test(i + 1)) {
                    bs.reset(i);
                    bs.reset(i + 1);
                    cnt -= 2;
                    if (!dp()) ans = true;
                    bs.set(i);
                    bs.set(i + 1);
                    cnt += 2;
                }
            }
            return m[bs] = ans;
        };
        return dp();
    }
};

Solution 2. Game Theory

// OJ: https://leetcode.com/problems/flip-game-ii/
// Author: github.com/lzl124631x
// Time: O(N^2)
// Space: O(N)
// Ref: https://leetcode.com/problems/flip-game-ii/discuss/73954
class Solution {
    int firstMissingNumber(unordered_set<int> &s) {
        int N = s.size();
        for (int i = 0; i < N; ++i) {
            if (s.count(i) == 0) return i;
        }
        return N;
    }
public:
    bool canWin(string s) {
        int curLen = 0, maxLen = 0, N = s.size();
        vector<int> init;
        for (int i = 0; i < N; ++i) {
            if (s[i] == '+') curLen++;
            if (i + 1 == N || s[i] == '-') {
                if (curLen >= 2) init.push_back(curLen);
                maxLen = max(maxLen, curLen);
                curLen = 0;
            }
        }
        vector<int> G(maxLen + 1);
        for (int len = 2; len <= maxLen; ++len) {
            unordered_set<int> sub;
            for (int lenFirstGame = 0; lenFirstGame < len / 2; ++lenFirstGame) {
                int lenSecondGame = len - lenFirstGame - 2;
                sub.insert(G[lenFirstGame] ^ G[lenSecondGame]);
            }
            G[len] = firstMissingNumber(sub);
        }
        int ans = 0;
        for (int n : init) ans ^= G[n];
        return ans != 0;
    }
};