LeetCode 294 - Flip Game II


Related:
LeetCode 293 - Flip Game
LeetCode 294 - Flip Game II
[LeetCode] Flip Game II - jcliBlogger - 博客园
You are playing the following Flip Game with your friend: Given a string that contains only these two characters: + 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.
Write a function to determine if the starting player can guarantee a win.
Example:
Input: s = "++++"
Output: true 
Explanation: The starting player can guarantee a win by flipping the middle "++" to become "+--+".
Follow up:
Derive your algorithm's runtime complexity

X. DFS + Cache
https://discuss.leetcode.com/topic/27250/share-my-java-backtracking-solution/10
https://discuss.leetcode.com/topic/27351/java-backtracking-solution-with-time-optimization-through-dp-205ms-19ms
If we use HashMap to memorize both win string & lose string, we can further reduce time from 208ms to 18ms
public boolean canWin(String s) {
    if(s == null || s.length() < 2) return false;
    Map<String, Boolean> map = new HashMap<>();
    return canWin(s, map);
}

public boolean canWin(String s, Map<String, Boolean> map){
    if(map.containsKey(s)) return map.get(s);
    for(int i = 0; i < s.length() - 1; i++) {
        if(s.charAt(i) == '+' && s.charAt(i + 1) == '+') {
            String opponent = s.substring(0, i) + "--" + s.substring(i + 2);
            if(!canWin(opponent, map)) {
                map.put(s, true);
                return true;
            }
        }
    }
    map.put(s, false);
    return false;
}
https://discuss.leetcode.com/topic/34617/simple-java-solution-with-time-complexity-analysis-exponential-vs-factorial
T(N): total ways to flip the string of length N. Assume the original string is "+++++++++". Move the "--" from left to write you will get: "--+++++++" (T(N - 2)), "+--++++++" (T(N - 3)), "++--+++++"(T(2) * T(N - 4)), "+++--++++"(T(3) * T(N - 5)), .......,"++++++--+"(T(N - 3)), "+++++++--" T(N - 2)
When a string separated by "--", e.g., "+++--++++", the time complexity is T(3) * T(N - 5), not T(N - 2). Note, T(3)*T(N - 5) < T(N - 2). So, the correct equation should be (we need to test every flip strategy)
T(N) = T(N-2) + T(N-3) + (T(2) * T(N-4)) + (T(3) * T(N-5)) + ... (T(N-5) * T(3)) + (T(N-4) * T(2)) + T(N-3) + T(N-2)
        public boolean canWin(String s) {
            char[] list = s.toCharArray();
            return helper(list);
        }
        
        private boolean helper(char[] list) {
            for (int i = 0; i < list.length - 1; i++) {
                if (list[i] == '-' || list[i + 1] == '-') continue;
                list[i] = '-';
                list[i + 1] = '-';
                
                boolean otherWin = helper(list);
                //need to go back to the original state before return
                list[i] = '+';
                list[i + 1] = '+';
                
                if (!otherWin) return true;
            }
            
            return false;
        }
https://leetcode.com/discuss/64350/short-java-%26-ruby?show=64350#q64350
You win if and only if you can make some move so that the resulting state can't be won. One thing I do differently from everyone else so far is that I replace "++" with "-" instead of "--".
public boolean canWin(String s) { for (int i=-1; (i = s.indexOf("++", i+1)) >= 0; ) if (!canWin(s.substring(0, i) + "-" + s.substring(i+2))) return true; return false; }
Both the library-searching and the "-"-replacement seem to speed it up significantly, here are results of submitting different similar solutions five times each:
Average 153 ms: Mine with "-" (150, 156, 152, 149 and 156)
Average 169 ms: Mine with "--" (184, 169, 156, 162 and 174)
Average 209 ms: easonhu's (214, 204, 207, 207 and 213)
Average 221 ms: jeantimex's (168, 196, 202, 194 and 345) (without the 345 the average is 190 ms)
Stefan, you can really speed it up by avoiding building string objects. The usage of chars[] for the backtracking can go down to 27ms. Try this one:
public boolean canWin(String s) {
    if (s == null) return false;    
    return canWin(s.toCharArray());
}
private boolean canWin(char[] chars) {
   for (int i = 0; i < chars.length - 1; i++) {
        if (chars[i] == '+' && chars[i+1] == '+') {
            chars[i] = chars[i+1] = '-';
            boolean win = !canWin(chars);
            chars[i] = chars[i+1] = '+'; //backtrack.
            if (win)
                return true;
        }
    }
    return false;
}
Using StringBuilder
http://www.chenguanghe.com/flip-game-ii/
这个backtracking很好想, 就是假设call这个function的人是先手, 然后尝试所有的翻转, 如果不能翻就说明先手的人不能保证必胜.

