队列
队列(Queue) 是一种基本的线性数据结构,它遵循先进先出(First In First Out, FIFO
)的原则。这意味着最先被添加到队列中的元素将会是最先被移除的。也就是在尾部添加元素,从头部移除元素,最新添加的元素排在末尾。
双端队列:collections.deque
deque是双端队列(double-ended queue)的缩写,在出队(pop)和入队(append)时的时间复杂度是O(1)。也可以直接用列表实现。
from collections import deque
append(x)
:添加 x 到右端appendleft(x)
:添加 x 到左端pop()
: 移去并且返回一个元素,deque 最右侧的那一一个popleft()
:移去并且返回一个元素,deque 最左侧的那一个insert(i, x)
:在位置i插入x。extend(iterable)
:扩展deque的右侧,通过添加iterable参数中的元素extendleft(iterable)
:扩展deque的左侧,通过添加iterable参数中的元素:注意,左添加时,在结果中iterable参数中的顺序将被反过来添加。remove(value)
:移除找到的第一个 value。clear()
: 清空copy()
:拷贝count(x)
:计算 deque 中元素等于x的个数.index(x[, start[, stop]])
:返回x在deque中的位置(在索引star之后,索引stop 之前)reverse()
:将deque逆序排列。rotate(n=1)
:向右循环移动n步。如果n是负数,就向左循环。maxlen
:Deque的最大尺寸,如果没有限定的话就是None
小R的随机播放顺序
问题描述
小R有一个特殊的随机播放规则。他首先播放歌单中的第一首歌,播放后将其从歌单中移除。如果歌单中还有歌曲,则会将当前第一首歌移到最后一首。这个过程会一直重复,直到歌单中没有任何歌曲。
例,给定歌单[5,3,2,1,4],真实的播放顺序是[5,2,4,1,3]。
保证歌曲中的id两两不同。
解题思路
-
初始化双端队列:使用
deque
来存储歌单。 -
从队列左侧出队:使用
queue.popleft()
来取出第一首歌播放,并加入结果列表。 -
左旋队列:左旋队列1次,以模拟将当前第一首歌移到最后一首。
-
重复操作:重复以上操作,直到队列为空。
from collections import deque
def solution(n: int, a: list) -> list:
# 初始化队列
queue = deque(a)
result = []
while queue:
# 取出第一首歌
song = queue.popleft()
result.append(song)
# 如果队列不为空,将当前第一首歌移到队列末尾
if queue:
queue.rotate(-1)
return result
小s的超辣火锅挑战
问题描述
小s和朋友们一共有
n
n
n个人,他们决定进行一次超辣火锅挑战,来决出最能吃辣的人。他们围成一圈,从1号开始依次顺时针数
k
k
k个,每数到一个人,这个人就得吃一口超辣火锅。每个人对辣度的耐受值不同,第
i
i
i个人的耐受值为
a
i
a_i
ai,表示他最多可以吃
a
i
a_i
ai口火锅。一旦吃完这
a
i
a_i
ai口火锅后,这个人就会因为承受不住而离席。
每次顺时针数
k
k
k个人时,离席的人不算在内。请你按顺序输出每个离席的人的编号,按照他们离席的顺序从早到晚排列。
解题思路
-
初始化双端队列:使用
deque
来存储每个人的序号和耐受度。 -
从队列左侧出队:使用
people.popleft()
来取出当前需要吃火锅的人。 -
重新加入队列右侧:如果耐受度大于0,使用
people.append()
将其重新加入队列;如果耐受度等于0,则该成员离席,将他的序号加入结果列表。 -
左旋队列:左旋队列k-1次,以模拟顺时针数k个。
-
重复操作:重复以上操作,直到队列为空。
from collections import deque
def solution(n: int, k: int, a: list) -> list:
# 初始化双端队列,存储每个人的序号和耐受度
people = deque([(i + 1, a[i]) for i in range(n)])
result = []
while people:
# 从队列左侧出队一个人
person = people.popleft()
person_id, tolerance = person
# 这个人吃一口火锅,耐受度减1
tolerance -= 1
# 如果这个人的耐受度仍然大于0,将其重新加入队列右侧
if tolerance > 0:
people.append((person_id, tolerance))
else:
# 如果这个人的耐受度变为0,将其序号加入结果列表
result.append(person_id)
# 左旋队列k-1次,以模拟顺时针数k个
if people:
people.rotate(1-k)
return result
优先队列:heapq
优先队列(Priority Queue) 是一种特殊的队列,除了具有队列的性质(先进先出,队列头出,队列尾入),还可以实现快速得到队列中优先级最高的元素。在优先队列中,每个元素都有一定的优先级,优先级最高的元素最先被移除。优先队列可以用于任何需要元素按照一定顺序处理的场景,例如操作系统任务调度、序列合并等
优先队列通常可以使用数组、链表、堆或二叉搜索树等数据结构来实现。其中,堆是最常用的底层数据结构,因为它可以在对数时间内插入和删除元素。
在 Python 中,可以使用 heapq
模块来实现优先队列。heapq
提供了一种基于堆的优先队列实现(默认最小堆)。堆是一种特殊的二叉树,满足父节点的值总是小于或等于其子节点的值(最小堆)或大于或等于其子节点的值(最大堆)的性质。每个节点都有两个子节点2k+1
,2k+2
。
import heapq
heapq.heapify(pq)
:将无序列表转换为优先队列pq(最小堆)heapq.heappush(pq, (priority, value))
:向优先队列pq中插入元素。这个函数会根据元素的值自动调整堆,保持堆的性质。这里(priority, value)
是一个元组,其中priority
是元素的优先级,value
是元素的值。优先级就是排序的一个标准。heapq.heappop(pq)
:从优先队列中弹出具有最高优先级的元素。这将返回一个元组(current_priority, current_value)
,其中current_priority
是弹出元素的优先级,current_value
是弹出元素的值。heapq.heaprepalce(pq,x)
: 弹出并返回最小元素,同时添加元素x。
数字替换后的最大和计算
问题描述
小C在白板上写了
n
n
n个数字,分别为 a_1,a_2,... ,a_n
。随后小R对这些数字进行了
m
m
m次修改操作。在每次操作中,小
R可以将白板上的某个数字替换为新数字
b
j
b_j
bj。每次修改的数字不需要是相同的,但只能替换一次。小C想知道,经过这
m
m
m次修改后,白板上所有数字之和的最大值是多少。
import heapq
def solution(n: int, m: int, a: list[int], b: list[int]) -> int:
# 将 a 转换为最小堆
heapq.heapify(a)
# 替换操作
for j in range(m):
current_b = b[j] #取出b中当前元素b_j
min_a = heapq.heappop(a) # 取出 a 中的最小元素
heapq.heappush(a, current_b) # 将 b 中的元素加入 a
return sum(a)
最小代价问题
问题描述
给定一个数组 a,每个元素的值代表当前的数字,数组中的第i个数加1的代价是b[i]。你需要找出最小的代价,使得数组a中的所有元素都各不相同。
解题思路
-
初始化优先队列:
- 首先,将数组
a
和b
中的元素组合成元组(value, cost)
,并将其放入一个列表pq
中。 - 然后,使用
heapq.heapify(pq)
将这个列表转换为一个最小堆。最小堆会根据value
的大小来排序。
- 首先,将数组
-
处理重复值:
- 使用一个集合
seen
来记录已经处理过的元素。 - 从最小堆中弹出堆顶元素
(value, cost)
。 - 如果
value
已经在seen
中,说明这个值已经处理过,需要将其值加1,并将其重新放回最小堆,同时累加代价cost
。 - 如果
value
不在seen
中,且当前堆中仍有元素,检查堆顶元素pq[0]
是否与当前value
相同。如果相同,说明有重复值,需要处理这些重复值。
- 使用一个集合
-
处理重复值:
- 当发现
value
与最小堆的堆顶元素pq[0]
相同时,比较当前cost
和堆顶元素的cost
,选择较小的代价进行累加,并将值加1后重新放回最小堆。 - 这个过程会一直持续,直到当前
value
不再与堆顶元素相同为止。
- 当发现
-
返回总代价:
- 最终,当最小堆为空时,返回累加的总代价
total_cost
。
- 最终,当最小堆为空时,返回累加的总代价
import heapq
def solution(n: int, a: list, b: list) -> int:
pq = [(a[i],b[i]) for i in range(n)]
heapq.heapify(pq)
total_cost = 0
seen = set() # 用于记录已经处理过的元素
while pq:
value,cost = heapq.heappop(pq)
if value in seen:
# 如果当前值已经处理过,直接将值+1放回最小堆,并累加代价
total_cost += cost
heapq.heappush(pq, (value + 1, cost))
else:
#如果当前值未处理过
if pq:
next_value, next_cost = pq[0]
#比较当前元素和最小堆的堆顶元素pq[0]的值
while value == next_value:
min_cost = min(cost,next_cost)
total_cost += min_cost
heapq.heappop(pq)
heapq.heappush(pq, (value + 1, min_cost))
cost = max(cost,next_cost)
next_value, next_cost = pq[0]
seen.add(value)
return total_cost
384 小C大作战得分计算
问题描述
在游戏"小C大作战"中,每个角色都有一个能量值a,,能量大的角色可以击败能量小的角色,并获得对应的分
数。每个角色被击败后,击败者可以获得b得分。为了维持游戏平衡,每个角色最多只能击败m个其他角
色。请计算出每个角色最终可以获得的最大分数。
保证这些球的能量值两两不同。
解题思路
-
初始化优先队列:
- 使用列表
characters
存储每个角色的能量值a_i
、得分b_i
和原始索引i
。 - 将
characters
转换为最小堆,以便按能量值从小到大处理角色。
- 使用列表
-
初始化结果列表:
- 创建一个长度为
n
的结果列表result
,用于存储每个角色的最终得分。 - 创建
scores
列表,用于存储之前处理过的角色的得分。
- 创建一个长度为
-
处理每个角色:
-
遍历每个角色,从最小堆中弹出当前角色。
-
计算当前角色的最大得分,并按照原始索引存入结果列表:对
scores
列表进行排序,并取前m
个最大得分进行求和。 -
将当前角色的得分加入
scores
列表。
-
-
返回结果:
- 最终返回
result
列表,其中包含每个角色的最大得分。
- 最终返回
import heapq
def solution(n: int, m: int, a: list, b: list) -> list:
# 将角色和他们的能量值、得分存储在一个列表中
characters = [(a[i], b[i],i) for i in range(n)]
heapq.heapify(characters)
# 初始化结果列表
result = [0]*n
scores = []
for i in range(n):
# 计算当前角色可以击败的其他角色的最大得分
# 将当前角色的得分加入结果
energy, score, index = heapq.heappop(characters)
result[index] = sum(sorted(scores, reverse=True)[:m])
# 将当前角色的得分加入得分列表
scores.append(score)
return result