【数据结构与算法——TypeScript】数组、栈、队列、链表

【数据结构与算法——TypeScript】

算法(Algorithm)的认识

  • 解决问题的过程中,不仅仅 数据的存储方式会影响效率,算法的优劣也会影响效率

  • 什么是算法?

    • 定义:
      🟢 一个有限指令集,每条指令的描述不依赖于言语 (编写指令:java/c++/ts/js)
      🟢 接收一些输入(有些情况下不需要输入)(接收:排序:无序数组)
      🟢 产生输出 (产出:有序数组)
      🟢 一定在有限步骤之后终止
  • 举例:电灯不工作的解决算法
    电灯不工作的解决算法

  • 算法的通俗理解

    • Algorithm这个单词本意就是 解决问题的办法/步骤逻辑
    • 数据结构的实现,离不开算法

线性结构(Linear List)

  • 线性结构(Linear List)是由n(n≧0)个数据元素(结点)a[0],a[1],a[2],…,a[n-1]组成的有限序列。

  • 其中

    • 【数据元素的个数 n 定义为表的长度】= “list”.length()(“list”.length() = 0 (表示没有一个元素) 时,称为空表)。
    • 将非空的线性表 (n>=1),记作:(a[0],a[1],a[2],…,a[n-1])。
    • 数据元素 a[i] (0 <= i <= n-1) 只是抽象符号,其具体含义在不同情况下可以不同。
  • 常见线性结构:
    💚 数组结构(Array)
    💚 栈结构(Stack)
    💚 队列结构(Queue)
    💚 链表结构(LinkedList)

数组(Array)结构

  • 数组(Array)结构是一种重要的数据结构

    • 几乎是每种编程语言都会提供的一种 原生数据结构(语言自带的)
    • 并且我们 可以借助于数组结构来实现其他的数据结构,比如:栈(Stack)、队列(Queue)、堆(Heap);
  • 通常数组的内存都是连续的,所以数组在知道下标值的情况下,访问效率是非常高的
    数组

  • TypeScript中数组的各种用法,和JavaScript是一致的:
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array

栈结构(Stack)

认识栈结构和特性 ❤️

认识栈结构

  • 栈 也是一种非常常见的数据结构,并且在程序中的应用非常广泛。

  • 数组:

    • 是一种 线性结构,可以在数组的 任意位置 插入和删除数据。
    • 但,为了实现某些功能,必须对这种 任意性加入限制
    • 栈和队列 就是比较常见的 受限的线性结构
  • 栈结构示意图
    ❤️ 后进先出/先进后出
    在这里插入图片描述

栈结构特性

  • 栈(Stack),它是一种受限的线性结构, 后进先出(LIFO)
    • 其限制是仅允许在 表的一端 进行插入和删除运算。这一端被称为 栈顶,相对地,另一端就是 栈低
    • LIFO(last in first out)表示 后进入的元素,第一个弹出栈空间。类似于,自动餐托盘,最后放上的托盘,往往先拿出去使用。
    • 向一个栈插入新元素,又叫 进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;
    • 从一个栈删除元素又称之为 出栈 或者 退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈结构特性 —— 面试题

面试题目

  • ❓题目:
    🔖 有六个元素6,5,4,3,2,1顺序入栈,问下列哪一个不是合法的出栈序列?()
    A. 5 4 3 6 1 2
    B. 4 5 3 2 1 6
    C. 3 4 6 5 2 1
    D. 2 3 4 1 5 6

  • 分析:

    • A. 5 4 3 6 2 1
      • 6,5进栈,5出栈,4进栈,4出栈,3进栈,3出栈,6出栈,2进栈,1进栈,1出栈,2出栈
    • B. 4 5 3 2 1 6
      • 6,5,4进栈,4出栈,5出栈,3进栈,3出栈,2进栈,2出栈,1入栈,1出栈,6出栈
    • C. 3 4 6(x) 5 2 1 ❌
      • 6,5,4,3进栈,3出栈,4出栈,5出栈
    • D. 2 3 4 1 5 6
      • 6,5,4,3,2进栈,2出栈,3出栈,4出栈,1进栈,1出栈,5出栈,6出栈
    • 答案: C

实现栈结构的封装

  • 常见的方式:
    🔹 基于数组实现
    🔹 基于链表实现