3
4
5
6
public boolean canWin(String s) {
    for (int i = 0; i < s.length() - 1; ++i)
        if (s.charAt(i) == '+' && s.charAt(i + 1) == '+' && !canWin(s.substring(0, i) + "--" + s.substring(i + 2)))
                return true;
    return false;
}
http://www.meetqun.com/thread-11570-1-1.html
  1. public class Solution {
  2.     public boolean canWin(String s) {
  3.         int n = s.length();
  4.         if(n<=1) return false;
  5.         return dfs(s);
  6.     }
  7.     private boolean dfs(String s){ 
  8.         StringBuffer buffer = new StringBuffer();
  9.         buffer.append(s);
  10.         for(int i=0;i<s.length()-1;i++){
  11.             if(s.charAt(i)==s.charAt(i+1)&&s.charAt(i+1)=='+'){
  12.                 buffer.setCharAt(i,'-');
  13.                 buffer.setCharAt(i+1,'-');
  14.                 if(!dfs(buffer.toString())) return true;
  15.                 buffer.setCharAt(i,'+');
  16.                 buffer.setCharAt(i+1,'+');
  17.             }
  18.         }+ t# Z, W) W$ ~, {, v
  19.         return false;1 m+ M3 o5 S$ v9 s0 T. z) p" N
  20.     }; k; {. Q8 Q7 w  Q; e$ x, h
  21. }
对每次回溯都检查是否有走下一步的可能,如果没有可能有下一步,意味着肯定是输的,所以返回false。如果有下一步,检查对手在下一步的基础上是否保证会赢,如果对手在下一步基础上保证会赢,则继续寻找其他走法;如果对手在下一步不能保证会赢,意味着如果我们走该次走法,可以在以后的比赛中通过某种走法保证对手输,返回true。如果发现无论怎么走对手都会赢,也返回false。
这样的代码复杂度是O(n!),如果输入是一个长度为N的"+++++...+++",T(N)=T(N-2)+T(N-3)+(T(2)+T(N-4))+(T(3)+T(N-5))+....+(T(N-5)+T(3))+(T(N-4)+T(2))+T(N-3)+T(N-2) = 2(T(2)+T(3) +..T(N-2))<N*T(N-2) = N(N-2)(N-4)..3*2 < n!

let's check the time complexity: Suppose originally the board of size N contains only '+' signs, then roughly we have:
T(N) = T(N-2) + T(N-3) + [T(2) + T(N-4)] + [T(3) + T(N-5)] + ... 
        [T(N-5) + T(3)] + [T(N-4) + T(2)] + T(N-3) + T(N-2)
     = 2 * sum(T[i])  (i = 3..N-2)
You will find that T(N) = 2^(N-1) satisfies the above equation. Therefore, this algorithm is at least exponential.

X. Use a HashMap to avoid duplicate computation
The idea of the solution is clear, but the time complexity of the backtracking method is high. During the process of searching, we could encounter duplicate computation as the following simple case.
One search path:
Input s = "++++++++"
Player 0: "--++++++"
Player 1: "----++++"
Player 0: "----+--+"
Player0 can win for the input string as "----++++".
Another search path:
Player 0: "++--++++"
Player 1: "----++++"
Player 0: "----+--+"
(Duplicate computation happens. We have already known anyone can win for the
input string as "----++++".)

public boolean canWin(String s) { if (s == null || s.length() < 2) { return false; } HashMap<String, Boolean> winMap = new HashMap<String, Boolean>(); return helper(s, winMap); } public boolean helper(String s, HashMap<String, Boolean> winMap) { if (winMap.containsKey(s)) { return winMap.get(s); } for (int i = 0; i < s.length() - 1; i++) { if (s.startsWith("++", i)) { String t = s.substring(0, i) + "--" + s.substring(i+2); if (!helper(t, winMap)) { winMap.put(s, true); return true; } } } winMap.put(s, false); return false; }
https://leetcode.com/discuss/64291/share-my-java-backtracking-solution
The idea is try to replace every "++" in the current string s to "--" and see if the opponent can win or not, if the opponent cannot win, great, we win!
For the time complexity, here is what I thought, let's say the length of the input string s is n, there are at most n - 1 ways to replace "++" to "--" (imagine s is all "+++..."), once we replace one "++", there are at most (n - 2) - 1 ways to do the replacement, it's a little bit like solving the N-Queens problem, the time complexity is (n - 1) x (n - 3) x (n - 5) x ..., so it's O(n!!)double factorial.
 in your solution, you only cache the win set, in my opinion we can also cache the lose set, and we can use a Map <String, Boolean> to help with that.
