学习笔记:特殊的随机播放规则
题目描述
小R使用一种特殊的随机播放规则:
- 播放歌单中第一首歌,并将其移除。
- 如果歌单中还有歌,则将新的第一首歌移到歌单末尾。
- 重复上述过程,直到歌单为空。
输入
- 一个整数
n,表示歌曲数量。 - 一个长度为
n的数组a,表示歌单中歌曲的 ID。
输出
- 一个数组,表示小R的真实播放顺序。
题目解析
1. 思路分析
-
模拟规则:题目本质是模拟歌单的播放顺序和移动过程。每次都从歌单头部弹出一首歌(模拟播放),然后检查剩余的歌曲是否需要移动。
-
数据结构选择:
- 数组模拟:需要频繁对数组首尾操作,效率较低。
- 双端队列(deque) :支持 O(1) 的头部弹出和尾部添加,非常适合这种规则的模拟。
-
算法步骤:
- 初始化一个双端队列存储歌单。
- 每次从队列头部弹出一首歌,加入结果。
- 如果队列非空,则将新的第一首歌移动到队列尾部。
- 重复上述操作直到队列为空。
2. 示例分析
示例1:n=5, a=[5, 3, 2, 1, 4]
- 初始化:
playlist = deque([5, 3, 2, 1, 4]),result = [] - 第1步:播放并移除
5,结果[5],剩余[3, 2, 1, 4]-> 将3移到末尾 ->[2, 1, 4, 3] - 第2步:播放并移除
2,结果[5, 2],剩余[1, 4, 3]-> 将1移到末尾 ->[4, 3, 1] - 第3步:播放并移除
4,结果[5, 2, 4],剩余[3, 1]-> 将3移到末尾 ->[1, 3] - 第4步:播放并移除
1,结果[5, 2, 4, 1],剩余[3]-> 将3移到末尾 ->[3] - 第5步:播放并移除
3,结果[5, 2, 4, 1, 3],队列为空。
输出:[5, 2, 4, 1, 3]
代码实现
from collections import deque
def solution(n: int, a: list) -> list:
# 初始化一个双端队列
playlist = deque(a)
result = []
# 模拟随机播放规则
while playlist:
# 播放并移除第一首歌
result.append(playlist.popleft())
# 如果队列非空,将新的第一首歌移到末尾
if playlist:
playlist.append(playlist.popleft())
return result
# 测试用例
if __name__ == '__main__':
print(solution(5, [5, 3, 2, 1, 4]) == [5, 2, 4, 1, 3])
print(solution(4, [4, 1, 3, 2]) == [4, 3, 1, 2])
print(solution(6, [1, 2, 3, 4, 5, 6]) == [1, 3, 5, 2, 6, 4])
知识总结
1. 数据结构:双端队列(deque)
双端队列是解决队列头部和尾部操作的高效工具,支持以下操作:
popleft():从队列头部弹出元素,时间复杂度 O(1)。append(x):将元素添加到队列尾部,时间复杂度 O(1)。
2. 模拟问题
- 模拟类问题通常需要选择合适的数据结构(如队列、栈)简化操作。
- 通过模拟过程理解规则,再设计高效的实现。
3. 算法复杂度
- 时间复杂度:每首歌最多操作两次(移出队列 + 移到尾部),总复杂度 O(n)。
- 空间复杂度:使用双端队列存储 n 首歌,复杂度为 O(n)。
学习建议
- 灵活选择数据结构:根据题目要求,选用合适的数据结构可以显著提升性能。多练习队列、栈相关题目,培养对数据结构的直觉。
- 手动模拟过程:在思考复杂规则时,先手动模拟小样例,理清思路后再编写代码。
- 实践优化:初始解法可用列表模拟,但性能较差;在理解性能瓶颈后尝试使用更高效的工具(如 deque)。
图解
| 步骤 | 队列内容 | 播放结果 |
|---|---|---|
| 初始化 | [5, 3, 2, 1, 4] | [] |
| 第1步 | [3, 2, 1, 4] | [5] |
| 第2步 | [2, 1, 4, 3] | [5, 2] |
| 第3步 | [1, 4, 3] | [5, 2, 4] |
| 第4步 | [4, 3, 1] | [5, 2, 4, 1] |
| 第5步 | [3] | [5, 2, 4, 1, 3] |