1. 程式人生 > >Can I Win 我能贏嗎

Can I Win 我能贏嗎

在 "100 game" 這個遊戲中,兩名玩家輪流選擇從 1 到 10 的任意整數,累計整數和,先使得累計整數和達到 100 的玩家,即為勝者。

如果我們將遊戲規則改為 “玩家不能重複使用整數” 呢?

例如,兩個玩家可以輪流從公共整數池中抽取從 1 到 15 的整數(不放回),直到累計整數和 >= 100。

給定一個整數 maxChoosableInteger (整數池中可選擇的最大數)和另一個整數 desiredTotal(累計和),判斷先出手的玩家是否能穩贏(假設兩位玩家遊戲時都表現最佳)?

你可以假設 maxChoosableInteger 不會大於 20, desiredTotal 不會大於 300。

示例:

輸入:
maxChoosableInteger = 10
desiredTotal = 11

輸出:
false

解釋:
無論第一個玩家選擇哪個整數,他都會失敗。
第一個玩家可以選擇從 1 到 10 的整數。
如果第一個玩家選擇 1,那麼第二個玩家只能選擇從 2 到 10 的整數。
第二個玩家可以通過選擇整數 10(那麼累積和為 11 >= desiredTotal),從而取得勝利.
同樣地,第一個玩家選擇任意其他整數,第二個玩家都會贏。

思路:如果有兩個玩家A和B,對於玩家A而言,贏得條件S如下:

1:當前可選的數字的上限即maxChoosableInteger大於等於desiredTotal

2:玩家B的下一局遊戲肯定贏

條件1和條件2是或的關係,而對於條件2,玩家B同樣遞迴呼叫贏的條件S,由於我們每次遞迴呼叫一定會呼叫之前所有沒有選擇過的數字並且desiredTotal都會減去當前選擇過的數字,所以desiredTotal會不斷減少總會有邊界滿足條件S的條件一,所以我們採用回溯法來完成這道題。回溯的核心方程如下:

if ((desiredTotal <= (i + 1)) || !canIWinCore(maxChoosableInteger, desiredTotal - i - 1, used | cur, m)) {
	m[used]=true;
	return true;
}
由於回溯法會大量重複計運算元問題的解,所以我們定義一個型別為
unordered_map<int, bool> &m

的hashmap,儲存每次遍歷過子問題的情況,還有一個技巧在於如何保證儲存的數字不重複呢?我們採用一個長度為陣列20的陣列,其下標表示出現的值。

參考程式碼:

class Solution {
public:
bool canIWinCore(int maxChoosableInteger, int desiredTotal, int used, unordered_map<int, bool> &m) {
	if (m.count(used)) {
		return m[used];
	}
	for (int i = 0; i < maxChoosableInteger; i++) {
		int cur = 1 << i;
		if ((cur & used) == 0) {
			if ((desiredTotal <= (i + 1)) || !canIWinCore(maxChoosableInteger, desiredTotal - i - 1, used | cur, m)) {
				m[used]=true;
				return true;
			}
		}
	}
	m[used] = false;
	return false;
}
bool canIWin(int maxChoosableInteger, int desiredTotal) {
	if (maxChoosableInteger >= desiredTotal) {
		return true;
	}
	if ((1 + maxChoosableInteger)*maxChoosableInteger / 2 < desiredTotal) {
		return false;
	}
	unordered_map<int, bool> m;
	return canIWinCore(maxChoosableInteger, desiredTotal, 0, m);
}
};