🔹 基于数组实现:创建栈的类

  • 代码解析:
    • 创建一个 Stack ,用户创建栈的类,可以定义一个泛型类。
    • 在构造函数中,定义一个变量,存储当前栈对象中的所有元素。
    • 这个变量是一个数组类型。
    • 之后,无论是入栈还是出栈操作,都是从数组中添加和删除元素。
    • 栈的一些操作方法,无论是什么语言,操作都是比较类似的。

栈结构常见的方法

🔸栈的常见操作

  • push(element):添加一个新元素到栈顶。
  • pop():删除栈顶元素,返回被移除的元素。
  • peek():仅返回栈顶元素,不对栈做任何修改。
  • isEmpty():判断栈里是否有元素。有,返回true,否,返回false。
  • size():返回栈里的元素个数。这个方法和数组的length属性类型。

实现

import { IStack } from './IStack';class ArrayStack<T = any> implements IStack<T> {// 定义一个数组/链表,用于存储元素private data: T[] = [];// 实现栈中的操作方法// push(element):添加一个新元素到栈顶push(element: T): void {this.data.push(element);}// pop():删除栈顶元素,返回被移除的元素。pop(): T | undefined {return this.data.pop();}// peek():仅返回栈顶元素,不对栈做任何修改。peek(): T | undefined {return this.data[this.data.length - 1];}// isEmpty():判断栈里是否有元素。有,返回true,否,返回false。isEmpty(): boolean {return this.data.length === 0;}// size():返回栈里的元素个数。这个方法和数组的length属性类型。size(): number {return this.data.length;}
}export default ArrayStack;
export interface IStack<T> {push(element: T): void;pop(): T | undefined;peek(): T | undefined;isEmpty(): boolean;size(): number;
}

栈面试题 —— 十进制转二进制

  • 为什么需要十进制转二进制?

    • 现实生活中,主要使用 十进制
    • 但,在计算机科学中, 二进制非常重要,因为 计算机里的所有内容都是用二进制数字表示的(0和1)
    • 没有十进制和二进制互相转换的能力,与计算机交流就很困难。
    • 转换二进制是计算机科学和编程领域中经常使用的算法
  • 如何实现时十进制转二进制(整数)?

    • 将十进制数字和2整除(二进制是满2进1),直到结果为0
    • 把十进制数字35转为二进制的数字:
      • 35/2 = 17…1, 余 1
      • 17/2 = 8…1, 余 1
      • 8/2 = 4…0,余 0
      • 4/2 = 2…0,余 0
      • 2/2 = 1…0,余 0
      • 1/2 = 0…1,余 1
      • 从后往前:100011
  • 简单实现

    import ArrayStack from './02_实现栈结构Stack(重构)';function decimalToBinary(decimal: number): string {// 1. 创建一个栈,用于存储余数const stack = new ArrayStack<number>();// 2. 使用循环:while:不确定次数,只知道循环结束条件while (decimal > 0) {const result = decimal % 2;stack.push(result);decimal = Math.floor(decimal / 2);}// 3.将所有的余数已放入到stack中,依次取出所有余数let binary = '';while (!stack.isEmpty()) {binary += stack.pop();}return binary;
    }
    export {};console.log('35:', decimalToBinary(35));
    console.log('100:', decimalToBinary(100));

栈面试题 —— 有效括号

  • 给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。

    • ❗️有效字符串需满足:
      • 左括号必须用相同类型的右括号闭合。
      • 左括号必须以正确的顺序闭合。
      • 每个右括号都有一个对应的相同类型的左括号
      • Leecode 20:https://leetcode.cn/problems/valid-parentheses/
  • 示例 1:

    输入:s = “()”
    输出:true

  • 示例 2:

    输入:s = “()[]{}”
    输出:true

  • 示例 3:

    输入:s = “(]”
    输出:false

  • ⚠️ 提示:

    • 1 <= s.length <= 10的4次
    • s 仅由括号 ‘()[]{}’ 组成
  • 代码

