访问完所有房间的第一天

标签: 数组 动态规划

难度: Medium

你需要访问 n 个房间,房间从 0n - 1 编号。同时,每一天都有一个日期编号,从 0 开始,依天数递增。你每天都会访问一个房间。

最开始的第 0 天,你访问 0 号房间。给你一个长度为 n下标从 0 开始 的数组 nextVisit 。在接下来的几天中,你访问房间的 次序 将根据下面的 规则 决定:

  • 假设某一天,你访问 i 号房间。
  • 如果算上本次访问,访问 i 号房间的次数为 奇数 ,那么 第二天 需要访问 nextVisit[i] 所指定的房间,其中 0 <= nextVisit[i] <= i
  • 如果算上本次访问,访问 i 号房间的次数为 偶数 ,那么 第二天 需要访问 (i + 1) mod n 号房间。

请返回你访问完所有房间的第一天的日期编号。题目数据保证总是存在这样的一天。由于答案可能很大,返回对 109 + 7 取余后的结果。

示例 1:

输入:nextVisit = [0,0]
输出:2
解释:
- 第 0 天,你访问房间 0 。访问 0 号房间的总次数为 1 ,次数为奇数。
  下一天你需要访问房间的编号是 nextVisit[0] = 0
- 第 1 天,你访问房间 0 。访问 0 号房间的总次数为 2 ,次数为偶数。
  下一天你需要访问房间的编号是 (0 + 1) mod 2 = 1
- 第 2 天,你访问房间 1 。这是你第一次完成访问所有房间的那天。

示例 2:

输入:nextVisit = [0,0,2]
输出:6
解释:
你每天访问房间的次序是 [0,0,1,0,0,1,2,...] 。
第 6 天是你访问完所有房间的第一天。

示例 3:

输入:nextVisit = [0,1,2,0]
输出:6
解释:
你每天访问房间的次序是 [0,0,1,1,2,2,3,...] 。
第 6 天是你访问完所有房间的第一天。

提示:

  • n == nextVisit.length
  • 2 <= n <= 105
  • 0 <= nextVisit[i] <= i

Submission

运行时间: 134 ms

内存: 28.1 MB

class Solution:
  def firstDayBeenInAllRooms(_, v):
    dp = [0] # first day for i
    v.pop()
    for a in v:
      dp.append(((dp[-1]<<1)+2-dp[a])%1000000007)
    return dp[-1]

Explain

在这个题解中,使用了动态规划的方法。数组 dp 用来记录到达每个房间的第一天的日期编号。初始时,dp[0] = 0,表示第 0 天访问房间 0。对于每个房间 i (从 1 开始),根据题目中的访问规则,如果访问次数为奇数次,则访问 nextVisit[i],否则访问 (i + 1) % n。题解中,dp 的更新公式为 dp[i] = ((dp[i-1] << 1) + 2 - dp[nextVisit[i-1]]) % 1000000007,这个公式是基于之前房间的访问次数和规则计算得出。

时间复杂度: O(n)

空间复杂度: O(n)

class Solution:
  def firstDayBeenInAllRooms(_, v):
    dp = [0] # 第0天访问房间0
    v.pop() # 移除最后一个元素,因为不需要它来计算最后一个房间
    for a in v:
      dp.append(((dp[-1]<<1) + 2 - dp[a]) % 1000000007) # 根据前一个房间和规则更新dp
    return dp[-1] # 返回最后一个房间的第一天日期编号

Explore

该公式是基于访问房间的规则和动态规划的状态转移来推导的。从房间 i-1 到房间 i,如果访问次数是偶数次,我们访问房间 (i+1)%n,如果是奇数次,我们访问 nextVisit[i] 房间。考虑到每次访问房间 i 会再访问一次房间 i-1,因此从房间 i-1 到 i 至少需要 `dp[i-1] + 1` 天;再加上从房间 i-1 通过 nextVisit[i-1] 访问房间 i 的天数,即 `dp[nextVisit[i-1]] + 1`。综上,状态转移方程为 `dp[i] = dp[i-1] + 1 + (dp[i-1] + 1 - dp[nextVisit[i-1]])`,简化后得到 `dp[i] = 2 * dp[i-1] + 2 - dp[nextVisit[i-1]]`,最后取模以防止整数溢出。

在动态规划中,通常只需要初始化起点的状态,其他的状态值将通过状态转移方程逐步计算出来。在这个问题中,dp[0] = 0 表示第0天开始访问房间0,这是一个明确的初始条件。其他房间的访问天数依赖于前一房间的结果和状态转移方程,因此,在开始之前不需要为它们设置初始值。

在题解中移除数组 `v` 的最后一个元素是为了简化计算过程,因为最后一个房间可以直接通过前一个房间的状态来计算它的状态,而不需要依赖于 nextVisit 的最后一个元素。这种处理方式简化了代码的复杂性,但不会影响算法的正确性或最终结果,因为最后一个房间的状态仍然会被计算和返回。

使用模数 `1000000007` 主要是为了防止整数溢出,并保持计算结果在可控的范围内。`1000000007` 是一个大的质数,常用于计算中以防止结果因为过大而溢出,并且在模运算中具有良好的性质,如简化冲突和分布均匀等。在各种编程竞赛和算法实现中,这个数经常被使用。