LeetCode 1900. The Earliest and Latest Rounds Where Players Compete(最佳运动员的比拼回合)
其他动态规划的应用实例:
动态规划的应用(一):最短路问题
动态规划的应用(二):cutting stock 问题
动态规划的应用(三):字符串相关问题
动态规划的应用(五):LeetCode 413, 446. 等差数列划分
动态规划的应用(六):矩阵相关问题
问题描述
暴力求解
笔者才疏学浅,只想到了暴力求解的方法,但是估计单纯的暴力求解肯定是不能通过的,因此尽可能想到了一些避免重复迭代的方法,比如将中间结果存储在字典中。
具体代码如下:
class Solution:
def earliestAndLatest(self, n: int, firstPlayer: int, secondPlayer: int) -> List[int]:
def run(list_player, first, second, dict_result):
"""
递归函数
:param list_player: 当前剩余运动员的序列
:param first: 最强运动员 1
:param second: 最强运动员 2
:param dict_result: 中间结果字典,用于存储中间结果防止重复迭代
:return: 当前运动员序列下的最少和最多回合数
"""
# special case 1: 如果运动员序列存在于中间结果字典中,直接获取结果
if tuple(list_player) in dict_result.keys():
return dict_result[tuple(list_player)]
# special case 2: 如果两个最强运动员相遇,直接返回结果 [1, 1]
if list_player.index(first) + list_player.index(second) == len(list_player) - 1:
return [1, 1]
# loop: 可能比拼结果,指数级
num_match = int((len(list_player) + 1) / 2)
result = [len(list_player), 0]
for i in range(2 ** num_match):
list_beat = list(bin(i)[2:])
for j in range(num_match - len(list_beat)):
list_beat = ['0'] + list_beat
list_beat = [True if j == '1' else False for j in list_beat]
# 两名最佳运动员比拼普通运动员一定获胜
if list_player.index(first) < num_match and not list_beat[list_player.index(first)]:
continue
if list_player.index(first) >= num_match and list_beat[len(list_player) - list_player.index(first) - 1]:
continue
if list_player.index(second) < num_match and not list_beat[list_player.index(second)]:
continue
if list_player.index(second) >= num_match and list_beat[len(list_player) - list_player.index(second) - 1]:
continue
# 如果人数为奇数,中间运动员视为获胜
if len(list_player) % 2 and not list_beat[-1]:
continue
# 当前回合的比赛结果
list_player_left, list_centre, list_player_right = [], [], []
for j in range(num_match):
if list_player[j] == list_player[-j - 1]: # 如果人数为奇数,对中间运动员单独处理
list_centre = [list_player[j]]
continue
if list_beat[j]:
list_player_left.append(list_player[j])
else:
list_player_right = [list_player[-j - 1]] + list_player_right
list_player_ = list_player_left + list_centre + list_player_right
# 下一个回合的迭代
result_ = run(list_player=list_player_, first=first, second=second, dict_result=dict_result)
result_ = [result_[0] + 1, result_[1] + 1]
# 更新最少/最多回合数
result[0] = result_[0] if result_[0] < result[0] else result[0]
result[1] = result_[1] if result_[1] > result[1] else result[1]
dict_result[tuple(list_player)] = result # 更新中间结果字典
return result
list_player_all = list(range(1, n + 1))
return run(list_player=list_player_all, first=firstPlayer, second=secondPlayer, dict_result={})
这里有一个有趣的事情,就是当编程语言选为 Python 时,会有算例通不过的:
n, firstPlayer, secondPlayer = 26, 16, 22
但如果将编程语言选为 Python3,就可以通过了,当然效果不是很好:
动态规划与记忆化搜索
官方题解中给出了一种基于动态规划的解决方法,思路分析令人叹为观止。不过题解中也坦诚地说,“本题思维难度较大。其中的有些技巧可能在其它的题目中很少出现。”
题解思路
代码实现
class Solution:
def earliestAndLatest(self, n: int, firstPlayer: int, secondPlayer: int) -> List[int]:
@cache
def dp(n: int, f: int, s: int) -> (int, int):
if f + s == n + 1:
return (1, 1)
# F(n, f, s) = F(n, n + 1 - s, n + 1 - f)
if f + s > n + 1:
return dp(n, n + 1 - s, n + 1 - f)
earliest, latest = float("inf"), float("-inf")
n_half = (n + 1) // 2
if s <= n_half:
# s 在左侧或者中间
for i in range(f):
for j in range(s - f):
x, y = dp(n_half, i + 1, i + j + 2)
earliest = min(earliest, x)
latest = max(latest, y)
else:
# s 在右侧
# s'
s_prime = n + 1 - s
mid = (n - 2 * s_prime + 1) // 2
for i in range(f):
for j in range(s_prime - f):
x, y = dp(n_half, i + 1, i + j + mid + 2)
earliest = min(earliest, x)
latest = max(latest, y)
return (earliest + 1, latest + 1)
# F(n, f, s) = F(n, s, f)
if firstPlayer > secondPlayer:
firstPlayer, secondPlayer = secondPlayer, firstPlayer
earliest, latest = dp(n, firstPlayer, secondPlayer)
dp.cache_clear()
return [earliest, latest]
运行效果