加载中...
1560-圆形赛道上经过次数最多的扇区(Most Visited Sector in a Circular Track)
发表于:2021-12-03 | 分类: 简单
字数统计: 2.5k | 阅读时长: 10分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/most-visited-sector-in-a-circular-track

英文原文

Given an integer n and an integer array rounds. We have a circular track which consists of n sectors labeled from 1 to n. A marathon will be held on this track, the marathon consists of m rounds. The ith round starts at sector rounds[i - 1] and ends at sector rounds[i]. For example, round 1 starts at sector rounds[0] and ends at sector rounds[1]

Return an array of the most visited sectors sorted in ascending order.

Notice that you circulate the track in ascending order of sector numbers in the counter-clockwise direction (See the first example).

 

Example 1:

Input: n = 4, rounds = [1,3,1,2]
Output: [1,2]
Explanation: The marathon starts at sector 1. The order of the visited sectors is as follows:
1 --> 2 --> 3 (end of round 1) --> 4 --> 1 (end of round 2) --> 2 (end of round 3 and the marathon)
We can see that both sectors 1 and 2 are visited twice and they are the most visited sectors. Sectors 3 and 4 are visited only once.

Example 2:

Input: n = 2, rounds = [2,1,2,1,2,1,2,1,2]
Output: [2]

Example 3:

Input: n = 7, rounds = [1,3,5,7]
Output: [1,2,3,4,5,6,7]

 

Constraints:

  • 2 <= n <= 100
  • 1 <= m <= 100
  • rounds.length == m + 1
  • 1 <= rounds[i] <= n
  • rounds[i] != rounds[i + 1] for 0 <= i < m

中文题目

给你一个整数 n 和一个整数数组 rounds 。有一条圆形赛道由 n 个扇区组成,扇区编号从 1n 。现将在这条赛道上举办一场马拉松比赛,该马拉松全程由 m 个阶段组成。其中,第 i 个阶段将会从扇区 rounds[i - 1] 开始,到扇区 rounds[i] 结束。举例来说,第 1 阶段从 rounds[0] 开始,到 rounds[1] 结束。

请你以数组形式返回经过次数最多的那几个扇区,按扇区编号 升序 排列。

注意,赛道按扇区编号升序逆时针形成一个圆(请参见第一个示例)。

 

示例 1:

输入:n = 4, rounds = [1,3,1,2]
输出:[1,2]
解释:本场马拉松比赛从扇区 1 开始。经过各个扇区的次序如下所示:
1 --> 2 --> 3(阶段 1 结束)--> 4 --> 1(阶段 2 结束)--> 2(阶段 3 结束,即本场马拉松结束)
其中,扇区 1 和 2 都经过了两次,它们是经过次数最多的两个扇区。扇区 3 和 4 都只经过了一次。

示例 2:

输入:n = 2, rounds = [2,1,2,1,2,1,2,1,2]
输出:[2]

示例 3:

输入:n = 7, rounds = [1,3,5,7]
输出:[1,2,3,4,5,6,7]

 

提示:

  • 2 <= n <= 100
  • 1 <= m <= 100
  • rounds.length == m + 1
  • 1 <= rounds[i] <= n
  • rounds[i] != rounds[i + 1] ,其中 0 <= i < m

通过代码

高赞题解

[5495] 圆形赛道上经过次数最多的扇区

题目思考

  1. 如何简化问题?

解决方案

思路

  • 题目 blahblah 说了很长, 但仔细分析可以发现, 中间部分对结果完全没影响, 中间不管有多少个值多少圈, 对于每个扇区增加的次数都是相同的
  • 所以我们可以只考虑起点和终点, 简化为一圈的情况, 这一圈经过的扇区是额外的部分, 最终结果只需要考虑起点和终点途径的扇区即可
  • 注意终点可能小于起点, 这时候就要先从 1 到终点, 再从起点遍历到 n 即可(因为最终结果要按顺序)

复杂度

  • 时间复杂度 O(N): 只需要遍历起点和终点之间的部分
  • 空间复杂度 O(1): 只使用了几个变量

代码

class Solution:
    def mostVisited(self, n: int, rounds: List[int]) -> List[int]:
        s, e = rounds[0], rounds[-1]
        if s <= e:
            # [起点, 终点]
            return list(range(s, e + 1))
        else:
            # [1, 终点]+[起点, n]
            return list(range(1, e + 1)) + list(range(s, n + 1))

[5496] 你可以获得的最大硬币数目

题目思考

  1. 如何保证自己每次拿到的都是最优的?

解决方案

思路

  • 贪心法, 既然我们只能拿第二大的, 那每次都把最大的留给 Alice, 然后我们选第二大的, 再把当前最小的塞给 Bob 就行了
  • 转换成代码就是先排序, 然后从大到小遍历, 从第二大的开始拿, 每次中间隔一个值(把当前最大的留给 Alice), 直到我们拿够 n 个硬币 (总共 3n 个硬币)

复杂度

  • 时间复杂度 O(NlogN): 需要排序
  • 空间复杂度 O(1): 只使用了几个变量

代码

class Solution:
    def maxCoins(self, piles: List[int]) -> int:
        piles.sort()
        cnt = 0
        res = 0
        for i in range(len(piles) - 2, -1, -2):
            # 从次大值开始, 隔一个硬币拿一次
            res += piles[i]
            cnt += 1
            if cnt == len(piles) // 3:
                break
        return res

[5497] 查找大小为 M 的最新分组

题目思考

  1. 需要哪些数据结构?
  2. 当前位置变成 1 之后会改变哪些位置的连续 1 的长度?

解决方案

