TypeScript_队列结构-链表
队列
队列(Queue),它是一种受限的线性表,先进先出(FIFO First In First Out)
- 受限之处在于它只允许在队列的前端(front)进行删除操作
- 而在队列的后端(rear)进行插入操作
队列
- 有五份文档需要打印,这些文档会按照次序放入到打印队列中
- 打印机会依次从队列中取出文档,优先放入的文档,优先被取出,并且对该文档进行打印
- 以此类推,直到队列中不再有新的文档
线程队列:
- 在开发中,为了让任务可以并行处理,通常会开启多个线程
- 但是,我们不能让大量的线程同时运行处理任务。(占用过多的资源)
- 这个时候,如果有需要开启线程处理任务的情况,我们就会使用线程队列
- 线程队列会依照次序来启动线程,并且处理对应的任务
当然队列还有很多其他应用,我们后续的很多算法中也会用到队列(比如二叉树的层序遍历)
队列操作
- enqueue(element):向队列尾部添加一个(或多个)新的项
- dequeue():移除队列的第一(即排在队列最前面的)项,并返回被移除的元素
- front/peek():返回队列中第一个元素————最先被添加,也将是最先被移除的元素。队列不做任何变动 (不移除元素,只返回元素信息一一与 Stack 类的 peek 方法非常类似)
- isEmpty():如果队列中不包含任何元素,返回 true,否则返回 false
- size():返回队列包含的元素个数,与数组的 length 属性类似
实现队列
队列的实现和栈一样,有两种方案:
- 基于 数组 实现
- 基于 链表 实现
import IQueue from './IQueue'class ArrayQueue<T> implements IQueue<T> {// 内部是通过数组保存private data: T[] = []enqueue(element: T): void {this.data.push(element)}dequeue(): T | undefined {return this.data.shift()}peek(): T | undefined {return this.data[0]}isEmpty(): boolean {return this.data.length === 0}size(): number {return this.data.length}
}export default ArrayQueue
泛型
interface IList<T> {peek(): T | undefinedisEmpty(): booleansize(): number
}
interface IQueue<T> extends IList<T> {enqueue(element: T): voiddequeue(): T | undefined
}
击鼓传花
原游戏规则
- 班级中玩一个游戏,所有学生围成一圈,从某位同学手里开始向旁边的同学传一束花
- 这个时候某个人(比如班长),在击鼓,鼓声停下的一颗,花落在谁手里,谁就出来表演节目
修改游戏规则
- 我们来修改一下这个游戏规则
- 几个朋友一起玩一个游戏,围成一圈,开始数数,数到某个数字的人自动淘汰
- 最后剩下的这个人会获得胜利,请问最后剩下的是原来在哪一个位置上的人
封装一个基于队列的函数
- 参数:所有参与人的姓名,基于的数字口结果:最终剩下的一人的姓名
function hotPotato(names: string[], num: number) {if (names.length === 0) return -1// 1.创建队列结构const queue = new ArrayQueue<string>()// 2.将所有的name入队操作for (const name of names) {queue.enqueue(name)}// 3.淘汰规则while (queue.size() > 1) {for (let i = 1; i < num; i++) {const name = queue.dequeue()if (name) queue.enqueue(name)}queue.dequeue()}return queue.dequeue()
}
约瑟夫环问题
阿桥问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环
- 人们站在一个等待被处决的圈子里
- 计数从圆圈中的指定点开始,并沿指定方向围绕圆圈进行
- 在跳过指定数量的人之后,处刑下一个人
- 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放
- 在给定数量的情况下,站在第几个位置可以避免被处决?
这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家口
- 他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中
- 他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁
剑指 Offer 62. 圆圈中最后剩下的数字
0,1,···,n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字
例如,0、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、1,因此最后剩下的数字是 3
import ArrayQueue from './queue'function lastRemaining(n: number, m: number) {// 1.创建队列const queue = new ArrayQueue<number>()// 2.将所有的数字加入队列中for (let i = 0; i < n; i++) {queue.enqueue(i)}// 3.判断队列中是否还有数字while (queue.size() > 1) {for (let i = 1; i < m; i++) {queue.enqueue(queue.dequeue()!)}queue.dequeue()}return queue.dequeue()
}console.log(lastRemaining(5, 3)) // 3
console.log(lastRemaining(10, 17)) // 2
动态规划
function lastRemaining(n: number, m: number) {let position = 0for (let i = 2; i <= n; i++) {position = (position + m) % i}return position
}
链表
链表与数组
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同
数组有很多缺点:
- 数组的创建通常需要申请一段 连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组 不能满足容量需求 时,需要 扩容。(一般情况下是申请一个更大的数组,比如 2 倍。然后将原数组中的元素复制过去)
- 而且在 数组开头或中间位置插入数据的成本很高,需要 进行大量元素的位移
- 尽管 JavaScript 的 Array 底层可以帮我们做这些事,但背后的原理依然是这样
要存储多个元素,另外一个选择就是链表
- 但不同于数组,链表中的元素在内存中不必是连续的空间
- 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成
相对于数组,链表有一些优点:
- 内存空间不是必须连续的
- 可以充分利用计算机的内存,实现灵活的内存动态管理口
- 链表不必在创建时就 确定大小,并且大小可以 无限的延伸 下去口
- 链表在 插入和删除 数据时,时间复杂度 可以达到 O(1)
- 相对数组效率高很多
相对于数组,链表有一些缺点:
- 链表访问任何一个位置的元素时,都需要 从头开始访问。(无法跳过第一个元素访问任何一个元素)
- 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素
链表
什么是链表呢?
- 其实上面我们已经简单的提过了链表的结构,我们这里更加详细的分析一下
- 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推


