链表小结

本文详细介绍了LinkedList数据结构,包括单向链表、双向链表和循环链表,以及它们在Java中的实现。接着,通过讲解各种链表相关问题的解法,如双指针技巧、链表操作等,深入探讨了LinkedList在算法中的应用。通过这些,读者可以更好地理解和运用LinkedList这一重要的数据结构。
摘要由CSDN通过智能技术生成


前言

List 的两个重要实现类 ArrayList 和 LinkedList,各自的特点都是非常明显的。

ArrayList:

  1. 底层是数组,在逻辑上和物理上来说都是连续的,也就是说数组在内存上是存储在连续的空间里面的,这种存储的优势是通过 index(索引)可以实现快速查找和随机访问,操作复杂度只是O(1)。
  2. ArrayList 的缺点也很显然。对于插入和删除操作,每次插入或者删除元素,都需要对插入位置后面的元素进行后移或前移操作;如果数组已满还需要进行空间申请、拷贝数据、释放空间的一系列扩容操作,最差时间复杂度达到了O(N),对于规模大的数组来说不太友好。

LinkedList:

  1. 相比于ArrayList来说有着很大的不同。它将每个元素定义为一个节点,在节点中又申请了一块空间用于存放下一个节点的指针(pointer)。由于有指针的存在,链表在内存中一般来说都不是连续的,充分利用了内存中的碎片空间。
  2. 链表的插入、删除操作时间复杂度由于指针的存在变成了O(1)。但是,链表是不支持随机访问的,每次查找或者访问都需要遍历链表,从最差角度和平均角度来看,时间复杂度是O(N)。

LinkedList 和 ArrayList 作为基本的数据结构,在程序设计中经常用到,根据场景的不同使用不同的数据结构也是能够有很好的效果。相比较而言链表更抽象,更难理解,所以对链表 LinkedList 的实现以及 leetcode 刷过的所有链表题型进行一个归纳总结。


一、LinkedList(链表)

链表总体来说有很多类型,这里只是记录一下常用的单向链表、双向链表以及循环链表。

1. 单向链表

单向链表比较简单,每个节点只有两个域:数据域(val)和指针域(next),指针域 next 指向下一个节点,尾结点的 next 是 null 。
1.1 单向链表节点定义

    /**
     * Definition for singly-linked list.
     */
    class SinglyNode {
        int val;// 数据域
        SinglyNode next;// 指针域
        public SinglyNode(int val) {
            this.val = val;
        }
    }

1.2 单向链表的实现

public class MySinglyLinkedList {

    /**
     * Definition for singly-linked list.
     */
    class SinglyNode {
        int val;
        SinglyNode next;
        public SinglyNode(int val) {
            this.val = val;
        }
    }

    public SinglyNode head;// 头结点

    /**
     * 头插法
     */
    public void addFirst(int val) {
        SinglyNode node = new SinglyNode(val);
        // 第一次插入结点
        if (this.head == null) {
            this.head = node;
            return;
        }
        node.next = this.head;
        this.head = node;
    }

    /**
     * 尾插法
     */
    public void addLast(int val) {
        SinglyNode node = new SinglyNode(val);
        // 第一次插入结点
        if (this.head == null) {
            this.head = node;
            return;
        }
        // 遍历到尾部
        SinglyNode cur = this.head;
        while (cur.next != null) {
            cur = cur.next;
        }
        // 插入结点
        cur.next = node;
    }

    /**
     * 任意位置插入
     */
    public void addIndex(int index, int val) {
        // index 不合法
        if (index < 0 || index > size()) return;
        // 下标为 0, 头插法
        if (index == 0) {
            addFirst(val);
            return;
        }
        // 查找 index 的前驱
        SinglyNode prev = searchPrev(index);
        SinglyNode node = new SinglyNode(val);
        // 插入
        node.next = prev.next;
        prev.next = node;
    }

