前端算法 链表结构

目录

在这里插入图片描述

数组的缺点

在这里插入图片描述

链表的优势

在这里插入图片描述

链表到底是什么?

在这里插入图片描述

链表结构的封装

在这里插入图片描述

链表常见操作

在这里插入图片描述

append方法

在这里插入图片描述

链表的遍历方法(traverse)

在这里插入图片描述

insert方法

在这里插入图片描述
在这里插入图片描述

 // 插入方法: abc
  insert(value: T, position: number): boolean {
    // 1.边界判断--
    if (position < 0 || position > this.size) return false;
    // 2.根据value创建新的值
    const newNode = new Node(value);
    // 3.进行value节点的插入
    // 3.1当插入的是头部
    if (position === 0) {
      newNode.next = this.head;
      this.head = newNode;
    }
    // 3.2插入其他位置
    else {
      let current = this.head;
      let previous: Node<T> | null = null;
      let index = 0;
      while (index++ < position && current) {
        previous = current;
        current = current.next;
      }
      newNode.next = current;
      previous!.next = newNode;
    }
    //  4.链表长度+1
    this.size++;
    return true;
  }

removeAt方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

get方法

在这里插入图片描述

遍历结点的操作重构

在这里插入图片描述

update方法

在这里插入图片描述

indexOf方法

在这里插入图片描述

remove方法以及其他方法

在这里插入图片描述

707. 设计链表 -字节、腾讯等公司面试题

在这里插入图片描述

// 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 index = 0;
    let current = this.head;
    while (index++ < position && current) {
      current = current.next;
    }
    return current;
  }

  // 追加节点
  append(value: T) {
    // 1.根据value创建一个新节点
    const newNode = new Node(value);

    // 2.判断this.head是否为null
    if (!this.head) {
      this.head = newNode;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }

      // current肯定是指向最后一个节点的
      current.next = newNode;
    }

    // 3.size++
    this.size++;
  }

  // 遍历链表的方法
  traverse() {
    const values: T[] = [];

    let current = this.head;
    while (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) {
      newNode.next = this.head;
      this.head = newNode;
    } else {
      const previous = this.getNode(position - 1);
      newNode.next = previous!.next;
      previous!.next = newNode;
    }
    this.size++;

    return true;
  }

  // 删除方法: 根据位置删除
  removeAt(position: number): T | null {
    // 1.越界的判断
    if (position < 0 || position >= this.size) return null;

    // 2.判断是否是删除第一个节点
    let current = this.head;
    if (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 {
    // 越界问题
    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 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() {
    return this.size === 0;
  }
}

const linkedList = new LinkedList<string>();
console.log("------------ 测试append ------------");
linkedList.append("aaa");
linkedList.append("bbb");
linkedList.append("ccc");
linkedList.append("ddd");
linkedList.traverse();

console.log("------------ 测试insert ------------");
linkedList.insert("abc", 0);
linkedList.traverse();
linkedList.insert("cba", 2);
linkedList.insert("nba", 6);
linkedList.traverse();

// 测试删除节点
console.log("------------ 测试removeAt ------------");
linkedList.removeAt(0);
linkedList.removeAt(0);
linkedList.traverse();

console.log(linkedList.removeAt(2));
linkedList.traverse();
console.log(linkedList.removeAt(3));
linkedList.traverse();

console.log("------------ 测试get ------------");
console.log(linkedList.get(0));
console.log(linkedList.get(1));
console.log(linkedList.get(2));

console.log("------------ 测试update ------------");
linkedList.update("why", 1);
linkedList.update("kobe", 2);

console.log("------------ 测试indexOf ------------");
console.log(linkedList.indexOf("cba"));
console.log(linkedList.indexOf("why"));
console.log(linkedList.indexOf("kobe"));
console.log(linkedList.indexOf("james"));

console.log("------------ 测试remove ------------");
linkedList.remove("why");
linkedList.remove("cba");
linkedList.remove("kobe");
linkedList.traverse();
console.log(linkedList.isEmpty());

export {};

237. 删除链表中的节点 – 字节、阿里等公司面试题

在这里插入图片描述

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;
  }
}