实现链表
- 封装一个 Node 类,用于封装每一个节点上的信息 (包括值和指向下一个节点的引用),它是一个泛型类
- 封装一个 LinkedList 类,用于表示我们的链表结构。(和 Java 中的链表同名,不同 Java 中的这个类是一个双向链表)
- 链表中我们保存两个属性,一个是链表的长度,一个是链表中第一个节点
class Node<T> {value: Tnext: Node<T> | null = nullconstructor(value: T) {this.value = value}
}class LinkedList<T> {private head: Node<T> | null = nullprivate size: number = 0get length() {return this.size}
}
链表操作
- append(element):向链表尾部添加一个新的项
- insert(position,element):向链表的特定位置插入一个新的项
- get(position):获取对应位置的元素indexOf(element): 返回元素在链表中的索引。如果链表中没有该元素则返回 -1
- update(position,element):修改某个位置的元素
- removeAt(position):从链表的特定位置移除一项
- remove(element):从链表中移除一项
- isEmpty():如果链表中不包含任何元素,返回 true,如果链表长度大于 0 则返回 false
- size():返回链表包含的元素个数。与数组的 length 属性类似
向链表尾部追加数据可能有两种情况
- 链表本身为空,新添加的数据时唯一的节点
- 链表不为空,需要向其他节点后面追加节点
class Node<T> {value: Tnext: Node<T> | null = nullconstructor(value: T) {this.value = value}
}class LinkedList<T> {private head: Node<T> | null = nullprivate size: number = 0get length() {return this.size}// 根据position获取到当前的节点(不是节点的value,而是获取节点)private getNode(position: number): Node<T> | null {let index = 0let current = this.headwhile (index++ < position && current) {current = current.next}return current}append(value: T) {// 1.根据value创建一个新及诶单const newNode = new Node(value)// 2.判断this.heade是否为nullif (!this.head) {this.head = newNode} else {let current = this.headwhile (current.next) {current = current.next}current.next = newNode}this.size++}traverse() {const values: T[] = []let current = this.headwhile (current) {values.push(current.value)current = current.next}console.log(values.join('->'))}insert(value: T, position: number): boolean {// 1.越界的判断if (position < 0 || position > this.size) return false// 2.根据value创建新的节点const newNode = new Node(value)// 3.判断是否需要插入头部if (position === 0) {// 新节点next指向头部节点、头部newNode.next = this.headthis.head = newNode} else {const previous = this.getNode(position - 1)newNode.next = previous?.next ?? nullprevious!.next = newNode}this.size++return true}removeAt(position: number): T | null {// 1.越界的判断if (position < 0 || position >= this.size) return null// 2.判断是否是删除第一个节点let current = this.headif (position === 0) {this.head = current?.next ?? null} else {const previous = this.getNode(position - 1)previous!.next = previous?.next?.next ?? null}this.size--return current?.value ?? null}get(position: number): T | null {// 1.越界的判断if (position < 0 || position >= this.size) return null// 2.查找元素,并且返回元素return this.getNode(position)?.value ?? null}update(value: T, position: number): boolean {if (position < 0 || position >= this.size) return falseconst currentNode = this.getNode(position)// 获取对应位置的节点,直接更新即可currentNode!.value = valuereturn true}indexOf(value: T): number {let current = this.headlet index = 0while (current) {if (current.value === value) {return index}current = current.nextindex++}return -1}remove(value: T): T | null {const index = this.indexOf(value)return this.removeAt(index)}isEmpty() {return this.size === 0}
}
设计链表
707. 设计链表
你可以选择使用单链表或者双链表,设计并实现自己的链表
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用
如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始
实现 MyLinkedList 类:
MyLinkedList()初始化MyLinkedList对象int get(int index)获取链表中下标为index的节点的值。如果下标无效,则返回-1void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点void addAtTail(int val)将一个值为val的节点追加到链表中作为链表的最后一个元素void addAtIndex(int index, int val)将一个值为val的节点插入到链表中下标为index的节点之前。如果index等于链表的长度,那么该节点会被追加到链表的末尾。如果index比长度更大,该节点将 不会插入 到链表中void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点
抽离接口方法
interface IList<T> {peek(): T | undefinedisEmpty(): booleansize(): number
}interface ILinkedList<T> extends IList<T> {append(value: T): voidtraverse(): voidinsert(value: T, position: number): booleanget(position: number): T | nullupdate(value: T, position: number): booleanindexOf(value: T): numberremove(value: T): T | null
}
删除链表中的节点
237. 删除链表中的节点
有一个单链表的 head,我们想删除它其中的一个节点 node
给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head
链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
- 给定节点的值不应该存在于链表中
- 链表中的节点数应该减少 1
node前面的所有值顺序相同node后面的所有值顺序相同
class ListNode {val: numbernext: ListNode | nullconstructor(val?: number, next?: ListNode | null) {this.val = val === undefined ? 0 : valthis.next = next === undefined ? null : next}
}function deleteNode(node: ListNode | null): void {node!.val = node!.next!.valnode!.next = node!.next!.next
}
反转链表
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表
利用栈结构解决
function reverseList(head: ListNode | null): ListNode | null {if (head === null) return nullif (head.next === null) return headconst stack: ListNode[] = []let current: ListNode | null = headwhile (current) {stack.push(current)current = current.next}let newHead: ListNode = stack.pop()!let newHeadCurrent = newHeadwhile (stack.length) {const node = stack.pop()!newHeadCurrent.next = nodenewHeadCurrent = newHeadCurrent.next}// 注意:获取到最后一个节点时,一定要将节点的 next 置为 nullnewHeadCurrent.next = nullreturn newHead
}const node1 = new ListNode(1)
node1.next = new ListNode(2)
node1.next.next = new ListNode(3)
const newHead = reverseList(node1)
let current = newHead
while (current) {console.log(current.val)current = current.next
}
非递归方式
-
让 current 指向下一个节点
- 目的:保留着下一个节点的引用,可以拿到,并且不会销毁
-
改变 head 当前指向的节点,指向 newHead
- 对于第一个节点来说,指向 newHead 就是指向 null
-
让 newHead 指向 head 节点
- 目的是下一次遍历时,第二步操作,可以让下一个节点指向第一个节点
-
让 head 移向下一个节点,指向 current
function reverseList(head: ListNode | null): ListNode | null {if (head === null || head.next === null) return headlet newHead: ListNode | null = nullwhile (head) {let current: ListNode | null = head.nexthead.next = newHeadnewHead = headhead = current}return newHead
}

