Can I win LT464

In the "100 game," two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins.

What if we change the game so that players cannot re-use integers?

For example, two players might take turns drawing from a common pool of numbers of 1..15 without replacement until they reach a total >= 100.

Given an integer maxChoosableInteger and another integer desiredTotal, determine if the first player to move can force a win, assuming both players play optimally.

You can always assume that maxChoosableInteger will not be larger than 20 and desiredTotal will not be larger than 300. 

Idea 1. DFS with memory. 

Understanding the question is not easy, starting from small example, 

maxChoosableInteger = 2, desiredTotoal = 3, first player lose, depends on who play the last, 2%2 = 0 means the 2nd player pick the last number

maxChoosableInteger = 2, desiredTotal = 4, first player lose no matter what number choose (or say both player will lose, as no one can reach the state to make the sum of choose numbers so far by both player  >= desiredTotal)

maxChoosableInteger = 2, desiredTotal = 2, if first player win if picking 2, otherwise lose

maxChoosableInteger = 3, desiredTotal = 5, 

  a. if 1st player choose 1, the state for second state is [2, 3], desiredTotoal = 4; if 2nd player choose 2, 1st player has state [3], desiredTotoal = 2, 1st player win; if 2nd player choose 3, 1st has tate [2], desiredTotal = 1, 1st player win; it mean no matter what 2nd palyer choose, with given state [2, 3], desiredTotal = 4, the 2nd player will lose, actually any player with such state will lose. As long as the 1st player can make a move and cause the state to be such losing state, the 1st player can win, or in any state which can make the next player lose, the current player can win.

      b. if 1st player choose 2, the state is [1, 3], desiredTotal = 3; if 2nd player choose 3, the 2nd player win

      c. if 1st player coose 3, the state is [1, 2], desiredTotal = 2; if the 2nd player choose 2, the 2nd player win.

 Assume maxChoosableInteger = N for easy notation, result lose = false, win = true, considering the corner cases: 

          1. false if sum(1...N) = (1+N)*N/2 < desiredTotal  

          2. (N%2 == 1) if sum(1..N) == desiredTotal, depending who takes the last number

 The core idea is to store the state and the result to save recomputation. If just bruteforce backtracking, the first player has N choices, the 2nd player has N-1 choices on the next level, there are N*(N-1)*(N-2)...1 = O(N!) nodes on searching tree,  since subproblems could get computed more than once, to avod recomputation on the same subproblem, a common technique is to use cache to store the state and result.  Since there are O(2^N) state, for each number can be picked or not, each subproblem takes O(N) time to loop the allowed number,

Time complexity: O(N2^N)

Space complexity: O(2^N)

How do we define the state of the game? Initially the list of allowed numbers + updated desired total, since the remaining total = desiredTotal - sum(not allowed number), the allowed numbers is enough to infer the remaining toal. How to represent the state?

        1. arrays of integers [1...N] or arrays of boolean [true, true...]

        2. integer, since N will not be larger than 20, we could use bit mask to represent the state by a number, for N = 2, 3(0b011) means 1 and 2 has been choosen. The advantage is that using integer do not need to reset the state after backtracking, as it's pass by value.

 initial idea, arrays of integers + updated desired total for state, also the removing choosing elements from array is not a good idea, which is involved copying new array and inserting the element back to the original state to keep the order.

Note: we need to reset the state even it's winning, because we don't know which player is winning, we might need to continue searching, hence we need to reset the previous state before the current move.

 1 class Solution {
 2     private boolean canIWinHelper(int desiredTotal, List<Integer> candidates, Map<String, Boolean> gameCache) {
 3         String key = desiredTotal + ":" + candidates;
 4         if(gameCache.containsKey(key)) {
 5             return gameCache.get(key);
 6         }
 7         else {
 8             boolean result = false;
 9         
10             List<Integer> copy = new ArrayList<>(candidates);
11             for(int i= 0; i < copy.size(); ++i) {
12                     int num = candidates.remove(i);
13                     if(num >= desiredTotal || !canIWinHelper(desiredTotal - num, candidates, gameCache)) {
14                         result = true;
15                         candidates.add(i, num);
16                         break;
17                     }
18               
19                    candidates.add(i, num);
20             }
21             
22             gameCache.put(key, result);
23             return result;
24         }
25     }
26     
27     public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
28         List<Integer> candidates = new ArrayList<>();
29         int sum = 0;
30         for(int i = 1; i <= maxChoosableInteger; ++i) {
31             sum += i;
32             candidates.add(i);
33         }
34         
35         if(desiredTotal > sum) {
36             return false;
37         }
38         
39         Map<String, Boolean> gameCache = new HashMap<>();
40         
41         return canIWinHelper(desiredTotal, candidates, gameCache);
42     }
43 }

