算法随笔-数据结构(队列)

122 阅读5分钟

算法随笔-数据结构(队列)

本文主要介绍数据结构中的队列的特点、使用场景、ES6 实现 Queue 类和题解 leetCode 真题。供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

队列可能是大家最熟悉的数据结构了,日常生活案例也不少,比如排队进地铁闸机口、排队进电影院、排队买票;对于代码而言,也很简单,就是数组里的一堆元素以排队的形式存储,然后按照先进先出的原则取出。

队列是先进先出(FIFO)的,它的插入(入队)和删除(出队)操作分别在队头和队尾进行,因此队列的插入和删除操作都是 O(1) 的时间复杂度。

队列的主要特点

数据结构中的队列是一种特殊的线性表,它具有以下特点:

  1. 先进先出(FIFO):队列中的元素按照它们被添加的顺序进行处理,即先进入队列的元素先被取出。
  2. 两端操作:队列的基本操作通常发生在两端,一端是队尾(用于添加元素,称为入队操作),另一端是队头(用于移除元素,称为出队操作)。
  3. 有序性:队列中的元素保持它们被添加时的顺序,直到它们被取出。
  4. 限制性访问:只能从队头取出元素,只能从队尾添加元素。

队列的应用场景

  • 任务调度:操作系统中的任务调度,按照任务到达的顺序执行。
  • 打印任务管理:打印机队列,按照任务提交的顺序打印文档。
  • 消息队列:在消息传递系统中,消息按照发送顺序进行处理。
  • 缓存系统:在某些缓存系统中,按照请求的顺序处理缓存项。

ES6实现队列类(Queue)

在ES6中,可以使用类(class)来实现一个简单的队列。下面是一个 Queue 类的实现示例:

class Queue {
  constructor() {
    this.items = []; // 使用数组来存储队列中的元素
  }

  // 入队操作,将元素添加到队列的末尾
  enqueue(element) {
    this.items.push(element);
  }

  // 出队操作,移除队列的第一个元素并返回它
  dequeue() {
    if (this.isEmpty()) {
      throw new Error('Queue is empty');
    }
    return this.items.shift();
  }

  // 查看队列的第一个元素
  front() {
    if (this.isEmpty()) {
      throw new Error('Queue is empty');
    }
    return this.items[0];
  }

  // 检查队列是否为空
  isEmpty() {
    return this.items.length === 0;
  }

  // 获取队列的大小
  size() {
    return this.items.length;
  }

  // 清空队列
  clear() {
    this.items = [];
  }

  // 打印队列中的元素
  print() {
    console.log(this.items.toString());
    return this.items.toString();
  }
}

// 使用Queue类
const queue = new Queue();
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);
queue.print(); // 输出: 1,2,3
console.log(queue.dequeue()); // 输出: 1
queue.print(); // 输出: 2,3

在这个Queue类中,我们定义了以下方法:

  • enqueue(element): 使用数组的 push 方法进行入队操作,将元素添加到队列的末尾。
  • dequeue(): 使用数组的 shift 方法进行出队操作,移除队列的第一个元素并返回它。
  • front(): 查看队列的第一个元素。
  • isEmpty(): 检查队列是否为空。
  • size(): 获取队列的大小。
  • clear(): 清空队列。
  • print(): 打印队列中的元素。

LeetCode真题

387. 字符串中的第一个唯一字符

给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。

示例 1

输入: s = "leetcode"

输出: 0

示例 2

输入: s = "loveleetcode"

输出: 2

示例 3

输入: s = "aabb"

输出: -1

题解

使用队列的入队和出队去做,参考如下:

/**
 * @param {string} s
 * @return {number}
 */
var firstUniqChar = function(s) {
  // 出队队列
  var queue = s.split('');
  // 入队队列
  var queue2 = [];
  // 存储第一个不重复的字符串
  var char = ''

  while (true) {
    // 如果队列为空, 跳出循环
    if (!queue.length) {
      break;
    }
    // 先执行出队
    const cur = queue.shift()
    // 判断出队队列中没有该字符 且 入队队列也没有该字符
    if (!queue.includes(cur) && !queue2.includes(cur)) {
      char = cur
      break;
    }
    // 入队
    queue2.push(cur)
  }
  
  return char === '' ? -1 : s.indexOf(char)
};

当然本题用队列去做,比较复杂了,下面是个更简单的,内存占用更小的:

var firstUniqChar = function(s) {
  for (let i = 0; i < s.length; i++) {
    if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) {
      return i;
    }
  }
  return -1
};

1823. 找出游戏的获胜者

共有 n 名小伙伴一起做游戏。小伙伴们围成一圈,按 顺时针顺序 从 1 到 n 编号。确切地说,从第 i 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置,其中 1 <= i < n ,从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置。

游戏遵循如下规则:

  1. 从第 1 名小伙伴所在位置 开始 。
  2. 沿着顺时针方向数 k 名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。
  3. 你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。
  4. 如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。
  5. 否则,圈子中最后一名小伙伴赢得游戏。

给你参与游戏的小伙伴总数 n ,和一个整数 k ,返回游戏的获胜者。

图片

输入:n = 5, k = 2

输出:3

解释:游戏运行步骤如下:

  1. 从小伙伴 1 开始。

  2. 顺时针数 2 名小伙伴,也就是小伙伴 1 和 2 。

  3. 小伙伴 2 离开圈子。下一次从小伙伴 3 开始。

  4. 顺时针数 2 名小伙伴,也就是小伙伴 3 和 4 。

  5. 小伙伴 4 离开圈子。下一次从小伙伴 5 开始。

  6. 顺时针数 2 名小伙伴,也就是小伙伴 5 和 1 。

  7. 小伙伴 1 离开圈子。下一次从小伙伴 3 开始。

  8. 顺时针数 2 名小伙伴,也就是小伙伴 3 和 5 。

  9. 小伙伴 5 离开圈子。只剩下小伙伴 3 。所以小伙伴 3 是游戏的获胜者。

题解

/**
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
var findTheWinner = function (n, k) {
  // 生成数组,原始队列
  const queue = [];
  for (let i = 1; i <= n; i++) {
    queue.push(i);
  }
  // 循环队列
  while (queue.length > 1) {
    // 循环入队
    for (let i = 1; i < k; i++) {
      console.log(queue);
      console.log(i);
      // 把之前出队的入队到
      queue.push(queue.shift());
    }
    // 出队
    queue.shift();
  }

  return queue[0];
};