import ArrayStack from './02_实现栈结构Stack(重构)';function isValid(s: string): boolean {// 1. 创建一个栈结构:const stack = new ArrayStack<string>();// 2. 遍历字符串for (let i = 0; i < s.length; i++) {const c = s[i];switch (c) {case '(':stack.push(')');break;case '{':stack.push('}');break;case '[':stack.push(']');break;default:if (c !== stack.pop()) return false;break;}}return stack.isEmpty();
}console.log(isValid('()'));
console.log(isValid('()[]{}'));
console.log(isValid('(]'));
console.log(isValid('([])'));
console.log(isValid('({[}])'));

队列结构(Queue)

认识队列和特性

  • 受限的线性结构

    • 这种受限的线性结构对于解决某些 特别问题有特别的结果
    • 受限的数据结构:队列
  • 队列(Queue),它是一种受限的线性结构,先进先出(FILO:First In Last Out)

    • 受限之处:只允许在队列的前端(front),进行删除操作;
    • 在队列的 后端(rear) 进行插入操作;
      在这里插入图片描述

实现队列结构封装

  • 基于 数组实现(性能低)
  • 基于 链表实现(更好)

队列结构常见方法

  • enqueue(element):向队列尾部添加一个(或者多个)新的项。
  • dequeue():移除队列的第一(即排在队列最前面的)项,也是最先移除的元素;
  • front/peek():返回队列中第一个元素——最先被添加,也是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息)
  • isEmpty():如果队列中不包含任何元素,返回true,否则返回false
  • size():返回队列包含的元素个数,与数组的length属性类似

基于数组实现

import { IQueue } from './IQueue';class ArrayQueue<T = any> implements IQueue<T> {// 内部通过数组保存private data: T[] = [];// 队列常见操作/*** enqueue(element):向队列尾部添加一个(或者多个)新的项。* dequeue():移除队列的第一(即排在队列最前面的)项,也是最先移除的元素;* front/peek():返回队列中第一个元素——最先被添加,也是最先被移除的元素。队列不做任何变动(不移除元素,只返回元素信息)* isEmpty():如果队列中不包含任何元素,返回true,否则返回false;* size():返回队列包含的元素个数,与数组的length属性类似*/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;}get size(): number {return this.data.length;}
}
export default ArrayQueue;
import type { IList } from '../types/IList';export interface IQueue<T> extends IList<T> {enqueue(element: T): void;dequeue(): T | undefined;
}
export interface IList<T> {peek(): T | undefined;isEmpty(): boolean;get size(): number;
}

面试题 —— 击鼓传花

  • 击鼓传花,是一个常见的面试算法题:使用队列可以非常方便的实现最终的结果

  • 原游戏规则

    • 班级中玩一个游戏,所有同学围成一个圈,从某位同学手里开始向旁边的同学传一束花;
    • 这个时候某个人(比如班长),在击鼓,鼓声停下的一刻,花落在谁的手里,谁就出来表演节目。
  • 修改游戏规则

    • 几个朋友一起玩一个游戏, 围成一圈,开始数数,数到某个数字的人自动淘汰;
    • 最后 剩下的这个人获胜,请问最后剩下的人是原来在哪一个位置上的人?
  • 封装一个基于队列的函数:

    • 参数:所有参与人的姓名,基于的数字;
    • 结果:最终剩下的一个人的姓名
  • 分析:

    • 假如数到3的人,淘汰;
      • 那么,队列中 数的1,2的人,出队之后,又入队;
      • 数到 3 的人,出队,不需要入队
    • 直到,队列的size() 等于1 的时候,取到队列里的人
  • 代码

import ArrayQueue from './01_基于数组的队列结构Queue';function hotPatato(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) {// 小于 num 的不淘汰for (let i = 1; i < num; i++) {const name = queue.dequeue();if (name) queue.enqueue(name);}// num 淘汰queue.dequeue();}// 取出最后一个人const leftName = queue.dequeue()!;// 取出最后一个人的原索引const index = names.indexOf(leftName);return index;
}
const leftIndex = hotPatato(['why', 'JIM', 'JANE', 'LENO', 'CURRY', 'TIM', 'TOM', 'JERRY', 'JENO', 'TIA'], 5);
console.log(leftIndex);

