队列是一种受限的线性结构,有着先进先出的特性,受限之处在于只允许在队列的前端进行删除操作,在队列的后端进行插入操作
下面基于数组实现一个队列(其实使用链表实现队列性能会更好一点,后续链表章节实现):
class ArrayQueue<T> {
private arr: T[] = [];
// 进队列
enqueue(element: T): void {
this.arr.push(element);
}
// 出队列
dequeue(): T | undefined {
return this.arr.shift();
}
// 队列头
front(): T | undefined {
return this.arr[0];
}
// 队列是否为空
isEmpty(): boolean {
return this.arr.length === 0;
}
// 队列长度
size(): number {
return this.arr.length;
}
// 清空队列
clear(): void {
this.arr = [];
}
// 打印队列
print(): void {
console.log(this.arr.toString());
}
}
队列练习
击鼓传花
游戏规则是这样的:几个朋友一起玩一个游戏,围成一圈,从1开始数数,数到某个数字的人自动淘汰,后面的人从1继续数,如此循环直到剩下最后一个人获得胜利,请问最后的赢家是谁?
假设函数签名是这样的function getWinner<T>(nameList: T[], num: number): T | undefined
思路:我们先把所有人都放入到队列中,然后开始循环,叫到数字小于num的人就从队列头部拿出来后再放进去,叫到num的人则直接拿出队列,如此循环直到队列长度为1,然后直接返回front就好了,下面具体实现:
function getWinner<T>(nameList: T[], num: number): T {
const queue = new ArrayQueue<T>();
// 先全部放入队列
nameList.forEach(name => {
queue.enqueue(name);
})
// 开始传花
while (queue.size() > 1) {
// 叫到小于n的都不用淘汰
for (let i = 1; i < num; i++) {
// 出队再入队
queue.enqueue(queue.dequeue()!);
}
// 叫到num的淘汰
queue.dequeue();
}
return queue.front()!;
}
圆圈中最后剩下的数字
这类题目在leetcode中有一道相似的题:0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。这道题其实跟击鼓传花几乎是一样的,但是当我们使用队列的方式求解会发现leetcode给出了超时的提示,可见使用队列求解时的时间复杂度过高。这个问题其实可以转化成数学问题然后使用动态规划的方式来求解,这里先给出答案,后续动态规划的章节再来详细解答:
var lastRemaining = function(n, m) {
let f = 0;
for (let i = 2; i != n + 1; ++i) {
f = (m + f) % i;
}
return f;
};