arrays of booleans with corner cases:

 1 class Solution {
 2     private boolean canIWinHelper(int desiredTotal, boolean[] state, Map<String, Boolean> gameCache) {
 3         String key = Arrays.toString(state);
 4         if(gameCache.containsKey(key)) {
 5             return gameCache.get(key);
 6         }
 7         
 8         boolean result = false;
 9         for(int i = 1; i <= state.length-1; ++i) {
10             if(!state[i]) {
11                 state[i] = true;
12                 if(i >= desiredTotal || !canIWinHelper(desiredTotal - i, state, gameCache)) {
13                     result = true;
14                     state[i] = false;
15                     break;
16                 }
17                 state[i] = false;
18             }
19         }
20         gameCache.put(key, result);
21        
22         return result;
23     }
24     
25     public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
26          int sum = maxChoosableInteger * (1 + maxChoosableInteger);
27          if(sum < desiredTotal) {
28              return false;
29          }
30          if(sum == desiredTotal) {
31              return (maxChoosableInteger%2 == 1);
32          }
33         
34          boolean[] state = new boolean[maxChoosableInteger + 1];
35         
36         Map<String, Boolean> gameCache = new HashMap<>();
37         
38         return canIWinHelper(desiredTotal, state, gameCache);
39     }
40 }

integer as state, using bit operation

 1 class Solution {
 2     private boolean canIWinHelper(int maxChoosableInteger, int desiredTotal, int state, Map<Integer, Boolean> gameCache) {
 3         
 4         if(gameCache.containsKey(state)) {
 5             return gameCache.get(state);
 6         }
 7         
 8         boolean result = false;
 9         for(int i = 1; i <= maxChoosableInteger; ++i) {
10             int allowed = (state >> i) & 1;
11             if(allowed == 0) {
12                 if(i >= desiredTotal || !canIWinHelper(maxChoosableInteger, desiredTotal - i, state^(1 << i), gameCache)) {
13                     result = true;
14                     break;
15                 }
16             }
17         }
18         gameCache.put(state, result);
19        
20         return result;
21     }
22     
23     public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
24          int sum = maxChoosableInteger * (1 + maxChoosableInteger);
25          if(sum < desiredTotal) {
26              return false;
27          }
28          if(sum == desiredTotal) {
29              return (maxChoosableInteger%2 == 1);
30          }
31         
32         Map<Integer, Boolean> gameCache = new HashMap<>();
33         int state = 0;
34         
35         return canIWinHelper(maxChoosableInteger, desiredTotal, state, gameCache);
36     }
37 }

python much conciser code even using integer array

 1 class Solution:
 2     def canIWinHelper(self, candidates, desiredTotal, gameCache) -> bool:
 3         key = str(candidates)
 4         
 5         if key in gameCache:
 6             return gameCache[key]
 7         
 8         for i in range(len(candidates)):
 9             if candidates[i] >= desiredTotal or not self.canIWinHelper(list(candidates[:i]) + list(candidates[i+1:]), desiredTotal - candidates[i], gameCache):
10                 gameCache[key] = True
11                 return True
12             
13         gameCache[key] = False
14         return False
15         
16     def canIWin(self, maxChoosableInteger: int, desiredTotal: int) -> bool:
17         sum = maxChoosableInteger * (1 + maxChoosableInteger) / 2
18         if sum < desiredTotal:
19             return False
20         
21         if sum == desiredTotal:
22             return (maxChoosableInteger%2 == 1)
23         
24         return self.canIWinHelper(range(1, maxChoosableInteger+1), desiredTotal, {})
25         
 1 class Solution:
 2     def canIWinHelper(self, maxChoosableInteger, desiredTotal, state, gameCache) -> bool:        
 3         if state in gameCache:
 4             return gameCache[state]
 5         
 6         for i in range(maxChoosableInteger):
 7             allowed = (state >> i) & 1
 8             if allowed == 0:
 9                 num = i + 1
10                 if (num >= desiredTotal) or (not self.canIWinHelper(maxChoosableInteger, desiredTotal - num, state ^ (1 << i), gameCache)):
11                     gameCache[state] = True
12                     return True
13             
14         gameCache[state] = False
15         return False
16         
17     def canIWin(self, maxChoosableInteger: int, desiredTotal: int) -> bool:
18         sum = maxChoosableInteger * (1 + maxChoosableInteger) / 2
19         if sum < desiredTotal:
20             return False
21         
22         if sum == desiredTotal:
23             return (maxChoosableInteger%2 == 1)
24         
25         return self.canIWinHelper(maxChoosableInteger, desiredTotal, 0, {})

Example

Input:
maxChoosableInteger = 10
desiredTotal = 11

Output:
false

Explanation:
No matter which integer the first player choose, the first player will lose.
The first player can choose an integer from 1 up to 10.
If the first player choose 1, the second player can only choose integers from 2 up to 10.
The second player will win by choosing 10 and get a total = 11, which is >= desiredTotal.
Same with other integers chosen by the first player, the second player will always win.

猜你喜欢

转载自www.cnblogs.com/taste-it-own-it-love-it/p/10468783.html