面试题 —— 约瑟夫环

  • 什么是约瑟夫环问题(历史)?

  • 阿乔问题(有时也称为约瑟夫斯置换),是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。

    • 人们站在一个等待被处决的圈子里;
    • 计数从圆圈中指定的点开始,并沿指定方向围绕圆圈进行;
    • 在跳过指定数量的人之后,处刑下一个人;
    • 对剩下的人重复该过程,从下一个人开始,朝同一方向跳过相同数量的人,直到只剩下一个人,并被释放;
    • 在给定数量的情况下,站在第几个位置可以避免被处刑?
  • Leecode 剑指 Offer 62. 圆圈中最后剩下的数字

  • https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/

  • 0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。

  • 求出这个圆圈里剩下的最后一个数字。

  • 例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

  • 示例 1:

    输入: n = 5, m = 3
    输出: 3

  • 示例 2:

    输入: n = 10, m = 17
    输出: 2

  • 代码

import ArrayQueue from './01_基于数组的队列结构Queue';function lastRemaining(n: number, m: number): number {const queue = new ArrayQueue<number>();for (let i = 0; i < n; i++) {queue.enqueue(i);}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));
console.log(lastRemaining(10, 17));

链表结构(LinkedList)

认识链表及特性

数组的缺点

  • 链表和数组一样,可以用于 存储一系列的元素,但是链表和数组的 实现机制完全不同
  • 用于存储数据的线性结构:链表

🥺

  • 数组:

    • 要存储多个元素,数组(或选择链表)可能是 最常用的数据结构
    • 几乎每种语言都有默认实现 数组结构
  • 💔 但是,数组有很多缺点

    • 数组的创建通常需要申请一段 连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当前数组 不能满足容量需求时,需要 扩容。(一般情况下时申请一个更大的数组,比如2倍。然后将原数组中的元素复制过去)
    • 而且在 数组开头或者中间位置插入数据的成本很高,需要 进行大量元素的位移

🥰 链表的优势

  • 要存储多个元素,另外一个选择就是 链表

  • 不同于数组,链表中的元素在内存中 不必是连续的空间

    • 链表的每个元素由一个存储 元素本身的节点一个指向下一个元素的引用(有些语音称为指针或链接)组成。
  • ❣️ 相对于数组,链表的优势有

    • 🟩 内存空间不是必须连续的
      • ✓ 可以充分利用计算机的内存,实现灵活的 内存动态管理
    • 🟩 链表不必在创建时就 确定大小,并且大小可以 无限延伸下去。
    • 🟩 链表在 插入和删除数据时,时间复杂度可以达到O(1)
      • ✔️ 相对数组效率高很多
  • 💔 相对于数组,链表的缺点有

    • ❌ 链表访问任何一个位置的元素时,都需要 从头开始访问(无法跳过第一个元素访问任何一个元素)。
    • 无法通过下标直接访问元素,需要从头一个一个访问,直到找到对应的元素。

什么是链表?

  • 链表是什么?
    • 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。

在这里插入图片描述

封装链表的类结构

分析代码

在这里插入图片描述

  • 封装一个 Node类,用于封装每一个节点上的信息(包括值和指向下一个节点的引用),它是一个泛型。
  • 封装一个 LinkedList类,用于表示我们的链表结构。
  • 链表中保存两个属性:链表长度、链表的第一个节点
// 1. 创建Node节点类
class Node<T> {value: T;next: Node<T> | null = null;constructor(value: T) {this.value = value;}
}// 2.创建LinkedList类
class LinkedList<T> {head: Node<T> | null = null;private size: number = 0;get length() {return this.size;}
}const linkedList = new LinkedList<string>();
console.log(linkedList.head);
export {};

封装链表的相关方法

  • append(element):向链尾添加一个新的项
      1. 链表本身为空,新添加数据时唯一的节点
      1. 链表本身不为空,需要向其他节点后面追加节点

      在这里插入图片描述

  // 追加节点append(value: T) {// 1.根据value创建一个新节点const newNode = new Node(value);//  1. 链表本身为空,新添加数据时唯一的节点//  2. 链表本身不为空,需要向其他节点后面追加节点if (!this.head) {this.head = newNode;} else {let current = this.head;while (current.next) {current = current.next;}// current此时,肯定是指向最后一个节点current.next = newNode;}this.size++;}
  • insert(position,element):向链表的特定位置插入一个新的项

    • 1.添加到第一个位置
      • 添加到第一个位置,表示新添加的节点是头,就需要将原来的头节点,作为新的节点的next
      • 这个时候的head应该指向新节点

    在这里插入图片描述

      1. 其他位置

