LeetCode体操-3 | 链表理论基础、203.移除链表元素、707.设计链表、206.反转链表

76 阅读5分钟

链表理论基础

简介

  • 链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
  • 链表的入口节点称为链表的头结点也就是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)
    }
}

图示

image.png

链表.png

203.移除链表元素

题目

删除链表中等于给定值 val 的所有节点。

示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]

示例 2: 输入:head = [], val = 1 输出:[]

示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]

解题思路

  1. 使用迭代法,代码实现上采用新增虚拟头节点的方式统一节点删除过程
  2. 声明虚拟头结点dummy且指针域指向head,声明cur初始化为dummy
  3. 当cur值不为空时执行while循环,在循环体中判断cur.next的值是否等于目标值,如相等则代表需移除cur.next节点(将cur.next赋值为cur.next.next)否则只需移动cur即可(cur赋值为cur.next)
  4. 最后返回链表起始点dummy.next;

代码实现

function removeElements(head: ListNode | null, val: number): ListNode | null {
    const dummy = new ListNode(0, head);
    let cur = dummy;
    while (cur.next) {
        if (cur.next.val === val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return dummy.next;
};

707.设计链表

题目

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

image.png

解题思路

  1. 题目要求实现一个链表,可以选择单链表或双链表。链表的基本操作包括插入节点、获取节点值、删除节点等。
  2. 单链表和双链表各有优缺点。单链表结构简单,但只能从头部开始遍历;双链表可以双向遍历,但结构复杂一些。根据题目要求,我们可以选择单链表来实现。
  3. 链表的节点至少包含两个属性:val(节点值)和next(指向下一个节点的引用)。如果选择双链表,还需要一个prev属性。
  4. MyLinkedList类需要实现以下功能:
    • 构造函数:初始化链表。
    • get(int index):根据索引获取节点值。
    • addAtHead(int val):在链表头部添加节点。
    • addAtTail(int val):在链表尾部添加节点。
    • addAtIndex(int index, int val):在指定索引位置添加节点。
    • deleteAtIndex(int index):删除指定索引位置的节点。
  5. 对于每个操作,我们需要考虑其时间复杂度和边界条件。例如,getdeleteAtIndex操作需要遍历链表,时间复杂度为O(n)。

代码实现

class MyLinkedList {
    private head: ListNode | null;
    private length: number;

    constructor() {
        this.head = null;
        this.length = 0;
    }

    get(index: number): number {
        let node = this.head;
        for (let i = 0; i < index && node !== null; i++) {
            node = node.next;
        }
        return node !== null ? node.val : -1;
    }

    addAtHead(val: number): void {
        const newNode = new ListNode(val);
        newNode.next = this.head;
        this.head = newNode;
        this.length++;
    }

    addAtTail(val: number): void {
        const newNode = new ListNode(val);
        if (!this.head) {
            this.head = newNode;
        } else {
            let node = this.head;
            while (node.next) {
                node = node.next;
            }
            node.next = newNode;
        }
        this.length++;
    }

    addAtIndex(index: number, val: number): void {
        const newNode = new ListNode(val);
        if (index < 0 || index > this.length) {
            return;
        }
        if (index === 0) {
            // 当index为0,即头部添加
            newNode.next = this.head;
            this.head = newNode;
        } else if (index === this.length) {
            // 当index等于链表长度,即尾部添加
            this.addAtTail(val);
        } else {
            let prevNode = this.head;
            for (let i = 0; i < index - 1; i++) {
                prevNode = prevNode.next!;
            }
            newNode.next = prevNode.next;
            prevNode.next = newNode;
            this.length++;
        }
    }
    
    deleteAtIndex(index: number): void {
        if (index >= this.length || index < 0) {
            return;
        }
        if (index === 0) {
            this.head = this.head.next;
        } else {
            let node = this.head;
            for (let i = 0; i < index - 1; i++) {
                node = node.next;
            }
            node.next = node.next.next;
        }
        this.length--;
    }
}

206.反转链表

题目

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

解题思路

  1. 使用迭代法(双指针)解决该题目相对好理解,在原链表上逐个节点反转指针域指向即可,过程中需要定义临时变量用于存储反转指针域之前的指向
  2. 定义慢指针pre、临时变量temp均初始化为null,定义快指针初始化为链表头节点head
  3. 当cur节点不为空时执行while循环,循环中先将cur.next保存到temp中,再依次将cur.next指向pre,pre赋值为cur,cur赋值为temp值实现往后移动一位
  4. 最后返回pre这个新的头结点即可

代码实现

function reverseList(head: ListNode | null): ListNode | null {
    let pre = null;
    let cur = head;
    let temp = null;
    while(cur){
        temp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = temp;
    }
    return pre
};