递归方式
- 第一次进入
const newHead下面的代码是倒数第二个节点,因为倒数第一个节点的 next 为 null
function reverseList(head: ListNode | null): ListNode | null {if (head === null || head.next === null) return headconst newHead = reverseList(head.next)head.next.next = headhead.next = nullreturn newHead
}
相关文章:
TypeScript_队列结构-链表
队列 队列(Queue),它是一种受限的线性表,先进先出(FIFO First In First Out) 受限之处在于它只允许在队列的前端(front)进行删除操作而在队列的后端(rear)进…...
STM32G0 定时器PWM DMA输出驱动WS2812配置 LL库
通过DMA方式输出PWM模拟LED数据信号 优点:不消耗CPU资源 缺点:占用内存较大 STM32CUBEMX配置 定时器配置 定时器通道:TIM3 CH2 分频:0 重装值:79,芯片主频64Mhz,因此PWM输出频率:…...
记录错误:Access denied for user ‘root‘@‘localhost‘ (using password:No) 解决方案
他说我没输入密码,但是我输入了啊??于是,我试了试这儿,password 一改就好了。。。 他原来是是我打的很快,快速生成的。。。。...
python爬虫实战(5)--获取小破站热榜
1. 分析地址 打开小破站热榜首页,查看响应找到如下接口地址 2. 编码 定义请求头 拿到标头 复制粘贴,处理成json 处理请求头代码如下: def format_headers_to_json():f open("data.txt", "r", encoding"utf-8") # 读…...
单目标应用:基于麻雀搜索算法SSA的微电网优化调度MATLAB
一、微网系统运行优化模型 参考文献: [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、麻雀搜索算法简介 麻雀搜索算法 (Sparrow Search Algorithm, SSA) 是一种新型的群智能优化算法,于2020…...
C# easymodbus
库介绍 EasyModbus是用于 .NET 和 Java 平台上的Modbus TCP/UDP/RTU通讯协议库,支持多种编程语言,如C#、VB.NET、Java、C 与更多C#的变体,如Unity、Mono、.NET Core等等。 EasyModbus的Java版本至少需要Java 7,而C#版本兼容 .NE…...
HikariCP源码修改,使其连接池支持Kerberos认证
HikariCP-4.0.3 修改HikariCP源码,使其连接池支持Kerberos认证 修改后的Hikari源码地址:https://github.com/Raray-chuan/HikariCP-4.0.3 Springboot使用hikari连接池并进行Kerberos认证访问Impala的demo地址:https://github.com/Raray-chuan/springboot-kerberos-hikari-im…...
5分钟看明白rust mod use
rust把mod简单的事没说清,一片混乱,似懂非懂. mod语句查找只有一条规则:先找mod名1.rs,没有就我同名文件夹下的mod名1.rs,如果没有,就同名文件夹下的mod名1/mod.rs,再没有就error. 在mod.rs中,pub mod 文件…...
【Java核心知识】ThreadLocal相关知识
ThreadLocal 什么是ThreadLocal ThreadLoacal类可以为每个线程保存一份独有的变量,该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMap,key为变量的对应的ThreadLocal对象,value为变量的值。 在日常使用中&…...
《Python基础教程(第三版)》阅读笔记 1
目录 1 快速上手:基础知识2 列表和元组3 字符串4 字典5 条件、循环及其他6 抽象7 再谈抽象8 异常9 魔法方法、特性和迭代器10 开箱即用 本文参考自《Beginning Python: from novice to professional》,中文版为《Python基础教程(第三版&#…...
坦克400 Hi4-T预售价28.5万元起,越野新能源好理解
8月25日,在以“智享蓉城,驭见未来”为主题的成都国际车展上,坦克品牌越野新能源再启新程,首次以全Hi4-T新能源阵容亮相展台,释放坦克品牌加速布局越野新能源的强烈信号。 Hi4-T架构首款落地车型坦克500 Hi4-T上市至今斩…...
我的Vim学习笔记(不定期更新)
2023年9月3日,周日上午 学到了啥就写啥,不定期更新 目录 字体 文件 标签页 分屏 调用系统命令 字体 设置字体大小 :set guifont字体:h字体大小 例如,:set guifontMonospace:h20 查询当前使用的字体和字体大小 :set guifont? 查看…...
spring boot项目生成容器并运行
一个安静的周末,shigen又睡懒觉了,上次说的拖延症的惩罚来了:早晚各100个健腹轮练习,早上的已经完成了。今天的文章来的有点晚,但是依旧保持质量。 springboot项目生成容器并运行 背景 将springboot项目打包成jar包&…...
Vue之html中特殊符号的展示
Vue之html中特殊符号的展示 在html中使用特殊字符时直接展示会报错,需要使用实体名称或者实体编号才能展示。 最常用的字符实体 显示结果 描述 实体名称 实体编号空格 < 小于号 < &…...
数据结构1 -- leetcode练习
三. 练习 3.1 时间复杂度 用函数 f ( n ) f(n) f(n) 表示算法效率与数据规模的关系,假设每次解决问题需要 1 微秒( 1 0 − 6 10^{-6} 10−6 秒),进行估算: 如果 f ( n ) n 2 f(n) n^2 f(n)n2 那么 1 秒能解决多…...
Java设计模式:四、行为型模式-05:备忘录模式
文章目录 一、定义:备忘录模式二、模拟场景:备忘录模式三、改善代码:备忘录模式3.1 工程结构3.2 备忘录模式模型结构图3.3 备忘录模式定义3.3.1 配置信息类3.3.2 备忘录类3.3.3 记录者类3.3.4 管理员类 3.4 单元测试 四、总结:备忘…...
MongoDB实验——MongoDB配置用户的访问控制
MongoDB 配置用户的访问控制 一、 实验原理 理解admin数据库:安装MongoDB时,会自动创建admin数据库,这是一个特殊数据库,提供了普通数据库没有的功能,例如,有些账户角色赋予用户操作多个数据库的权限&…...
golang逃逸技术分析
“ 申请到栈内存好处:函数返回直接释放,不会引起垃圾回收,对性能没有影响。 申请到堆上面的内存才会引起垃圾回收。 func F() { a : make([]int, 0, 20) b : make([]int, 0, 20000) l : 20 c : make([]int, 0, l)} “ a和b代码一样࿰…...
说说你了解的 Nginx
分析&回答 nginx性能数据 高并发连接: 官方称单节点支持5万并发连接数,实际生产环境能够承受2-3万并发。内存消耗少: 在3万并发连接下,开启10个nginx进程仅消耗150M内存 (15M10150M) 1. 正向、反向代理 所谓“代理”,是指在内网边缘 …...
SpringWeb(SpringMVC)
目录 SpringWeb介绍 搭建 SpringWeb SpringWeb介绍 Spring Web是一个基于 Servlet API 构建的原始 web 框架,用于构建基于MVC模式的Web应用程序。在 web 层框架历经 Strust1,WebWork,Strust2 等诸多产品的历代更选 之后,目前业界普…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...
