学习笔记:特殊随机播放规则问题

100 阅读5分钟

问题描述

小R有一个特殊的随机播放规则:

  1. 播放歌单中的第一首歌,并将其移除。
  2. 如果歌单中还有歌曲,将当前第一首歌移到歌单末尾。
  3. 重复上述步骤,直到歌单为空。

问题分析

  • 每次操作包含两步:
    1. 播放并移除当前第一首歌
    2. 如果歌单还有歌曲,将新的第一首歌移到末尾
  • 任务是模拟整个播放过程,并输出最终的播放顺序。

输入输出

  • 输入:
    • n:歌单中歌曲的数量。
    • a:长度为 n 的数组,表示歌单中歌曲的 ID。
  • 输出:
    • 一个数组,表示按照规则播放歌曲后的顺序。

解题思路

  1. 初始化:
    • 用一个列表 play_order 记录播放顺序。
  2. 模拟操作:
    • 播放(移除并记录)当前第一首歌。
    • 如果剩余歌单不为空,将当前第一首歌移动到队尾。
  3. 终止条件:
    • 当歌单为空时停止操作。
  4. 返回结果:
    • 输出记录的播放顺序。

实现代码

以下是实现代码及详细注释:

def solution(n: int, a: list) -> list:
    # 初始化播放顺序列表
    play_order = []
    
    # 模拟播放规则
    while a:
        # 播放第一首歌并移除
        play_order.append(a.pop(0))
        
        # 如果还有剩余歌曲,将第一首移到队尾
        if a:
            a.append(a.pop(0))
    
    return play_order

# 测试代码
if __name__ == '__main__':
    print(solution(n=5, a=[5, 3, 2, 1, 4]) == [5, 2, 4, 1, 3])  # 示例 1
    print(solution(n=4, a=[4, 1, 3, 2]) == [4, 3, 1, 2])        # 示例 2
    print(solution(n=6, a=[1, 2, 3, 4, 5, 6]) == [1, 3, 5, 2, 6, 4])  # 示例 3

关键知识点

  1. 列表操作:
    • pop(0):移除列表中的第一个元素。
    • append():向列表末尾添加元素。
  2. 循环条件:
    • 当列表 a 不为空时,继续执行操作。

算法复杂度

  1. 时间复杂度:
    • 每次 pop(0) 的时间复杂度为 O(n)O(n)O(n),因为需要移动所有后续元素。
    • 每次 append() 的时间复杂度为 O(1)O(1)O(1)。
    • 总的时间复杂度为 O(n2)O(n^2)O(n2)。
    • 如果需要优化时间复杂度,可以使用双端队列 collections.deque,将时间复杂度降低为 O(n)O(n)O(n)。
  2. 空间复杂度:
    • O(n)O(n)O(n),因为需要存储播放顺序。

优化实现

通过使用双端队列来减少 pop(0) 的时间复杂度:

from collections import deque

def solution(n: int, a: list) -> list:
    queue = deque(a)  # 使用双端队列
    play_order = []
    
    while queue:
        # 播放第一首歌
        play_order.append(queue.popleft())
        
        # 如果还有剩余歌曲,将第一首移到队尾
        if queue:
            queue.append(queue.popleft())
    
    return play_order

# 测试代码
if __name__ == '__main__':
    print(solution(n=5, a=[5, 3, 2, 1, 4]) == [5, 2, 4, 1, 3])  # 示例 1
    print(solution(n=4, a=[4, 1, 3, 2]) == [4, 3, 1, 2])        # 示例 2
    print(solution(n=6, a=[1, 2, 3, 4, 5, 6]) == [1, 3, 5, 2, 6, 4])  # 示例 3

优化后的复杂度

  • 时间复杂度:O(n)O(n)O(n)。
  • 空间复杂度:O(n)O(n)O(n)。

测试结果

验证输出是否符合预期:

True
True
True

总结

  1. 本题通过模拟规则实现,核心在于灵活运用列表操作或双端队列。
  2. 双端队列是优化时间复杂度的关键,适合处理频繁的首尾操作。
  3. 对于类似的循环处理问题,理解队列的特性非常重要。

常见的队列问题

队列是一种**先进先出(FIFO)**的数据结构,广泛应用于各种算法问题中。以下是一些常见的队列相关算法问题以及其解决思路:


1. 滑动窗口最大值

问题描述 在一个数组中,用固定大小 kkk 的滑动窗口滑过每个位置,求每个窗口中的最大值。

解题思路

  • 使用双端队列(deque)存储当前窗口内的元素索引,并维护一个单调递减队列(存储的索引对应的值从大到小排列)。
  • 在每次滑动窗口时:
    1. 移除不在窗口范围内的队头元素。
    2. 将新的元素按大小插入队列,移除比它小的元素。
    3. 队头的索引始终指向当前窗口的最大值。

2. 任务调度

问题描述 对于一组任务,如果相同任务之间有冷却时间 nnn,求完成所有任务的最短时间。

解题思路

  • 使用优先队列或队列模拟任务执行过程。
  • 每次从任务池中选择未冷却的任务优先执行,将执行过的任务加入冷却队列,直到冷却时间结束后重新加入任务池。

3. 模拟队列旋转

问题描述 给定一个队列,可以将队首元素移动到队尾或从队首删除,求按顺序删除目标元素所需的最小操作次数。

解题思路

  • 维护一个双端队列。
  • 对于每个目标值,计算它在队列中的索引位置,根据索引判断是左移(队列前端)还是右移(队列后端)操作次数更少。
  • 删除目标元素后继续对剩余队列重复操作。

4. 打开转盘锁

问题描述 一个 4 位数的转盘锁,每次可以拨动一个数字向上或向下,目标是从初始状态 "0000" 拨动到给定目标状态,且某些状态为“死锁”(不可到达)。求最小拨动次数。

解题思路

  • 使用广度优先搜索(BFS):
    1. 初始状态作为根节点,每次拨动锁的一个数字生成一个新状态。
    2. 将“死锁”状态作为访问过的节点,避免进入。
    3. 找到目标状态时结束搜索,并记录步数。

5. 计算数组操作次数

问题描述 给定一个数组,每次将首元素处理后对其值做特定修改,或者将其删除。求数组操作至空时的总操作次数。

解题思路

  • 使用队列模拟操作。
  • 每次处理队首元素,根据规则决定是否修改或移除:
    1. 如果值等于 0,则移除队首。
    2. 如果值不为 0,则更新值后将其重新插入队尾。
  • 重复以上步骤直到队列为空。