1 先手必胜问题
1.1 区间博弈
1.2 取数字博弈
问题描述
给定一个数组区间,两位玩家只能从数组区间的开始位置或者末尾位置开始取数,最后比谁取得的数字和最大。
参考题目链接leetcode在线题目
思路答案
public static boolean predictTheWinner(int[] nums) {
return predict(nums, 0, nums.length, 0, 0, true);
}
private static boolean predict(int[] nums, int s, int e, int sum1, int sum2, boolean first) {
if (e == s + 1) {
if (first)
return sum1 + nums[s] >= sum2;
else return sum2 + nums[s] <= sum1;
}
if (first) // 先手方的两种情况,能赢一种就可以
return predict(nums, s + 1, e, sum1 + nums[s], sum2, false)
|| predict(nums, s, e - 1, sum1 + nums[e - 1], sum2, false);
else // 后手方选择的两种情况,必须保证先手方都能赢
return predict(nums, s + 1, e, sum1, sum2 + nums[s], true)
&& predict(nums, s, e - 1, sum1, sum2 + nums[e - 1], true);
}
这是一个递归的思路,如果我们对时间复杂度要求更高的话,比如同样的题目leetcode486stone-game题,这个解法会超时,这时就需要进行记忆化动态规划。
dp[i][j]
means the biggest number of stones you can get more than opponent picking piles in piles[i] ~ piles[j]
.
You can first pick piles[i]
or piles[j]
.
piles[i]
, your result will be piles[i] - dp[i + 1][j]
piles[j]
, your result will be piles[j] - dp[i][j - 1]
So we get:dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
We start from smaller subarray and then we use that to calculate bigger subarray.
follow this thought, you can implement the code in java below:
public boolean stoneGame(int[] piles) {
int[][] dp = new int[piles.length][piles.length];
for (int i = 0; i < piles.length; i++) {
dp[i][i] = piles[i];
if (i < piles.length - 1)
dp[i][i + 1] = Math.max(piles[i], piles[i + 1]) - Math.min(piles[i], piles[i + 1]);
}
for (int distance = 1; distance < piles.length; distance++) {
for (int j = 0; j + distance < piles.length; j++) {
dp[j][j + distance] = Math.max(piles[j] - dp[j+1][j+distance], piles[j +distance] - dp[j][j+distance -1]);
}
}
return dp[0][piles.length -1] > 0;
}
leetcode 464在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。
如果我们将游戏规则改为 "玩家不能重复使用整数" 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定一个整数 maxChoosableInteger
(整数池中可选择的最大数)和另一个整数 desiredTotal
(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?
你可以假设 maxChoosableInteger
不会大于 20, desiredTotal
不会大于 300。
思路:
必胜和必败的两个特判
然后用dp记录每个特定状态(已经选了哪些)的胜负
再来一个options以供选择
然后dfs
如果currentState已经出现在记忆化dp直接返回,need表示还需要多少
否则,开始遍历每一个商品看看是否取过
如果取过了就不考虑
如果没有取过就nextState取,然后判断如果options[i] >= need或者dfs(nextState, need - options[i]) == False
都可以证明当前state是获胜的,所以记录dp并返回
如果整个for都没有取胜,就是lose了,记录dp并返回
最后调用dfs(0, desire)
class Solution {
public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
if (desiredTotal <= 0) return true;
if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) return false;
char[] used = new char[maxChoosableInteger];
Arrays.fill(used, '0');
Map map = new HashMap<>();
return canIWin(desiredTotal, used, map);
}
private static boolean canIWin(int state,char[] used, Map hashMap) {
if (state <= 0) return false;
String status = new String(used);
if (hashMap.containsKey(status)) return hashMap.get(status);
for (int i = 1; i <= used.length; i++) {
if(used[i-1] == '1') {
continue;
}
used[i-1] = '1';
if ( !canIWin(state - i, used, hashMap)) {
used[i-1] = '0';
hashMap.put(status, true);
return true;
}
used[i-1] = '0';
}
hashMap.put(status, false);
return false;
}
}
在leetcode时间限制下会超时,更快速的
class Solution {
int mem[1<<20] = {};
public:
bool canIWin(int M, int T)
{
int sum = M*(M+1)/2; // sum of entire choosable pool
// I just pick 1 to win
if (T < 2) return true;
// Total is too large, nobody can win
else if (sum < T) return false;
// Total happens to match sum, whoever picks at odd times wins
else if (sum == T) return M%2;
// Non-trivial case: do DFS
// Initial total: T
// Initial game state: k = 0 (all numbers are not picked)
else return dfs(M, T, 0);
}
// DFS to check if I can win
// k: current game state
// T: remaining total to reach
bool dfs(int M, int T, int k)
{
// memorized
if (mem[k] != 0) return mem[k] > 0;
// total is already reached by opponent, so I lose
if (T <= 0) return false;
// try all currently available numbers
for (int i = 0; i < M; ++i)
// if (i+1) is available to pick and my opponent can't win after I picked, I win!
if (!(k&(1<