    /**
     * 查找 index 结点的前驱
     */
    public SinglyNode searchPrev(int index) {
        int count = 0;
        SinglyNode cur  = this.head;
        while (count < index-1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

    /**
     * 查找在单链表当中是否包含关键字 key
     */
    public boolean contains(int val) {
        SinglyNode cur = this.head;
        while (cur != null) {
            if (cur.val == val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    /**
     * 删除第一次出现关键字为key的节点
     */
    public void remove(int val) {
        if (this.head.val == val) {
            this.head = this.head.next;
            return;
        }
        SinglyNode cur = this.head;// 遍历查找关键字
        SinglyNode prev = null;// 遍历结点的前驱结点
        // 查找关键字
        while (cur != null) {
            if (cur.val == val) {
                prev.next = cur.next;
                return;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
    }

    /**
     * 删除所有值为key的节点
     */
    public void removeAllKey(int val) {
        SinglyNode prev = this.head;
        SinglyNode cur = this.head.next;
        while (cur != null) {
            if (cur.val == val) {
                prev.next = cur.next;
            }else {
                prev = cur;
            }
            cur = cur.next;
        }
        // 判断头结点是否是删除结点
        if (this.head.val == val) {
            this.head = this.head.next;
        }
    }

    /**
     * 获取单链表的结点个数
     */
    public int size() {
        SinglyNode cur = this.head;
        int count = 0;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    /**
     * 打印单链表
     */
    public void display() {
        SinglyNode cur = this.head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 删除单链表所有结点
     */
    public void clear() {
        this.head = null;
    }


    public static void main(String[] args) {
        MySinglyLinkedList mySinglyList = new MySinglyLinkedList();

        // 添加元素
        mySinglyList.addFirst(1);
        mySinglyList.addFirst(2);
        mySinglyList.addIndex(0,1);
        mySinglyList.addIndex(3,4);
        mySinglyList.addLast(1);
        mySinglyList.addLast(5);

        // 打印单链表
        mySinglyList.display();// 1 2 1 4 1 5

        // 查找在单链表当中是否包含关键字 key
        System.out.println(mySinglyList.contains(5));// true
        System.out.println(mySinglyList.contains(6));// false

        // 删除第一次出现关键字为key的节点
        mySinglyList.remove(5);
        mySinglyList.display();// 1 2 1 4 1

        // 删除所有出现的关键字
        mySinglyList.removeAllKey(1);
        mySinglyList.display();// 2 4

        // 清空单链表
        mySinglyList.clear();
        mySinglyList.display();//
    }
}

2. 双向链表

双向链表就较为复杂了,一个节点有三个域:一个数据域(val)和两个指针域(prev、next),其中 prev 指向当前节点的前一个节点(前驱节点),next 指向当前节点的下一个节点(后继节点)。当然,头部节点的前驱节点是 null,尾部节点的后继节点是 null。
2.1 双向链表节点的定义

    /**
     * Definition for doubly-linked list.
     */
    class DoublyNode {
        int val;// 数据域
        DoublyNode next;// 指向后继节点
        DoublyNode prev;// 指向前驱节点
        public DoublyNode(int val) {
            this.val = val;
        }
    }

2.2 双向链表的实现

public class MyDoublyLinkedList {
    /**
     * Definition for doubly-linked list.
     */
    class DoublyNode {
        int val;
        DoublyNode next;
        DoublyNode prev;
        public DoublyNode(int val) {
            this.val = val;
        }
    }

    public DoublyNode head;// 头结点
    public DoublyNode rear;// 尾结点
    /**
     * 头插法
     */
    public void addFirst(int val) {
        DoublyNode node = new DoublyNode(val);
        // 第一次插入
        if (this.head == null) {
            this.head = node;
            this.rear = node;
        }else {
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }
    }

    /**
     * 尾插法
     */
    public void addLast(int val) {
        DoublyNode node = new DoublyNode(val);
        // 第一次插入
        if (this.head == null) {
            this.head = node;
            this.rear = node;
        }else {
            this.rear.next = node;
            node.prev = this.rear;
            this.rear = node;
        }
    }

    /**
     * 任意位置插入
     */
    public void addIndex(int index, int val) {
        // 插入下标非法
        if (index < 0 || index > size()) return;
        // index = 0
        if (index == 0) {
            addFirst(val);
            return;
        }
        if (index == size()) {
            addLast(val);
            return;
        }
        // 查找前驱结点
        DoublyNode prev = searchPrev(index);
        DoublyNode node = new DoublyNode(val);
        if (prev == null) return;
        node.next = prev.next;
        prev.next.prev = node;
        prev.next = node;
        node.prev = prev;
    }

    /**
     * 查找 index 的前驱结点
     */
    public DoublyNode searchPrev(int index) {
        DoublyNode cur = this.head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }


    /**
     * 查找在单链表当中是否包含关键字 key
     */
    public boolean contains(int key) {
        DoublyNode cur = this.head;
        while (cur != null) {
            if (cur.val == key) return true;
            cur = cur.next;
        }
        return false;
    }

    /**
     * 删除第一次出现关键字为key的节点
     */
    public void remove(int key) {
        if (this.head.val == key) {
            this.head = this.head.next;
            return;
        }
        DoublyNode cur = this.head;
        DoublyNode prev = null;// 当前结点的前驱结点
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                return;
            }else {
                prev = cur;
            }
            cur = cur.next;
        }
    }

    /**
     * 删除所有关键字为 key 的节点
     */
    public void removeAllKey(int key) {

        DoublyNode cur = this.head.next;
        DoublyNode prev = this.head;
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
            }else {
                prev = cur;
            }
            cur = cur.next;
        }
        // 头部结点判断是否需要删除
        if (this.head.val == key) {
            this.head = this.head.next;
            return;
        }
    }
    
    /**
     * 得到双链表的长度
     */
    public int size() {
        int count = 0;
        DoublyNode cur = this.head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    /**
     * 打印双链表
     */
    public void display() {
        DoublyNode cur = this.head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 清空双链表
     */
    public void clear() {
        this.head = null;
        this.rear = null;
    }

    public static void main(String[] args) {
        MyDoublyLinkedList myDoublyLinkedList = new MyDoublyLinkedList();

        // 添加元素
        myDoublyLinkedList.addFirst(3);
        myDoublyLinkedList.addFirst(2);
        myDoublyLinkedList.addFirst(1);
        myDoublyLinkedList.addIndex(0,0);
        myDoublyLinkedList.addIndex(0,1);
        myDoublyLinkedList.addIndex(5,5);
        myDoublyLinkedList.addLast(4);
        myDoublyLinkedList.addLast(1);

        // 打印双链表
        myDoublyLinkedList.display();// 1 0 1 2 3 5 4 1

        // 是否包含某个关键字
        System.out.println(myDoublyLinkedList.contains(1));// true
        System.out.println(myDoublyLinkedList.contains(6));// false

        // 删除第一次出现关键字为key的节点
        myDoublyLinkedList.remove(1);
        myDoublyLinkedList.display();// 0 1 2 3 5 4 1

        // 删除所有出现的关键字 key
        myDoublyLinkedList.removeAllKey(1);
        myDoublyLinkedList.display();// 0 2 3 5 4

        // 清空双链表
        myDoublyLinkedList.clear();
        myDoublyLinkedList.display();//
    }
}

3. 循环链表

将链表的尾结点指向头结点(首尾相连)就是循环链表了,。其实也相当于无头无尾,单向链表和双向链表都可以变为环形链表,这是很好理解的。接下来通过相关的题型进一步了解。

二、LinkedList(链表)相关题型及解法

1. 常用方法

1.1. 多指针(双指针)
当需要跟踪保存多个节点时,使用多指针实现。最常用的就是双指针,巧妙地使用双指针某些时候可以有奇效,很好地降低了空间时间复杂度。

对于双指针的使用又有快慢指针、首尾指针以及普通的双指针。
对于快慢指针简单的模板:

// Initialize slow and fast pointers
ListNode fast = head;
ListNode slow = head;
// move
while(fast != null && fast.next != null) {
	fast = fast.next.next;// fast pointer move two steps each time.
	slow = slow.next;// slow pointer move one step each time.
}

1.2. 傀儡头结点
当有可能对头部节点做出一些操作,使得头节点发生改变的时候,定义一个傀儡头结点可能更易于实现。
1.3. 模拟
通过对于题目的一步步解析,使用代码去模拟每一步实现相关操作。

2. 题型解析

2.1. 双指针

首先是可以看作方法的两道题型:

  • leetcode 876.链表的中间节点快慢指针。
    快指针 fast 每次走两步,慢指针 slow 每次走一步,当快指针走到链表尾部的时候,慢指针刚好位于链表中间。
public ListNode middleNode(ListNode head) {
	if (head == null) return null;
	ListNode fast = head;
	ListNode slow = head;
	while (fast != null && fast.next != null) {
		fast = fast.next.next;
		slow = slow.next;
	}
	return slow;
}
  • leetcode 206.反转链表:双指针。
    指针 cur 用于遍历链表,指针 prev 指向 cur 的前驱节点;每一次的反转就是将 cur.next 指向了自己的前驱节点 prev.
public ListNode reverseList(ListNode head) {
	if (head == null) return null;
	ListNode cur = head;
	ListNode prev = null;
	while (cur != null) {
		ListNode curNext = cur.next;
		cur.next = prev;
		prev = cur;
		cur = curNext;
	}
	return prev;
}

然后就是双指针的其他题型:

  • leetcode 19.删除链表的倒数第 k 个节点:快慢指针
    既然需要找倒数第 k 个节点,而 k 一定有效。那就先让 fast 先走 k 步,如果此时 fast=null 说明要删除的就是头节点;反之就让 slow 和 fast 一起一起同速走,当 fast 再走 size-k 步到达尾结点的时候,slow 刚好走到待删除节点的前驱节点,直接删除!

  • leetcode 86.分割链表:双指针+傀儡头结点
    两个操作:一是分割;而是合并。
    定义两个傀儡头节点 beforeDummy 和 afterDummy 分别用于存放小于 x 的节点和其他节点;分别使用 before 和 after 给两个链表连接节点,而原链表之后没用了,可以直接使用 head 遍历。遍历完原链表之后,将两个链表连接起来,beforeDummy 当然在前。

  • leetcode 92.反转链表Ⅱ:双指针
    定义 cur 是待反转节点,prev 指向待反转节点的前驱节点。
    cur 先走 m-1 步到第一个待反转节点,保存反转部分的前驱 first;然后向后反转 n-m+1 个节点,保存反转部分的下一个节点 last。反转之后需要重新连接,first 下一个节点是反转部分的尾结点,last 的前一个节点是反转部分的头节点。

  • leetcode 109.有序链表转二叉搜索树:快慢指针
    二叉搜索树中序遍历有序。
    使用快慢指针查找中间节点将链表分割成两部分,将中间节点作为二叉搜索树的根节点,递归地去查找根节点的左右子树。

  • leetcode 141.环形链表:快慢指针
    使用快慢指针遍历链表,如果存在环快慢指针肯定在环里面相遇了;反之不相遇,而是 fast 走到了尾部。

  • leetcode 142.环形链表Ⅱ:快慢指针+双指针
    如果存在环了两个指针肯定在环里面。然后令 fast 和 slow 分别从相遇点和 head 出发每次走一步向前,两个指针相遇点就是环入口处。

  • leetcode 143.重排链表:快慢指针+首尾指针
    快慢指针查找中间节点 + 反转后半部分链表。然后从两头遍历链表,将后面的指针 slow 指向的节点插入到前面指针 fast 的后面。重复执行直到两个指针相遇(节点个数是奇数)或相邻(节点个数是偶数)。在插入过程中为了防止走丢,插入之前将原本的 next 节点记录下来。可以称为四指针?

  • leetcode 160.Romantic相交链表:双指针
    两个链表相交一定是 Y 型的。使用 指针1号 先走链表 1 再走链表 2;使用 指针2号 先走链表 2,再走链表 1。这路程是一样的,而如果相交了又是 Y 型,这后半辈子不就是一块走?
    使用指针 pA 由 A 到 B,pB 由 B 到 A,如果两个指针相等的时候为 null,那就没相遇,反之肯定相遇了。

  • leetcode 203.移除链表元素:双指针
    指针 cur 指向当前节点(初始为head.next),prev 指向 cur 的前一个节点(初始为head),和给定 val 相等就删除。最后判断一下头节点相等否?

  • leetcode 234.回文链表:快慢指针
    通过快慢指针寻找到中间节点,然后反转后半部分链表。这倒好,两个方法都用了。然后使用两个指针分别从两头同速遍历链表进行比较,直到相遇了(节点个数奇数)或者相邻了(节点个数偶数)还没找到不同的,就是回文链表了。

  • leetcode 328.奇偶链表:双指针
    方法1: 构建两个新链表遍历连接即可。
    方法2: 奇偶总是相邻的,所以直接将 head 作为奇链表头,head.next 作为偶链表头,使用双指针 odd 和 even 进行连接:奇数节点的下一个节点是偶数节点的下一个节点,后移;偶数节点的下一个节点是奇数节点的下一个节点,后移。直到遍历完将偶链表连接在奇链表之后即可。

2.2. 其他题型

  • leetcode 2.两数相加:模拟
    链表逆序存储数字,那不正好?直接对应位置相加向高位进位,然后继续模拟两个数字相加就好。

  • leetcode 21.合并两个有序链表:傀儡头节点
    定义一个傀儡头节点用于储存合并之后的链表。使用各自的头节点作为指针遍历两个链表比较当前节点的值,然后将较小的添加到新链表中。直到有一个链表为 null 就不需要继续了,另一个链表直接添加到新链表。

  • leetcode 23.合并 k 个有序链表:归并 / 优先级队列
    方法1: k 个链表两个一组 + 合并两个有序链表,循环进行直到链表有序。
    方法2: 将 k 个链表的头节点装入优先级队列(小根堆),每次弹出最小的节点连接,并将该节点所在链表的下一节点装入优先级队列。重复直到队列为空。

  • leetcode 24.两两交换链表中的节点:递归 / 迭代
    方法1: 递归。两个节点作为一组,每一层反转这两个节点。反转之后的尾结点连接下一层的头节点,每一层将反转之后的头节点返回给上一层,当剩余 0/1 个节点不需要反转终止。
    方法2: 迭代。需要定义傀儡头节点 dummyHead
    每一次初始:prev 是待交换部分的前一个节点,cur 是交换的第一个节点;
    每一次操作:将第二个节点插入到第一个结点之前。

  • leetcode 25.k 个一组反转链表:分治
    方法1: k 个一组进行反转,反转之后重新连接:反转之后的尾部连接未反转部分,头部连接在已反转链表之后。
    方法2: 直接反转。
    (1)分组之后,对于每一组将待反转节点插入到小组的头节点和其前驱节点之间。
    (2)修改指针指向,进行下一组反转;
    (3)重复前两步直到剩余节点个数不足一组。

  • leetcode 61.旋转链表:模拟 / 环形链表
    方法1: 将一部分前面的节点重新连接到另一部分的后面,由于存在 k > size 的可能,所以肯定是需要 k=k%size。如果 k是 0 就不用变,否则将前 size-k 个元素断开重新连接到另一部分的尾节点后面,然后整体链表的尾节点的 next 置为 null 即可。
    方法2: 结环在断开。把尾节点的 next 指向头节点,然后从 head 开始移动 size-k%size 位就是尾结点了,下一节点是头节点,断开。

  • leetcode 82.删除排序链表中的重复元素Ⅱ:模拟 / 递归
    方法1: 定义傀儡头节点用于储存新链表。遍历链表,遇到重复节点就跳过,需要注意多跳过一个(重复节点的最后一个和下一个也不相同);反之添加到新链表。
    方法2: 递归。每一层都需要判断当前节点和下一节点是否相等,相等就跳过(多跳一次);否则连接到上一层的 next,也就是说每一层返回值是当前的不重复节点。直到遍历完链表。

  • leetcode 83.删除排序链表中的重复元素:模拟
    遍历链表,遇到重复节点就删除。

  • leetcode 138.复制带随机指针的链表:Map
    将原链表节点作为 key,根据原链表节点值实例的新节点作为 value存储;通过 key 将原链表节点的 next 和 random 复制到 key所对应的 value 相对应的位置也就是新链表中的 next 和 random 中即可。
    (1)next :map.get(key).next = map.get(key.next)
    (2)random :map.get(key).random = map.get(key.random )

  • leetcode 147.对链表进行插入排序:插入排序
    定义一个傀儡头节点便于可能会插入到头节点之前。使用指针 cur 遍历链表排序:两种情况:

  1. 待排序节点大于等于前一个结点,那就不用管了,下一个;
  2. 否则,使用指针 temp 从傀儡头节点开始比较查找第一个大于待排序节点的节点,插入在它的前面(比较 temp.next 和 cur)。
  • leetcode 148.链表排序:归并排序
    合并两个有序链表,刚刚好。
    定义一个傀儡头节点连接每一次排序后的头节点。subLen 作为每次的分组链表长度(初始为 1),每次是前一次的二倍。根据 subLen 进行分组,每一组链表都要断开,当然,需要考虑到最后一组可能长度不够不存在第二个链表。

  • leetcode 237.删除链表中的节点:
    修改节点值 OK!

  • leetcode 430.扁平化多级链表:递归 / 二叉树遍历
    方法1: 递归。递!每个节点没有 child 向后走,有 child 就向下一层走,直到最后一层遍历完整个"子链表";归!将上一层 “父节点” 的下一节点连接在 “子链表” 之后即可。每一层在经过 child 以后需要将 child 置为 null。
    方法2: 二叉树。将 child 看作是左子树,next 是右子树,进行前序遍历。需要使用prev 指向栈顶节点的前驱节点 + 栈:将弹出的栈顶节点连接在 prev 之后,使用栈存储该节点的右子树(next)和左子树(child)(如果不为 null),同时将 child 置为 null。

  • leetcode 445.两数相加Ⅱ:模拟
    方法1: 1.反转两个链表,2.两数相加,3.反转结果链表。
    方法2: 使用栈,先进后出恰好!1.压栈, 2.出栈两数相加,3.将每个结果插入到新链表的头节点后面。

  • leetcode 707.设计链表
    实现单向链表以及带有虚拟头尾节点的双向链表。

  • leetcode 725.分隔链表:模拟
    求出链表长度 size, 每组有 n=size/k 个节点,可能会有 m=size%k(m<k) 个节点多出来,那就需要在前 m 组中每组多放入一个节点。也就是前 m 组每组 n+1 个节点,后面每组 n 个节点。最后需要注意的就是分隔的时候需要将每组最后一个节点的 next 置为 null。

  • leetcode 817.链表组件:模拟
    遍历链表,当前节点在列表 G,组件数+1,向后遍历直到下一个连续节点不在列表 G 中结束该组件,进行下一个组件的判断。

  • leetcode 1019.链表中的下一个更大节点:暴力 / 单调栈
    方法1: 暴力、取出所有节点值顺序装入 list,两重循环寻找第一个大于自身的值即可。
    方法2: 单调栈、维护一个单调递增栈:将所有不大于当前节点值的节点弹出栈;栈为空说明对应数组位置为 0,否则就是栈顶结点值。最后将当前节点压栈。

  • leetcode 1171.从链表中删去总和值为 0 的连续节点:Map
    总和值相等的两个节点直接的总和值为 0.
    第一次 map:遍历链表计算总和值作为 key,并保存对应节点,总和值相同时覆盖更新;
    第二次 map:遍历遍历计算总和值获取 map 中存取的相应节点连接该节点的下一个节点。

  • leetcode 1367.二叉树中的列表:递归
    需要使用两个递归函数:一个用于遍历二叉树,另一个判断以当前二叉树节点开始向下的路径是否全部和链表相同。

  • leetcode 1290.二进制数转整数:暴力 / 位运算
    方法1: 暴力。取出所有节点的值连接为字符串,调用Integer.parseInt()转换为十进制。
    方法2: 位运算。遍历节点的每次后移相当于前面所有节点都左移了一位,所以在前面结果左移 1 之后加上当前的节点值就 OK.

  • leetcode 1669.合并两个链表:模拟
    先删除 [a, b] 之间的节点,保存 a-1 和 b+1 节点用于连接另一个链表。
    注意的只有一点:这个下标是从 0 开始的。

  • leetcode 1670.设计前中后队列:list / 双向链表
    方法1: List 实现直接使用 List 方法。
    方法2: 双向链表实现带有虚拟头尾节点。

  • 剑指Offer 06.从尾到头打印链表:栈 / 反向装入
    方法1: 使用栈"先进后出"特性。
    方法2: 遍历求出链表长度以后根据反向的下标装入节点值。

  • 面试题 0201.移除重复结点:Set去重 / 数组
    方法1: Set去重。Set 中存在就删除该节点。
    方法2: 数组。节点值对应下标有元素就删除;没有就加进去。


总结

根据情况的不同选用合适的方法可以极大地提高效率,也可以使用一些奇思妙想的方法来解答,但是对于抽象的链表要有一个很清晰地理解。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值