动态规划的应用(四):LeetCode 1900. 最佳运动员的比拼回合

LeetCode 1900. The Earliest and Latest Rounds Where Players Compete(最佳运动员的比拼回合)

其他动态规划的应用实例:
动态规划的应用(一):最短路问题
动态规划的应用(二):cutting stock 问题
动态规划的应用(三):字符串相关问题
动态规划的应用(五):LeetCode 413, 446. 等差数列划分
动态规划的应用(六):矩阵相关问题


问题描述

LeetCode 1900 I
LeetCode 1900 II

暴力求解


笔者才疏学浅,只想到了暴力求解的方法,但是估计单纯的暴力求解肯定是不能通过的,因此尽可能想到了一些避免重复迭代的方法,比如将中间结果存储在字典中。
具体代码如下:

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,就可以通过了,当然效果不是很好:
运行效果 暴力求解

动态规划与记忆化搜索


官方题解中给出了一种基于动态规划的解决方法,思路分析令人叹为观止。不过题解中也坦诚地说,“本题思维难度较大。其中的有些技巧可能在其它的题目中很少出现。”

LeetCode 1900 官方题解

题解思路

官方题解 I
官方题解 II
官方题解 III
官方题解 IV
官方题解 V

代码实现

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]

运行效果

运行效果 官方题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值