问题描述
小Q和小X是很好的朋友,她们正在玩一个游戏。她们拿到了一个数组,游戏开始时小Q随机选择一个元素作为起点。接着,两人轮流行动,小Q先行动。
每次行动时,当前玩家需要选择当前元素左边比它更小的元素,然后移动到该元素,接下来换另一方从这个元素继续移动。如果某一方无法进行合法的移动,则该方输掉游戏。
小Q想知道,在双方都采取最优策略的情况下,她最终获胜的概率是多少?请输出分数的最简形式,即分子和分母互素。如果小Q必胜,则输出 1/1。如果小Q必败,则输出 0/1。
测试样例
样例1:
输入:
n = 5,a = [3, 1, 5, 4, 3]
输出:'3/5'
样例2:
输入:
n = 6,a = [6, 2, 9, 7, 4, 3]
输出:'2/3'
样例3:
输入:
n = 4,a = [8, 5, 6, 3]
输出:'1/4'
问题描述
这个问题本质上是一个策略游戏问题。给定一个整数数组,目标是确定有多少个起始位置能够让玩家“小Q”在与另一个玩家“小X”的轮流游戏中获胜。游戏规则如下:
- 两个玩家轮流移动。
- 从数组中的某个索引开始,玩家只能向左移动到一个比当前元素小的位置。
- 如果某个玩家无法进行有效的移动,则该玩家输掉游戏。
函数 solution(n, a) 计算有多少个起始位置能够让“小Q”最终获胜。最后的结果以 win_count/n 的最简分数形式返回。
代码逐步解析
让我们逐步讲解代码中的每个函数:
find_simplest_fraction(numerator, denominator)
python
复制代码
from math import gcd
def find_simplest_fraction(numerator, denominator):
common_divisor = gcd(numerator, denominator)
return f"{numerator // common_divisor}/{denominator // common_divisor}"
- 该函数用于简化分数。
- 使用 Python 的
math库中的gcd函数来计算分子和分母的最大公约数。 - 然后通过将分子和分母都除以它们的最大公约数来简化分数。
- 最后返回一个字符串,表示最简分数形式。
can_win(start_index, a)
python
复制代码
def can_win(start_index, a):
# 用于缓存结果,避免重复计算
memo = {}
def dfs(index, turn):
if index in memo:
return memo[index]
# 获取当前元素左边比它小的元素的索引
for i in range(index - 1, -1, -1):
if a[i] < a[index]:
# 如果是小Q的回合且能够移动,则小Q获胜
if turn == 0:
memo[index] = True
return True
# 如果是小X的回合,则继续递归判断小Q能否获胜
elif not dfs(i, 1 - turn):
memo[index] = True
return True
# 当前玩家不能移动,则他输
memo[index] = False
return False
return dfs(start_index, 0)
-
can_win(start_index, a)函数用于判断从数组中某个具体的起始索引开始,小Q是否能够最终获胜。 -
使用
memo字典缓存之前计算过的结果,避免重复计算,从而提高效率。 -
嵌套函数
dfs(index, turn)是一个递归深度优先搜索函数,用于模拟游戏:index表示当前的位置。turn表示当前轮到的玩家(0表示小Q,1表示小X)。
-
函数会遍历当前索引左侧的元素:
- 如果左边的某个元素小于当前元素(即
a[i] < a[index]),则表示这是一个有效的移动。 - 如果是小Q的回合(
turn == 0),且他可以移动,则小Q从该位置获胜。 - 如果是小X的回合(
turn == 1),函数会继续递归判断“小Q”是否可以在“小X”之后最终获胜。 - 如果“小Q”可以最终逼迫小X输掉,则返回
True。
- 如果左边的某个元素小于当前元素(即
-
如果当前玩家无法移动,则该玩家输掉游戏,函数返回
False。
solution(n, a)
python
复制代码
def solution(n, a):
win_count = 0
for i in range(n):
if can_win(i, a):
win_count += 1
return find_simplest_fraction(win_count, n)
solution函数用于计算“小Q”从多少个起始位置能够获胜。- 函数遍历数组
a中的每一个索引,对于每一个起始索引i,调用can_win(i, a)来判断小Q是否能从该位置获胜。 win_count记录能够获胜的起始位置的数量。- 最后函数返回能够获胜的起始位置数量与总元素数量的最简分数形式。
示例解析
假设输入数组为 [4, 2, 3, 1]。
-
从每个索引开始 (
i = 0, 1, 2, 3) 使用can_win()函数来判断小Q是否能从该位置获胜。 -
对于每个索引,游戏按照规则进行,评估能否向左移动:
- 例如,从索引
2(值为3)开始,小Q可以移动到索引1(值为2)。此时,小X将没有有效移动。
- 例如,从索引
-
评估完所有索引后,
win_count将保存小Q可以获胜的起始位置数量。 -
最后函数以分数形式返回结果,表示小Q能够获胜的概率。
复杂度分析
- 该函数使用了备忘录(memoization)来减少冗余递归调用。
- 最坏情况下,由于递归探索所有可能的移动,时间复杂度会很高。但通过缓存已经评估过的结果,可以显著加快解决方案的速度。
总结
- 代码实现了一个递归深度优先搜索来模拟游戏过程。
- 通过备忘录避免对已经评估过的索引重复计算。
- 最终返回小Q能够获胜的起始位置数量与总元素数量的比值,并以最简形式表示。
整个逻辑的目标是判断“小Q”是否有从每个起始位置获胜的策略,最后计算出能够获胜的概率,并以简化的分数形式表示。