基础数据结构(二):线性结构(队列)

62 阅读2分钟

队列是一种受限的线性结构,有着先进先出的特性,受限之处在于只允许在队列的前端进行删除操作,在队列的后端进行插入操作

image.png 下面基于数组实现一个队列(其实使用链表实现队列性能会更好一点,后续链表章节实现):

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;
};