问题描述
小R有一个特殊的随机播放规则:
- 播放歌单中的第一首歌,并将其移除。
- 如果歌单中还有歌曲,将当前第一首歌移到歌单末尾。
- 重复上述步骤,直到歌单为空。
问题分析
- 每次操作包含两步:
- 播放并移除当前第一首歌。
- 如果歌单还有歌曲,将新的第一首歌移到末尾。
- 任务是模拟整个播放过程,并输出最终的播放顺序。
输入输出
- 输入:
n:歌单中歌曲的数量。a:长度为n的数组,表示歌单中歌曲的 ID。
- 输出:
- 一个数组,表示按照规则播放歌曲后的顺序。
解题思路
- 初始化:
- 用一个列表
play_order记录播放顺序。
- 用一个列表
- 模拟操作:
- 播放(移除并记录)当前第一首歌。
- 如果剩余歌单不为空,将当前第一首歌移动到队尾。
- 终止条件:
- 当歌单为空时停止操作。
- 返回结果:
- 输出记录的播放顺序。
实现代码
以下是实现代码及详细注释:
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
关键知识点
- 列表操作:
pop(0):移除列表中的第一个元素。append():向列表末尾添加元素。
- 循环条件:
- 当列表
a不为空时,继续执行操作。
- 当列表
算法复杂度
- 时间复杂度:
- 每次
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)。
- 每次
- 空间复杂度:
- 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
总结
- 本题通过模拟规则实现,核心在于灵活运用列表操作或双端队列。
- 双端队列是优化时间复杂度的关键,适合处理频繁的首尾操作。
- 对于类似的循环处理问题,理解队列的特性非常重要。
常见的队列问题
队列是一种**先进先出(FIFO)**的数据结构,广泛应用于各种算法问题中。以下是一些常见的队列相关算法问题以及其解决思路:
1. 滑动窗口最大值
问题描述 在一个数组中,用固定大小 kkk 的滑动窗口滑过每个位置,求每个窗口中的最大值。
解题思路
- 使用双端队列(
deque)存储当前窗口内的元素索引,并维护一个单调递减队列(存储的索引对应的值从大到小排列)。 - 在每次滑动窗口时:
- 移除不在窗口范围内的队头元素。
- 将新的元素按大小插入队列,移除比它小的元素。
- 队头的索引始终指向当前窗口的最大值。
2. 任务调度
问题描述 对于一组任务,如果相同任务之间有冷却时间 nnn,求完成所有任务的最短时间。
解题思路
- 使用优先队列或队列模拟任务执行过程。
- 每次从任务池中选择未冷却的任务优先执行,将执行过的任务加入冷却队列,直到冷却时间结束后重新加入任务池。
3. 模拟队列旋转
问题描述 给定一个队列,可以将队首元素移动到队尾或从队首删除,求按顺序删除目标元素所需的最小操作次数。
解题思路
- 维护一个双端队列。
- 对于每个目标值,计算它在队列中的索引位置,根据索引判断是左移(队列前端)还是右移(队列后端)操作次数更少。
- 删除目标元素后继续对剩余队列重复操作。
4. 打开转盘锁
问题描述
一个 4 位数的转盘锁,每次可以拨动一个数字向上或向下,目标是从初始状态 "0000" 拨动到给定目标状态,且某些状态为“死锁”(不可到达)。求最小拨动次数。
解题思路
- 使用广度优先搜索(BFS):
- 初始状态作为根节点,每次拨动锁的一个数字生成一个新状态。
- 将“死锁”状态作为访问过的节点,避免进入。
- 找到目标状态时结束搜索,并记录步数。
5. 计算数组操作次数
问题描述 给定一个数组,每次将首元素处理后对其值做特定修改,或者将其删除。求数组操作至空时的总操作次数。
解题思路
- 使用队列模拟操作。
- 每次处理队首元素,根据规则决定是否修改或移除:
- 如果值等于 0,则移除队首。
- 如果值不为 0,则更新值后将其重新插入队尾。
- 重复以上步骤直到队列为空。