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
的节点的值。如果下标无效,则返回-1
void 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 等诸多产品的历代更选 之后,目前业界普…...

Mysql 语句
数据库管理 SQL语言分类 DDL 数据定义语言,用于创建数据库对象,如库、表、索引等 create 创建 create database/table; 数据库/表 create table 表名 (括号内添加类型和字段);drop 删除 drop database/table; 数据库/表…...

软考高级架构师——6、软件架构设计
像学写文章一样,在学会字、词、句之后,就应上升到段落,就应追求文章的“布局谋 篇”,这就是架构。通俗地讲,软件架构设计就是软件系统的“布局谋篇”。 人们在软件工程实践中,逐步认识到了软件架构的重要性…...

虚拟内存相关笔记
虚拟内存是计算机系统内存管理的一个功能,它允许程序认为它们有比实际物理内存更多的可用内存。它使用硬盘来模拟额外的RAM。当物理内存不足时,操作系统将利用磁盘空间作为虚拟内存来存储数据。这种机制提高了资源的利用率并允许更大、更复杂的应用程序的…...

【linux】定时任务讲解
文章目录 一. 在某时刻只执行一次:at1. 设置定时任务2. 查看和删除定时任务 二. 周期性执行任务:cron1. 启动crond进程2. 编辑定时任务3. 查看和删除4. 用户权限4.1. 黑名单4.2指定用户 三. /etc/crontab的管理 一. 在某时刻只执行一次:at 1…...

安卓10创建文件夹失败
最近在做拍照录像功能,已经有了文件读写权限,却发现在9.0手机上正常使用,但是在安卓12系统上根本没有创建文件夹。经过研究发现,创建名称为“DCIM”的文件夹可以,别的又都不行。而且是getExternalStorageDirectory和ge…...

文件操作(c/c++)
文件操作可以概括为几步: 打开文件,写入文件,读取文件,关闭文件 FILE FILE 是一个在C语言中用于文件操作的库函数,它提供了一系列函数来实现文件的创建、打开、读取、写入、关闭等操作。FILE 库函数可以帮助开发者处理…...

设计模式-适配器
文章目录 一、简介二、适配器模式基础1. 适配器模式定义与分类2. 适配器模式的作用与优势3.UML图 三、适配器模式实现方式1. 类适配器模式2. 对象适配器模式3.类适配器模式和对象适配器模式对比 四、适配器模式应用场景1. 继承与接口的适配2. 跨平台适配 五、适配器模式与其他设…...

C. Queries for the Array - 思维
分析: 分析出现矛盾的地方,也就是可能遇到0,并且已有字符串的长度小于等于1,另一种情况就是,遇到了1并且已有字符串不是排好序的,或者遇到了0已有字符串是排好序的,那么可以遍历字符串ÿ…...

音频——硬件拓扑
文章目录 硬件拓扑I2S 数据通路五线模式四线模式两线 TX两线 RX 典型应用硬件连接数据流 硬件拓扑 控制路径:UART/I2C/SPI数据路径:I2S 简略图如下 I2S 数据通路 五线模式 四线模式 两线 TX 两线 RX 典型应用 硬件连接 控制信号:SPI 用…...

Oracle表索引查看方法总结(查看oracle表索引)
Oracle表索引查看方法总结 Oracle是当前应用最广泛的关系数据库,也是多数大型企业使用的数据库。Oracle表索引在提高查询效率方面起着至关重要的作用,掌握该方法也是技术人员必备技能之一。本文总结了几种常见的查看Oracle表索引信息的方法,…...