[动态规划]1900. 最佳运动员的比拼回合
1900. 最佳运动员的比拼回合
1900. 最佳运动员的比拼回合 - 力扣(LeetCode)
问题理解
- 比赛规则:每轮比赛将选手两两配对比赛,胜者晋级下一轮。如果人数为奇数,最后一人自动晋级。
- 目标:计算选手A和选手B最早和最晚会在第几轮相遇。
解决方案
主要思路
使用深度优先搜索(DFS)结合记忆化来递归计算所有可能的比赛情况,找出最早和最晚的相遇轮次。
关键函数:dfs(n, first, second)
这个递归函数计算在当前有n
个选手的情况下,选手A(位置first
)和选手B(位置second
)相遇的最早和最晚轮次。
参数说明
n
: 当前轮次的选手总数first
: 选手A的位置(1-based)second
: 选手B的位置(1-based)
返回值
返回一个元组(earliest, latest)
,表示最早和最晚相遇轮次。
代码
class Solution:def earliestAndLatest(self, n: int, firstPlayer: int, secondPlayer: int) -> List[int]:@cache #缓存装饰器,避免重复计算dfs(一行代码实现记忆化)def dfs(n:int, first: int, second: int) -> Tuple[int,int]:# 配对时,位置 i 的选手会和位置 n + 1 - i 的选手比赛。# 当两个选手的位置满足first + second == n + 1时,说明他们会在当前轮次相遇if first + second == n+1:return 1, 1#为了减少计算量,我们始终保持选手A在左侧#注释:题目已保证first < secondif first + second > n + 1:first, second = n + 1 - second, n + 1 - first#下一轮的人数m是当前人数加1除以2的整数部分m = (n + 1) // 2# 两选手间保留[min_mid, max_mid]个人# 计算两个选手之间可能保留的中间选手数量范围min_mid = 0 if second <= m else second - n // 2 - 1max_mid = second - first if second <= m else m - firstearliest, latest = inf, 0# 枚举所有可能情况# 通过双重循环枚举:# 选手A左侧保留的选手数量left# 选手A和B之间保留的选手数量mid# 对于每种情况,递归计算下一轮的结果for left in range(first):for mid in range(min_mid, max_mid):e, l = dfs(m, left + 1, left + mid + 2)earliest = min(earliest, e)latest = max(latest, l)#将当前轮次加上递归结果return earliest + 1, latest + 1return list(dfs(n, firstPlayer, secondPlayer))# 时间复杂度:由于使用了记忆化,避免了重复计算,复杂度约为O(n^4)
# 空间复杂度:主要取决于递归深度和缓存大小,约为O(n^3)
1、相遇的条件
- 如果选手A的位置是
first
,选手B的位置是second
,那么:- 选手A的对手位置是
n + 1 - first
。 - 选手B的对手位置是
n + 1 - second
。
- 选手A的对手位置是
2、first + second > n + 1
的代码能保证选手A在左侧
它确保选手A(first
)的位置总是在左侧(即更靠近起点),而选手B(second
)的位置总是在右侧(即更靠近终点)
- 问题描述中已经保证
first < second
(选手A的位置在选手B的左侧)。 - 但直接递归计算时,可能会遇到选手A和选手B的位置分布不对称的情况(比如选手A靠近右侧,选手B靠近左侧)。
- 通过对称性处理,我们可以将问题统一为“选手A在左侧,选手B在右侧”的形式,避免重复计算。
对称性处理的逻辑
- 如果
first + second > n + 1
,说明选手A和选手B的位置分布偏向右侧:- 比如
n = 5
,first = 4
,second = 5
:first + second = 9 > 6 = n + 1
。- 此时选手A(4)和选手B(5)都靠近右侧。
- 比如
- 通过对称性转换:
first' = n + 1 - second = 6 - 5 = 1
second' = n + 1 - first = 6 - 4 = 2
- 新的
first' = 1
和second' = 2
表示选手A和选手B在对称位置的左侧。 - 这样问题就转换为“选手A在左侧,选手B在右侧”的标准形式。
- 因为
first < second
,所以first' > second'
不成立(因为n + 1 - second < n + 1 - first
)。 - 因此,转换后的
first'
仍然小于second'
,且first'
更靠近左侧。
3、计算 min_mid
和 max_mid
计算 min_mid
min_mid
表示两个选手之间至少保留的中间选手数量:
- 如果
second <= m
(选手B在左侧半区):- 中间选手数量可以为
0
(即两个选手之间没有其他人)。 - 因此
min_mid = 0
。
- 中间选手数量可以为
- 如果
second > m
(选手B在右侧半区):- 选手B在下一轮的位置会映射到
second - n // 2 - 1
。 - 因此
min_mid = second - n // 2 - 1
。
- 选手B在下一轮的位置会映射到
计算 max_mid
max_mid
表示两个选手之间最多保留的中间选手数量:
- 如果
second <= m
(选手B在左侧半区):- 中间选手数量最多为
second - first - 1
(即两个选手之间的所有选手)。 - 因此
max_mid = second - first
(因为mid
的取值范围是左闭右开区间)。
- 中间选手数量最多为
- 如果
second > m
(选手B在右侧半区):- 下一轮的选手数量是
m
,选手A的位置最多可以保留m - first
个中间选手。 - 因此
max_mid = m - first
。
- 下一轮的选手数量是
枚举 mid
的意义
mid
表示两个选手之间保留的中间选手数量。- 通过枚举
mid
的所有可能值(从min_mid
到max_mid - 1
),我们可以覆盖所有可能的比赛配对情况。 - 对于每个
mid
,递归计算下一轮的最早和最晚相遇轮次。