-在这里插入图片描述

  //插入 insert(position, element): 向链表的特定位置插入一个新的项;insert(value: T, position: number): boolean {if (position < 0 || position > this.size) return false;// 是否添加到第一个const newNode = new Node(value);if (position === 0) {newNode.next = this.head;this.head = newNode;} else {let current = this.head;let previous: Node<T> | null = null;let index = 0;while (index++ < position && current) {previous = current;current = current.next;}// index === positionnewNode.next = current;previous!.next = newNode;}this.size++;return true;}
  • get(position):获取对应位置的元素
  // 获取getget(position: number): T | null {if (position < 0 || position >= this.size) return null;// 查找元素let index = 0;let current = this.head;while (index++ < position && current) {current = current?.next;}// index === positionreturn current?.value ?? null;}
  • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回 -1
  // 获取索引indexOf(value: T): number {// 遍历let current = this.head;let index = 0;while (current) {if (current.value === value) {return index;}current = current.next;index++;}return -1;}
  • update(position,element):修改某个位置的元素
  // 更新update(value: T, position: number): boolean {if (position < 0 || position >= this.size) return false;let currentNode = this.head;let index = 0;while (index++ < position && current) {currentNode = currentNode.next;}currentNode!.value = value;return true;}
  • removeAt(position):从链表的特定位置移除一项
    • 常见两种

        1. 根据位置位移对应的数据
        1. 根据数据,先找到对应的位置,再移除数据
    • 移除第一项:

      • 移除第一项时,直接让head指向第二项信息
      • 第一项信息没有引用指向,就在链表中不再有效,后面会被回收
        在这里插入图片描述
    • 移除其他项:

      • 首先,通过while循环,找到正确的位置
      • 其次,直接将上一项的next指向current项的next,这样中间的项就没有引用指向它,也就不存于链表中,后面就会被回收

      在这里插入图片描述

  // 删除removeAt(position: number): T | null {// 越界判断if (position < 0 || position >= this.size) return null;// 删除元素// 判断是否是删除第一个节点let current = this.head;if (position === 0) {this.head = this.head?.next ?? null;} else {let previous: Node<T> | null = null;let index = 0;while (index++ < position && current) {previous = current;current = current.next;}// index === positionprevious!.next = current?.next ?? null;}this.size--;return current?.value ?? null;}
  • remove(element):从链表中移除一项
  // 根据value删除remove(value: T): T | null {const index = this.indexOf(value);return this.removeAt(index);}
  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
// 链表是否为空isEmpty(): boolean {return this.size === 0;}
  • size():返回链表包含的元素个数。与数组的length属性类似

完整代码

// 1. 创建Node节点类
class Node<T> {value: T;next: Node<T> | null = null;constructor(value: T) {this.value = value;}
}// 2.创建LinkedList类
class LinkedList<T> {private head: Node<T> | null = null;private size: number = 0;get length() {return this.size;}// 封装私有方法:// 根据position 获取到当前节点(不是节点的value,而是获取节点)private getNode(position: number): Node<T> | null {let current = this.head;let index = 0;while (index++ < position && current) {current = current.next;}return current;}// 追加节点append(value: T) {// 1.根据value创建一个新节点const newNode = new Node(value);//  1. 链表本身为空,新添加数据时唯一的节点//  2. 链表本身不为空,需要向其他节点后面追加节点if (!this.head) {this.head = newNode;} else {let current = this.head;while (current.next) {current = current.next;}// current此时,肯定是指向最后一个节点current.next = newNode;}this.size++;}// 遍历链表的方法traverse() {const values: T[] = [];let current = this.head;while (current) {values.push(current.value);current = current.next;}console.log(values.join(' -> '));}// 插入 insert(position, element): 向链表的特定位置插入一个新的项;insert(value: T, position: number): boolean {if (position < 0 || position > this.size) return false;// 是否添加到第一个const newNode = new Node(value);if (position === 0) {newNode.next = this.head;this.head = newNode;} else {let previous = this.getNode(position - 1);// index === positionnewNode.next = previous?.next ?? null;previous!.next = newNode;}this.size++;return true;}// 根据位置删除removeAt(position: number): T | null {// 越界判断if (position < 0 || position >= this.size) return null;// 删除元素// 判断是否是删除第一个节点let current = this.head;if (position === 0) {this.head = this.head?.next ?? null;} else {// 重构let previous = this.getNode(position - 1);// index === positionprevious!.next = previous?.next?.next ?? null;}this.size--;return current?.value ?? null;}// 获取getget(position: number): T | null {if (position < 0 || position >= this.size) return null;// 查找元素return this.getNode(position)?.value ?? null;}// 更新update(value: T, position: number): boolean {if (position < 0 || position >= this.size) return false;const currentNode = this.getNode(position);currentNode!.value = value;return true;}// 获取索引indexOf(value: T): number {// 遍历let current = this.head;let index = 0;while (current) {if (current.value === value) {return index;}current = current.next;index++;}return -1;}// 根据value删除remove(value: T): T | null {const index = this.indexOf(value);return this.removeAt(index);}// 链表是否为空isEmpty(): boolean {return this.size === 0;}
}
const linkedList = new LinkedList<string>();
linkedList.append('aaa');
linkedList.append('bbb');
linkedList.append('ccc');linkedList.insert('abc', 0);
linkedList.insert('abcd', 0);linkedList.insert('中间444', 2);
linkedList.insert('nba', 6);
linkedList.traverse();linkedList.removeAt(0);
linkedList.traverse();
linkedList.removeAt(2);
linkedList.traverse();console.log('-----测试get-----');
console.log(linkedList.get(0));
console.log(linkedList.get(1));
console.log(linkedList.get(2));console.log('-----测试update-----');
console.log(linkedList.update('11111', 1));
linkedList.traverse();console.log('-----测试indexOf-----');
console.log(linkedList.indexOf('11111'));
linkedList.traverse();
export {};

链表常见的面试题

    1. 设计链表 https://leetcode.cn/problems/design-linked-list/
    • 你可以选择使用单链表或者双链表,设计并实现自己的链表。

    • 单链表中的节点应该具备两个属性: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 的节点。
class Node<T> {val: T;next: Node<T> | null = null;constructor(val: T) {this.val = val;}
}
class MyLinkedList<T> {private head: Node<T> | null = null;private size: number = 0;private getNode(index: number): Node<T> | null {let current = this.head;let i = 0;while (i++ < index && current) {current = current.next;}return current;}// int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。get(index: number): any {if (index < 0 || index >= this.size) {return -1;}return this.getNode(index)?.val ?? null;}addAtHead(val: T): void {const newNode = new Node(val);newNode.next = this.head;this.head = newNode;this.size++;}addAtTail(val: T): void {const newNode = new Node(val);if (!this.head) {this.head = newNode;} else {let current = this.head;while (current.next) {current = current.next;}// current此时,肯定是指向最后一个节点current.next = newNode;}this.size++;}addAtIndex(index: number, val: T): boolean {if (index < 0 || index > this.size) return false;const newNode = new Node(val);if (index === 0) {newNode.next = this.head;this.head = newNode;} else {let previous = this.getNode(index - 1);// index === positionnewNode.next = previous?.next ?? null;previous!.next = newNode;}this.size++;return true;}deleteAtIndex(index: number): T | null {if (index < 0 || index >= this.size) return null;// 删除元素// 判断是否是删除第一个节点let current = this.head;if (index === 0) {this.head = this.head?.next ?? null;} else {// 重构let previous = this.getNode(index - 1);// index === positionprevious!.next = previous?.next?.next ?? null;}this.size--;return current?.val ?? null;}
}
export {};
    1. 删除链表中的节点
    • https://leetcode.cn/problems/delete-node-in-a-linked-list/
      有一个单链表的 head,我们想删除它其中的一个节点 node

    给你一个需要删除的节点 node 。你将 无法访问 第一个节点 head

    链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

    删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

    • 给定节点的值不应该存在于链表中。
    • 链表中的节点数应该减少 1。
    • node 前面的所有值顺序相同。
    • node 后面的所有值顺序相同。

    自定义测试:

    • 对于输入,你应该提供整个链表 head 和要给出的节点 nodenode 不应该是链表的最后一个节点,而应该是链表中的一个实际节点。
    • 我们将构建链表,并将节点传递给你的函数。
    • 输出将是调用你函数后的整个链表。
class ListNode {val: number;next: ListNode | null;constructor(val?: number, next?: ListNode | null) {this.val = val === undefined ? 0 : val;this.next = next === undefined ? null : next;}
}
function deleteNode(node: ListNode | null): void {node!.val = node!.next!.val;node!.next = node!.next!.next;
}
export {};
    1. 反转链表
    • https://leetcode.cn/problems/reverse-linked-list/
    • 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
class ListNode {val: number;next: ListNode | null;constructor(val?: number, next?: ListNode | null) {this.val = val === undefined ? 0 : val;this.next = next === undefined ? null : next;}
}function reverseList(head: ListNode | null): ListNode | null {if (!head || !head.next) return head;const newHead = reverseList(head.next);head.next.next = head;head.next = null;return newHead;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5、CesiumForUnreal接入WMTS格式地图瓦片,如ArcGIS、Mapbox、天地图

文章目录 1.实现目标2.实现过程2.1 WMTS与TMS2.2 cesium-native改造2.3 CesiumForUnreal插件改造2.4 WMTS瓦片加载测试2.5 EPSG:3857与43263.参考资料1.实现目标 通过改造cesium-native和CesiumForUnreal插件,参考tms的栅格瓦片地图加载逻辑,实现在UE5中通过CesiumForUnreal…

后端进阶之路——万字总结Spring Security与数据库集成实践(五)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ 解决算法&#xff0c;一个专栏就够了★ ★ 架…

数据结构——空间复杂度

3.空间复杂度 空间复杂度也是一个数学表达式&#xff0c;是对一个算法在运行过程中临时占用存储空间大小的量度 。 空间复杂度不是程序占用了多少bytes的空间&#xff0c;因为这个也没太大意义&#xff0c;所以空间复杂度算的是变量的个数。 空间复杂度计算规则基本跟实践复杂…

springboot中@Async的简单用法

springboot中Async的简单用法 文章目录 springboot中Async的简单用法开启配置Async的使用无返回值调用带返回值的调用 开启配置 在配置文件或者入口文件上新增注解: EnableAsync即可 Async的使用 对应需要异步调用的方法上添加Async注解即可 无返回值调用 controller代码 …

四项代表厂商,Kyligence 入选 Gartner 数据及人工智能相关领域多项报告

近日&#xff0c;全球权威的技术研究与咨询公司 Gartner 发布了《2023 年中国数据、分析及人工智能技术成熟度曲线》、《2023 年分析与商业智能技术成熟度曲线报告》、《2023 年数据管理技术成熟度曲线报告》&#xff0c;Kyligence 分别入选这三项报告的指标平台 Metrics Store…

[保研/考研机试] KY187 二进制数 北京邮电大学复试上机题 C++实现

描述 大家都知道&#xff0c;数据在计算机里中存储是以二进制的形式存储的。 有一天&#xff0c;小明学了C语言之后&#xff0c;他想知道一个类型为unsigned int 类型的数字&#xff0c;存储在计算机中的二进制串是什么样子的。 你能帮帮小明吗&#xff1f;并且&#xff0c;小…

《兴森大求真》重磅来袭!先进电子电路可靠性大揭秘

兴森实验室&#xff0c;让可靠看得见 前言介绍 芯片性能不断增强、先进封装不断演进&#xff0c;导致封装基板信号互连的IO数量和密度不断增加、PCB的层数增加、孔间距减小、厚径比提升&#xff0c;可靠性的挑战正在加剧。 电路板作为各种电子元器件的载体和电路信号传输的枢…

ROS入门-常见的rostopic命令及其用法的示例

目录 常见的rostopic命令及其用法的示例 1. 列出所有可用的话题&#xff1a; 2. 获取话题详细信息&#xff1a; 3. 实时显示话题消息内容&#xff1a; 4. 发布消息到话题&#xff1a; 5. 发布随机消息到话题&#xff1a; 6. 查看话题消息类型&#xff1a; 7. 查看话题消…

UDP简介

UDP 1. UDP格式2. UDP特点3. 差错检验 1. UDP格式 16位UDP长度&#xff0c;表示整个数据报&#xff08;UDP首部UDP数据&#xff09;的最大长度&#xff1b; 如果校验和出错&#xff0c;就会直接丢弃; 2. UDP特点 无连接: 知道对端的IP和端口号就直接进行传输&#xff0c;不需…

idea使用protobuf

本文参考&#xff1a;https://blog.csdn.net/m0_37695902/article/details/129438549 再次感谢分享 什么是 protobuf &#xff1f; Protocal Buffers(简称protobuf)是谷歌的一项技术&#xff0c;用于结构化的数据序列化、反序列化。 由于protobuf是跨语言的&#xff0c;所以用…

xxx酒业有限责任公司突发环境事件应急预案WORD

导读&#xff1a;原文《xxx酒业有限责任公司突发环境事件应急预案word》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 目 录 1 总则 1.1 编制目的 1.2 编制…

【vue3】基础知识点-pinia

学习vue3&#xff0c;都会从基础知识点学起。了解setup函数&#xff0c;ref&#xff0c;recative&#xff0c;watch、computed、pinia等如何使用 今天说vue3组合式api&#xff0c;pinia 戳这里&#xff0c;跳转pinia中文文档 官网的基础示例中提供了三种写法 1、选择式api&a…

python进阶

目录 Json数据格式 前言 JSON格式 python数据和Json数据的相互转化 网络编程 套接字 socket服务端编程步骤 socket客户端编程步骤 python操作mysql数据库 查询并接收数据 数据插入 Json数据格式 前言 JSON是一种轻量级的数据交换格式&#xff0c;可以按照JSON指定…

【福建事业单位-资料分析】04 倍数、特殊增长率

【福建事业单位-资料分析】04 倍数、特殊增长率 一、倍数1.1现期倍数1.2 基期倍数总结 二、特殊增长率2.1 间隔增长率间隔倍数和间隔基期&#xff08;都要先求得间隔增长率r&#xff09; 2.2 年均增长率年均增长率的比较年均增长率计算-居中代入 2.3 混合增长率总结 三、总结 一…

E. Power of Points - 思维

分析&#xff1a; 题意本质就是找点在数组中任意一个位置时和所有的端点之间的距离和&#xff0c;但是直接暴力会超时&#xff0c;可以对数组排个序&#xff0c;设当前遍历的是xi&#xff0c;那么此时求的到各端点的距离就是j从1 ~ i - 1的所有端点与xi的距离之和&#xff0c;也…

配置nginx服务端口时-在同一个页面中打开多个地址端口-查看服务情况

1&#xff1a;把代码保存到xxx.html文件中 2&#xff1a;因为一个个端口打开查看&#xff0c;实在太麻烦了 3&#xff1a;在一个页面中查看多页的响应才能提高测试效率 <html><head><title>本地连接列表</title> </head><body><cente…

【C语言学习】函数的定义和调用

一、函数定义 要有返回类型、函数名和函数体 二、调用函数 函数名&#xff08;函数值&#xff09;&#xff1b; &#xff08;&#xff09;起到表示函数调用的重要作用&#xff0c;即使没有参数也需要&#xff08;&#xff09; 若有参数&#xff0c;则需要给出正确的数量和顺序…

vscode ssh远程的config/配置文件无法保存解决

问题 之前已经有了一个config&#xff0c;我想更改连接的地址和用户名&#xff0c;但是无法保存&#xff0c;显示需要管理员权限&#xff0c;但以管理员启动vscode或者以管理员权限保存都不行 未能保存“config”: Command failed: “D:\vscode\Microsoft VS Code\bin\code.c…

『PostgreSQL』在 PostgreSQL中创建只读权限和读写权限的账号

&#x1f4e3;读完这篇文章里你能收获到 理解在 PostgreSQL 数据库中创建账号的重要性以及如何进行账号管理掌握在 PostgreSQL 中创建具有只读权限和读写权限的账号的步骤和方法学会使用 SQL 命令来创建账号、为账号分配适当的权限以及控制账号对数据库的访问级别了解如何确保…

C++友元函数和友元类的使用

1.友元介绍 在C中&#xff0c;友元&#xff08;friend&#xff09;是一种机制&#xff0c;允许某个类或函数访问其他类的私有成员。通过友元&#xff0c;可以授予其他类或函数对该类的私有成员的访问权限。友元关系在一些特定的情况下很有用&#xff0c;例如在类之间共享数据或…