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

这篇具有很好参考价值的文章主要介绍了【数据结构与算法——TypeScript】数组、栈、队列、链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

算法(Algorithm)的认识

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

  • 什么是算法?

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

  • 算法的通俗理解

    • 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】数组、栈、队列、链表,数据结构与算法,typescript,链表,javascript

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

栈结构(Stack)

认识栈结构和特性 ❤️

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

  • 数组:

    • 是一种 线性结构,可以在数组的 任意位置 插入和删除数据。
    • 但,为了实现某些功能,必须对这种 任意性加入限制
    • 栈和队列 就是比较常见的 受限的线性结构
  • 栈结构示意图
    ❤️ 后进先出/先进后出
    【数据结构与算法——TypeScript】数组、栈、队列、链表,数据结构与算法,typescript,链表,javascript

栈结构特性
  • 栈(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) 进行插入操作;
      【数据结构与算法——TypeScript】数组、栈、队列、链表,数据结构与算法,typescript,链表,javascript

实现队列结构封装

  • 基于 数组实现(性能低)
  • 基于 链表实现(更好)
队列结构常见方法
  • 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)
      • ✔️ 相对数组效率高很多
  • 💔 相对于数组,链表的缺点有

    • ❌ 链表访问任何一个位置的元素时,都需要 从头开始访问(无法跳过第一个元素访问任何一个元素)。
    • 无法通过下标直接访问元素,需要从头一个一个访问,直到找到对应的元素。
什么是链表?
  • 链表是什么?
    • 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。

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

封装链表的类结构

分析代码

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

  • 封装一个 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. 链表本身不为空,需要向其他节点后面追加节点

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

  // 追加节点
  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应该指向新节点

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

      1. 其他位置

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

  //插入 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 === position
      newNode.next = current;
      previous!.next = newNode;
    }
    this.size++;
    return true;
  }
  • get(position):获取对应位置的元素
  // 获取get
  get(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 === position
    return 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指向第二项信息
      • 第一项信息没有引用指向,就在链表中不再有效,后面会被回收
        【数据结构与算法——TypeScript】数组、栈、队列、链表,数据结构与算法,typescript,链表,javascript
    • 移除其他项:

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

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

  // 删除
  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 === position
      previous!.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 === position
      newNode.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 === position
      previous!.next = previous?.next?.next ?? null;
    }
    this.size--;
    return current?.value ?? null;
  }
  // 获取get
  get(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 === position
      newNode.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 === position
      previous!.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 后面的所有值顺序相同。

    自定义测试:文章来源地址https://www.toymoban.com/news/detail-626923.html

    • 对于输入,你应该提供整个链表 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;
}

到了这里,关于【数据结构与算法——TypeScript】数组、栈、队列、链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 数据结构之线性表的类型运用Linear Lists: 数组,栈,队列,链表

    定义 一个最简单,最基本的数据结构。一个线性表由多个相同类型的元素穿在一次,并且每一个元素都一个前驱(前一个元素)和后继(后一个元素)。 线性表的类型 常见的类型:数组、栈、队列、链表 这些不同数据结构的特性可以解决不同种类的问题 题面 题目描述 有

    2024年02月12日
    浏览(16)
  • Python数据结构与算法-数据结构(列表、栈、队列、链表)

    Python数据结构与算法-数据结构(列表、栈、队列、链表)

    数据结构是指相互之间存在这一种或者多种关系的数据元素的集合和该集合中元素之间的关系组成。 简单来说,数据结构就是设计数据以何种方式组织并存储在计算机中。 比如:列表、集合与字典等都是一种数据结构。 N.Wirth:“程序=数据结构+算法” 数据结构按照其 逻辑结

    2024年02月08日
    浏览(20)
  • 数据结构与算法之队列: Leetcode 621. 任务调度器 (Typescript版)

    任务调度器 https://leetcode.cn/problems/task-scheduler/ 描述 给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或

    2024年02月02日
    浏览(8)
  • 数据结构与算法之数组: Leetcode 89. 格雷编码 (Typescript版)

    格雷编码 https://leetcode.cn/problems/gray-code/ 描述 n 位格雷码序列 是一个由 2n 个整数组成的序列,其中: 每个整数都在范围 [0, 2n - 1] 内(含 0 和 2n - 1) 第一个整数是 0 一个整数在序列中出现 不超过一次 每对 相邻 整数的二进制表示 恰好一位不同 ,且 第一个 和 最后一个 整数

    2024年02月02日
    浏览(12)
  • 数据结构与算法之数组: Leetcode 605. 种花问题 (Typescript版)

    种花问题 https://leetcode.cn/problems/can-place-flowers/ 描述 假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示

    2024年02月02日
    浏览(9)
  • 《数据结构与算法》之队列与链表复习

    《数据结构与算法》之队列与链表复习

    我们在上一次学习了堆栈的数据结构以后,可以了解到它是受限制的操作,比如我们操作只能在栈顶,现在我们要学习的东西叫做队列,它也是受限制的一种数据结构,它的特点是队头只出数据,而队尾只入数据, 它的结构就和它的名字,像我们平时排队一样先来的人肯定要

    2024年02月08日
    浏览(20)
  • 数据结构与算法之数组: Leetcode 914. 卡牌分组 (Typescript版)

    卡牌分组 https://leetcode.cn/problems/x-of-a-kind-in-a-deck-of-cards/ 描述 给定一副牌,每张牌上都写着一个整数。 此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组: 每组都有 X 张牌。 组内所有的牌上都写着相同的整数。 仅当你可选的 X = 2 时返回 true。

    2024年02月02日
    浏览(12)
  • Java常见的数据结构:栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树

    Java常见的数据结构:栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树

    数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率 栈 队列 数组 链表 二叉树 二叉查找树 平衡二叉树 红黑树... 后进先出,先进后出 数据进入栈模型的过程称为

    2024年02月07日
    浏览(16)
  • 【数据结构和算法】使用数组的结构实现链表(单向或双向)

    【数据结构和算法】使用数组的结构实现链表(单向或双向)

    上文我们通过结构体的结构实现了队列 、以及循环队列的实现,我们或许在其他老师的教学中,只学到了用结构体的形式来实现链表、队列、栈等数据结构,本文我想告诉你的是,我们 可以使用数组的结构实现链表、单调栈、单调队列 目录 前言 一、用数组结构的好处 1.数

    2024年01月20日
    浏览(51)
  • AcWing 算法基础课二 数据结构 链表 栈 队列 并查集 哈希表

    AcWing 算法基础课二 数据结构 链表 栈 队列 并查集 哈希表

    链表题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 AcWing. 826.单链表 双链表 AcWing 827.双链表 AcWing 828.模拟栈 AcWing 3302.表达式求值 AcWing 829. 模拟队列 AcWing 830.单调栈 AcWing 154.滑动窗口 AcWing 831. KMP字符串 AcWing 835. Trie字符串统计 AcWing 143. 最大异或对 AcWing 836.合并集合

    2024年02月15日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包