LeetCode探索(39):1510-石子游戏IV

148 阅读2分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战」。

题目

Alice 和 Bob 两个人轮流玩一个游戏,Alice 先手。

一开始,有 n 个石子堆在一起。每个人轮流操作,正在操作的玩家可以从石子堆里拿走 任意 非零 平方数 个石子。

如果石子堆里没有石子了,则无法操作的玩家输掉游戏。

给你正整数 n ,且已知两个人都采取最优策略。如果 Alice 会赢得比赛,那么返回 True ,否则返回 False

示例 1:

输入:n = 1
输出:true
解释:Alice 拿走 1 个石子并赢得胜利,因为 Bob 无法进行任何操作。

示例 2:

输入:n = 2
输出:false
解释:Alice 只能拿走 1 个石子,然后 Bob 拿走最后一个石子并赢得胜利(2 -> 1 -> 0)。

示例 3:

输入:n = 4
输出:true
解释:n 已经是一个平方数,Alice 可以一次全拿掉 4 个石子并赢得胜利(4 -> 0)。

示例 4:

输入:n = 7
输出:false
解释:当 Bob 采取最优策略时,Alice 无法赢得比赛。
如果 Alice 一开始拿走 4 个石子, Bob 会拿走 1 个石子,然后 Alice 只能拿走 1 个石子,Bob 拿走最后一个石子并赢得胜利(7 -> 3 -> 2 -> 1 -> 0)。
如果 Alice 一开始拿走 1 个石子, Bob 会拿走 4 个石子,然后 Alice 只能拿走 1 个石子,Bob 拿走最后一个石子并赢得胜利(7 -> 6 -> 2 -> 1 -> 0)。

示例 5:

输入:n = 17
输出:false
解释:如果 Bob 采取最优策略,Alice 无法赢得胜利。

提示:

  • 1 <= n <= 10^5

思考

这是一道有意思的题目,难度困难。相比于前面的石子游戏系列的 I ~ III,我们可以注意到本题中是n个石子堆在一起,而不是有n堆石子。而且,每轮操作时当前玩家可以从石子堆里拿走任意非零 平方数 个石子。

我们考虑用动态规划解决该问题。我们用f[i]表示先手在面对 i 颗石子时是否会赢得比赛。由于先手和后手都采取最优策略,那么 f[i] 必胜时,当且仅当存在某个 f[i - k^2] 为必败的状态。边界条件为 f[0] = false,即没有石子时,先手会输掉游戏。

总而言之,这道题还是挺有意思的,上述的思考过程看似很简单,然而事实并非如此 ... 千里之行始于足下!在本题中,我们定义状态并列出状态转移方程的过程,需要我们好好地体会和学习!

解答

方法一:动态规划

/**
 * @author 觅迹寻踪
 * @param {number} n
 * @return {boolean}
 */
var winnerSquareGame = function(n) {
  const f = new Array(n + 1).fill(false)
  // i需要从1到n遍历,因为f[i]默认值是false
  for (let i = 1; i <= n; ++i) {
    for (let k = 1; k * k <= i; ++k) {
      if (!f[i - k * k]) {
        f[i] = true
        break
      }
    }
  }
  return f[n]
}
// 执行用时:104 ms, 在所有 JavaScript 提交中击败了60.00%的用户
// 内存消耗:43.7 MB, 在所有 JavaScript 提交中击败了5.00%的用户

复杂度分析

  • 时间复杂度:O(n * n ^ 0.5)。对于每一个数 i,k 的枚举上限不超过 n ^ 0.5。
  • 空间复杂度:O(n),即为存储所有状态需要的空间。

参考