You are right. We should also cache the lost case. I've posted the Map<String, Boolean> version and the time reduced by 14.3%.

 I actually tried s.startsWith("++", i) the run time is a bit slower and it's calling a convenient API. Anyway both are fine for an interview.
I even tried to eliminate substring but it looks like converting to string using new String(charArray)greatly increase the time cost.
public boolean canWin(String s) { if(s == null || s.length() < 2) return false; Set<String> winSet = new HashSet<String>(); return canWin(s, winSet); } public boolean canWin(String s, Set<String> winSet){ if(winSet.contains(s)) return true; for(int i = 0; i < s.length() - 1; i++) { if(s.charAt(i) == '+' && s.charAt(i + 1) == '+') { String sOpponent = s.substring(0, i) + "--" + s.substring(i + 2); if(!canWin(sOpponent, winSet)) { winSet.add(s); return true; } } } return false; }

https://leetcode.com/discuss/64522/simple-backtracking-inspired-by-flip-game-i
public boolean canWin(String s) { for (String next : generatePossibleNextMoves(s)) if(!canWin(next)) return true; return false; }
public boolean canWin(String s) { List<String> list = new ArrayList<>(); for(int i = 0; i < s.length() - 1; i++){ if(s.charAt(i) == '+' && s.charAt(i + 1) == '+') list.add(s.substring(0, i) + "--" + s.substring(i + 2, s.length())); // generate all possible sequence after every attempt } for(String str : list){ if(!canWin(str)) // if there is any one way the next player can't win, take it and you'll win return true; } return false; }

X.
https://discuss.leetcode.com/topic/27282/theory-matters-from-backtracking-128ms-to-dp-0ms
If you are interested to learn more, this post shares a more sophisticated solution using game theory (it reduces the running time to 0 seconds!). 

Now let's check the time complexity: Suppose originally the board of size N contains only '+' signs, then roughly we have:
T(N) = T(N-2) + T(N-3) + [T(2) + T(N-4)] + [T(3) + T(N-5)] + ... 
        [T(N-5) + T(3)] + [T(N-4) + T(2)] + T(N-3) + T(N-2)
     = 2 * sum(T[i])  (i = 3..N-2)
You will find that T(N) = 2^(N-1) satisfies the above equation. Therefore, this algorithm is at least exponential.

Concept 1 (Impartial Game): Given a particular arrangement of the game board, if either player have exactly the same set of moves should he move first, and both players have exactly the same winning condition, then this game is called impartial game. For example, chess is not impartial because the players can control only their own pieces, and the +- flip game, on the other hand, is impartial.
--
Concept 2 (Normal Play vs Misere Play): If the winning condition of the game is that theopponent has no valid moves, then this game is said to follow the normal play convention; if, alternatively, the winning condition is that the player himself has no valid moves, then the game is a Misere game. Our +- flip has apprently normal play.
Now we understand the the flip game is an impartial game under normal play. Luckily, this type of game has been extensively studied. Note that our following discussion only applies to normal impartial games.

X. Brute force
https://discuss.leetcode.com/topic/27250/share-my-java-backtracking-solution
For the time complexity, here is what I thought, let's say the length of the input string s is n, there are at most n - 1 ways to replace "++" to "--" (imagine s is all "+++..."), once we replace one "++", there are at most (n - 2) - 1 ways to do the replacement, it's a little bit like solving the N-Queens problem, the time complexity is (n - 1) x (n - 3) x (n - 5) x ..., so it's O(n!!)double factorial.
public boolean canWin(String s) {
  if (s == null || s.length() < 2) {
    return false;
  }
    
  for (int i = 0; i < s.length() - 1; i++) {
    if (s.startsWith("++", i)) {
      String t = s.substring(0, i) + "--" + s.substring(i + 2);
      
      if (!canWin(t)) {
        return true;
      }
    }
  }
    
  return false;
}

TODO:
http://www.1point3acres.com/bbs/thread-144510-1-1.html

Read full article from [LeetCode] Flip Game II - jcliBlogger - 博客园

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts