464.我能赢吗

155 阅读1分钟

题目:
在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和 达到或超过  100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家 不能 重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定两个整数 maxChoosableInteger (整数池中可选择的最大数)和 desiredTotal(累计和),若先出手的玩家是否能稳赢则返回 true ,否则返回 false 。假设两位玩家游戏时都表现 最佳 。
算法:
方法一:模拟
执行会超时。因为desiredTotal最大20,考虑用state保存数字是否使用。二进制的第i位是否为1,代表i是否被使用了。

	totalSum := maxChoosableInteger * (maxChoosableInteger + 1) / 2
	if desiredTotal > totalSum {
		return false
	}

	var dfs func(state int, sum int, maxInt int) bool
	dfs = func(state int, sum int, maxInt int) bool {
		for i := 1; i <= maxInt; i ++ {
			if state & (1 << maxInt) > 0 {
				continue
			}
			if sum + i >= desiredTotal {
				return true
			}
			if !dfs(state & (1 << maxInt), sum + i, maxInt) {
				return true
			}
		}
		return false
	}

	return dfs(0, 0, maxChoosableInteger)
}

方法二:记忆化搜索
比如maxChoosableInteger为 5 选择1,2,3,再选择4 和选择2,1,3再选择4 的结果是一样的,1,2,3的先后选择次序不影响结果,可以把第一次计算了把结果存起来,这就是记忆化。

func canIWin(maxChoosableInteger int, desiredTotal int) bool {
	totalSum := maxChoosableInteger * (maxChoosableInteger + 1) / 2
	if desiredTotal > totalSum {
		return false
	}
	// state最大的数字为1 << 21 - 1
	visited := make([]int, 1 << 21)
	var dfs func(state int, sum int, maxInt int) bool
	dfs = func(state int, sum int, maxInt int) bool {
		if visited[state] == 1 {
			return true
		}
		if visited[state] == 2 {
			return false
		}
		for i := 1; i <= maxInt; i ++ {
			if state & (1 << i) > 0 {
				continue
			}
			if sum + i >= desiredTotal {
				visited[state] = 1
				return true
			}
			if !dfs(state | (1 << i), sum + i, maxInt) {
				visited[state] = 1
				return true
			}
		}
		visited[state] = 2
		return false
	}

	return dfs(0, 0, maxChoosableInteger)
}