思路

  • 分析题目, 某个位置变成 1 之后, 最直接的影响就是其左右两边, 左右两边连续的 1 的长度都会变成新的总长度
  • 所以首先我们需要一个字典 iToLen, 键值对为{i:len}, 存储某个下标对应的连续 1 的长度, 用于动态更新
  • 其次我们还需要一个反向字典 lenToCnt, 键值对为{len:cnt}, 存储当前连续 1 长度对应的个数, 那么每次只要这个字典里 m 对应的 cnt 大于 0, 就说明仍有连续 1 长度为 m 的部分
  • 如果我们在每次把某个位置变成 1 之后都修改左右两边所有连续 1 的位置的 iToLen 字典, 这样时间复杂度就达到了 O(N^2), 大概率会超时
  • 但真的有必要修改所有下标吗? 答案是否定的, 其实我们只需要修改新的连续 1 的起点和终点的 iToLen 字典即可, 因为后面操作里新的 1 的左右两边绝不可能是当前连续 1 的中间部分, 只需要考虑两个边界就行, 这样就把这部分操作从 O(N)降到了 O(1)
  • 然后就是修改反向字典 lenToCnt 了, 这个也很简单, 就是拿到原来左右两侧连续 1 的长度 left 和 right, 将其对应的值各减去 left 和 right, 因为这些下标的长度都不再是原来的值了, 然后再把总长度 left+right+1 在 lenToCnt 字典中的值加上 left+right+1 即可
  • 下面代码对必要的步骤有详细的解释, 方便大家理解

复杂度

  • 时间复杂度 O(N): 只遍历了数组一遍
  • 空间复杂度 O(N): 使用了额外两个字典, 需要存 N 个元素

代码

class Solution:
    def findLatestStep(self, arr: List[int], m: int) -> int:
        n = len(arr)
        lenToCnt = collections.defaultdict(int)
        iToLen = {}
        res = -1
        for index, x in enumerate(arr):
            # 转成以0为起点的下标
            i = x - 1
            # 原来的左侧和右侧的连续1的长度
            left = 0
            right = 0
            # 新的连续1的起点和终点下标, 初始化为当前下标
            start = i
            end = i
            if i - 1 >= 0 and i - 1 in iToLen:
                # 更新左侧长度和起点下标
                left = iToLen[i - 1]
                start -= left
            if i + 1 < n and i + 1 in iToLen:
                # 更新右侧长度和终点下标
                right = iToLen[i + 1]
                end += right
            newlen = left + right + 1
            # 更新iToLen字典, 只需要更新两个边界即可
            iToLen[start] = newlen
            iToLen[end] = newlen
            # 更新lenToCnt字典, 减去旧长度的值, 加上新长度的值
            lenToCnt[left] -= left
            lenToCnt[right] -= right
            lenToCnt[newlen] += newlen
            if lenToCnt[m] > 0:
                # 如果仍有连续1长度为m的部分, 更新最终结果为当前arr下标+1
                res = index + 1
        return res

[5498] 石子游戏 V

题目思考

  1. 如何避免重复计算?

解决方案

思路

  • 一般对于这种博弈问题, 都可以先尝试用记忆化搜索的思路来解决, 这个题也不例外
  • 但这道题不需要双方做最优决策, 只需要 Alice 一个人来分, 所以不需要额外一个 flag 来判断当前是谁的回合
  • 直接模拟整个过程, 传入当前元组(转成元组的目的是可以作为 memo 字典的 key), 然后依次遍历当前元组, 动态求得左侧和右侧部分的和, 根据题目描述的情况继续递归, 最终求得最大值作为当前元组的最终结果, 加入 memo 字典中
  • 递归出口是元组长度为 1 的时候, 此时游戏结束, 直接返回 0 即可
  • 下面代码对必要的步骤有详细的解释, 方便大家理解

复杂度

  • 时间复杂度 O(N^3): memo 字典中最多存 N^2 个状态(对应长度为 2~N 的元组, 长度为 2 时有 N-1 个元组, 为 N 时有 1 个, 所以前 N-1 项和就是 N^2 数量级), 然后每次递归的时候需要遍历 N 个数
  • 空间复杂度 O(N^2): memo 字典需要存 2*N 个状态

代码

class Solution:
    def stoneGameV(self, stoneValue: List[int]) -> int:
        # 记忆化搜索
        memo = {}

        def getMx(t):
            if len(t) == 1:
                # 递归出口, 只有1个石头, 返回0
                return 0
            # 用memo字典存当前元组下的最大值, 避免重复计算
            if t not in memo:
                # 先计算当前的总和
                sm = sum(t)
                # 记录左侧行的当前和
                leftsm = 0
                mx = 0
                for i in range(len(t) - 1):
                    leftsm += t[i]
                    # 根据总和以及当前左侧和得到右侧和
                    rightsm = sm - leftsm
                    # 模拟题目的三种情况, 求不同分片下的最大值, 作为当前元组的最终结果
                    if leftsm < rightsm:
                        mx = max(mx, leftsm + getMx(t[:i + 1]))
                    elif leftsm == rightsm:
                        mx = max(mx, leftsm + getMx(t[:i + 1]),
                                 leftsm + getMx(t[i + 1:]))
                    else:
                        mx = max(mx, rightsm + getMx(t[i + 1:]))
                memo[t] = mx

            return memo[t]

        return getMx(tuple(stoneValue))

统计信息

通过次数 提交次数 AC比率
8905 15561 57.2%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
1575-统计所有可行路径(Count All Possible Routes)
下一篇:
1562-查找大小为 M 的最新分组(Find Latest Group of Size M)
本文目录
本文目录