export default ListNode;

// 偷梁换柱 不修改获取的node节点,不用删除,将值改变,在将指针执行next.next,对应指向的认知要清晰
function deleteNode(node: ListNode | null): void {
  node!.val = node!.next!.val;
  node!.next = node!.next!.next;
}

206. 反转链表 – 字节、谷歌等面试题

在这里插入图片描述

import ListNode from "./面试题_ListNode";

function reverseList(head: ListNode | null): ListNode | null {
  // 什么情况下链表不需要处理?
  // 1.head本身为null的情况
  if (head === null) return null;
  // 2.只有head一个节点
  if (head.next === null) return head;

  // 数组模拟栈结构
  const stack: ListNode[] = [];
  let current: ListNode | null = head;
  while (current) {
    stack.push(current);
    current = current.next;
  }

  // 以此从栈结构中取出元素, 放到一个新的链表中
  const newHead: ListNode = stack.pop()!;
  let newHeadCurrent = newHead;
  while (stack.length) {
    const node = stack.pop()!;
    newHeadCurrent.next = node;
    newHeadCurrent = newHeadCurrent.next;
  }

  // 注意: 获取到最后一个节点时, 一定要将节点的next置为null,不然最后一个next会还是执行前一个对象节点,形成循环引用,形成递归
  newHeadCurrent.next = null;

  return newHead;
}

// 模拟数据进行测试
const node1 = new ListNode(1);
node1.next = new ListNode(2);
node1.next.next = new ListNode(3);

// 通过for循环模拟数据进行测试
// const node1 = new ListNode(1);
// let current1 = node1;
// for (let i = 0; i < 5; i++) {
//   current1.next = new ListNode(i);
//   current1 = current1.next;
// }

const newHead = reverseList(node1);

let current = newHead;
while (current) {
  console.log(current.val);
  current = current.next;
}

export {};

206. 反转链表(非递归)

在这里插入图片描述

import ListNode from "./面试题_ListNode";

function reverseList(head: ListNode | null): ListNode | null {
  // 1.判断节点为null, 或者只要一个节点, 那么直接返回即可
  if (head === null || head.next === null) return head;

  // 2.反转链表结构
  let newHead: ListNode | null = null;
  while (head) {
    // 链表 --变量是指向地址的,赋予的是地址中存在的值,变量可以再次发送改变,之前的指向变量已经赋予过值的不会改变
    //  eg  newHead = null (newHead值为null)   head.next = newHead  (head.next值为null) newHead = head  (newHead的位为整个head节点 ,而head.next值依然为null)
    // 1.让current节点指向下一个节点(head.next)
    // 目前:保留着下一个节点的引用,可以获取并且不会销毁
    const current: ListNode | null = head.next;
    // 2.改变head.next当前指向的节点,指向newHead
    // 对于第一个节点来说,指向的newHead就是指向null
    head.next = newHead;
    // 让newHead指向head节点
    // 目的是下一次遍历时,第二步操作,可以让下一个节点指向前一个节点
    newHead = head;
    // 让head移向下一个节点,指向current
    head = current;
  }

  return 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;
}

export {};

14_面试题三-反转链表(递归)

在这里插入图片描述

import ListNode from "./面试题_ListNode";

let count = 1;
function reverseList(head: ListNode | null): ListNode | null {
  // 如果使用的是递归, 那么递归必须有结束条件
  if (head === null || head.next === null) return head;
  const newHead = reverseList(head?.next ?? null);

  // 完成想要做的操作是在这个位置
  // 第一次来到这里的时候, 是倒数第二个节点 head是指向倒是第二个
  // head.next.next是递归后链表中最后一个节点的next 指向head递归到的节点
  head.next.next = head;
  head.next = null;

  return 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;
}

export {};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值