本文主要记录了自己跟着代码随想录通过leetcode上与链表有关的题学习链表知识时整理的笔记。首先学习链表的基础知识,比如链表的定义、类型、存储方式、定义的代码等。然后对链表的基本操作进行简单的介绍,比如链表删除节点、添加节点。同时,分析对比了链表和数组的性能。此外,通过leetcode上链表的经典题目的分析和实现来掌握链表的具体使用,例如删除、增加、查找、两两交换链表的节点、反转链表、判断两个链表是否相交,相交则求出相交的节点、判断链表是否有环,有环则求出环的入口等等。最后,对本文的知识点进行了总结,并附上总结的流程图和本文涉及的所有题目链接地址。
链表基础知识
链表的类型
链表的存储方式
链表的定义代码
链表的基本操作
删除节点
添加节点
与数组性能对比分析
链表经典题目(附详细的解题分析和c++代码)
一、删除链表
二、反转链表
三、两两交换链表中的节点
四、设计链表(增、删、查)
五、删除链表倒数第n个节点
六、链表相交
七、环形链表
链表总结
虚拟头结点
链表的基本操作
反转链表
删除倒数第N个节点
链表相交
环形链表
总结的思维导图
题目链接总结
定义:链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
如图所示:
链表主要有单链表、双链表和循环链表三种。
刚刚说的就是单链表。
注意:单链表中的指针域只能指向节点的下一个节点。
定义:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
优点:既可以向前查询也可以向后查询。
如图所示:
定义:首尾相连的链表。
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
如图所示:
这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。
C/C++的定义链表节点方式,如下所示:
// 单链表
struct ListNode {int val; // 节点上存储的元素ListNode *next; // 指向下一个节点的指针ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
C++默认生成一个构造函数(不会初始化任何成员变量),所以定义时可以不写构造函数。
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!
删除D节点,如图所示:
分析:只要将C节点的next指针 指向E节点就可以了。
注意:D节点不是依然存留在内存里,只不过是没有在这个链表里而已。所以在C++里最好是再手动释放这个D节点,释放这块内存。
其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。
如图所示:
链表的增添和删除都是O(1)操作,且不会影响到其他节点。
要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。
链表的特性和数组的特性进行一个对比,如图所示:
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
主要对链表的基本操作进行方法的介绍和详细地说明,同时会附带相关的题目来加强理解和学习。学习主要参考代码随想录,感兴趣的可以了解一下,解释写得很详细。
题目链接:203. 移除链表元素
题目描述:给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == 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)设置一个虚拟头结点在进行移除
可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
return 头结点的时候,别忘了 return dummyNode->next;
, 这才是新的头结点
代码
(1)不设虚拟节点且删除时没有手动在内存中删除被删除的节点
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {while(head && head -> val == val) {head = head -> next;}if (!head) return head;ListNode *cur = head;while(cur -> next) {if(cur -> next -> val == val) {cur -> next = cur -> next -> next;}else {cur = cur -> next;}}return head;}
};
(2)不设虚拟头结点且手动删除在内存中删除被删除的节点
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {// 删除头结点while (head != NULL && head->val == val) { // 注意这里不是ifListNode* tmp = head;head = head->next;delete tmp;}// 删除非头结点ListNode* cur = head;while (cur != NULL && cur->next!= NULL) {if (cur->next->val == val) {ListNode* tmp = cur->next;cur->next = cur->next->next;delete tmp;} else {cur = cur->next;}}return head;}
};
(3)设置虚拟头结点并手动在内存中删除被删除的节点
class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作ListNode* cur = dummyHead;while (cur->next != NULL) {if(cur->next->val == val) {ListNode* tmp = cur->next;cur->next = cur->next->next;delete tmp;} else {cur = cur->next;}}head = dummyHead->next;delete dummyHead;return head;}
};
题目链接:206. 反转链表
题目描述:给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例2:输入:head = [1,2] 输出:[2,1]
示例 3:输入:head = [] 输出:[]
解题思路:
(1)双指针法
只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
如动画所示:(纠正:动画应该是先移动pre,在移动cur)
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
代码
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* pre = NULL;ListNode* cur = head;ListNode* temp;//cur的next,用来存储遍历到的节点的下一个节点while (cur) {temp = cur -> next;cur -> next = pre;pre = cur;cur = temp;}return pre;}
};
(2)递归法【还未研究】
递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。
关键是初始化的地方,可以看到双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的,只不过写法变了。
具体可以看代码(已经详细注释),双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。
class Solution {
public:ListNode* reverse(ListNode* pre,ListNode* cur){if(cur == NULL) return pre;ListNode* temp = cur->next;cur->next = pre;// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步// pre = cur;// cur = temp;return reverse(cur,temp);}ListNode* reverseList(ListNode* head) {// 和双指针法初始化是一样的逻辑// ListNode* cur = head;// ListNode* pre = NULL;return reverse(NULL, head);}};
我们可以发现,上面的递归写法和双指针法实质上都是从前往后翻转指针指向,其实还有另外一种与双指针法不同思路的递归写法:从后往前翻转指针指向。
具体代码如下(带详细注释):
class Solution {
public:ListNode* reverseList(ListNode* head) {// 边缘条件判断if(head == NULL) return NULL;if (head->next == NULL) return head;// 递归调用,翻转第二个节点开始往后的链表ListNode *last = reverseList(head->next);// 翻转头节点与第二个节点的指向head->next->next = head;// 此时的 head 节点为尾节点,next 需要指向 NULLhead->next = NULL;return last;}
};
题目链接:24. 两两交换链表中的节点
题目描述:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:输入:head = [1,2,3,4]输出:[2,1,4,3]
示例 2:输入:head = [] 输出:[]
示例 3:输入:head = [1] 输出:[1]
解题分析:画图理解
代码
class Solution {
public:ListNode* swapPairs(ListNode* head) {ListNode *newNode = new ListNode(0);//创建虚拟头结点newNode -> next = head;ListNode* cur = newNode;while (cur -> next && cur -> next -> next) {ListNode* temp = cur -> next;ListNode* temp1 = cur -> next -> next ->next;cur -> next = cur -> next -> next;//步骤一cur -> next -> next = temp;//步骤二temp -> next =temp1;//步骤三cur = cur -> next -> next;//步骤四}return newNode -> next;//注意返回的是虚拟头节点的下一个节点}
};
【还很容易乱】
题目链接:707. 设计链表
题目描述:你可以选择使用单链表或者双链表,设计并实现自己的链表。单链表中的节点应该具备两个属性: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
的节点。示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
(1); // 返回 3
解题思路:
删除链表节点:
添加链表节点:
代码
class MyLinkedList {
public:// 定义链表节点结构体struct LinkedNode {int val;LinkedNode* next;LinkedNode(int val):val(val), next(nullptr){}};// 初始化链表MyLinkedList() {_dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点_size = 0;}// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点int get(int index) {if (index > (_size - 1) || index < 0) {return -1;}LinkedNode* cur = _dummyHead->next;while(index--){ // 如果--index 就会陷入死循环cur = cur->next;}return cur->val;}// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点void addAtHead(int val) {LinkedNode* newNode = new LinkedNode(val);newNode->next = _dummyHead->next;_dummyHead->next = newNode;_size++;}// 在链表最后面添加一个节点void addAtTail(int val) {LinkedNode* newNode = new LinkedNode(val);LinkedNode* cur = _dummyHead;while(cur->next != nullptr){cur = cur->next;}cur->next = newNode;_size++;}// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点// 如果index大于链表的长度,则返回空// 如果index小于0,则在头部插入节点void addAtIndex(int index, int val) {if(index > _size) return;if(index < 0) index = 0; LinkedNode* newNode = new LinkedNode(val);LinkedNode* cur = _dummyHead;while(index--) {cur = cur->next;}newNode->next = cur->next;cur->next = newNode;_size++;}// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的void deleteAtIndex(int index) {if (index >= _size || index < 0) {return;}LinkedNode* cur = _dummyHead;while(index--) {cur = cur ->next;}LinkedNode* tmp = cur->next;cur->next = cur->next->next;delete tmp;//delete命令指示释放了tmp指针原本所指的那部分内存,//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间tmp=nullptr;_size--;}// 打印链表void printLinkedList() {LinkedNode* cur = _dummyHead;while (cur->next != nullptr) {cout << cur->next->val << " ";cur = cur->next;}cout << endl;}
private:int _size;LinkedNode* _dummyHead;};
index
的相关操作为 O(index), 其余为 O(1)题目链接:19. 删除链表的倒数第 N 个结点
题目描述:给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:输入:head = [1], n = 1 输出:[]
示例 3:输入:head = [1,2], n = 1 输出:[1]
解题思路:可以通过快慢指针的思想。具体操作如下:初始化快慢指针都指向虚拟头结点。让快指针fast提前走n步,之后快慢指针再一起走,直到快指针走到最后一个节点,此时在删除慢指针后面的那个节点。
解题代码
class Solution {
public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* newNode = new ListNode(0);newNode->next = head;ListNode* low = newNode;ListNode* fast = newNode;while (n-- && fast->next) {fast = fast -> next;}while (fast->next) {fast = fast->next;low = low->next;}ListNode* temp = low->next;low->next = low->next->next;delete temp;return newNode->next;}
};
题目链接:面试题 02.07. 链表相交
题目描述:给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
解题思路:此题的关键在于清楚两条链表判断节点相交,可以让长链表先走几步,直到剩下的长度和短链表一样长时,则两个链表逐一节点判断是否相等。注意:相等指的是指针相等,而不是数值相等。
1)求出两个链表的长度size1、seize2,并求出两个链表长度的差值gap,然后让temp1移动到,和temp2 末尾对齐的位置,如图:
2)此时就可以比较temp1和temp2是否相同,如果不相同,同时向后移动temp1和temp2,如果遇到temp1== temp2,则找到交点。否则循环退出返回空指针。
代码
class Solution {
public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {ListNode* temp1 = headA;ListNode* temp2 = headB;int size1 = 0, size2 = 0;// 计算链表A的节点总数while (temp1) {size1++;temp1 = temp1->next;}// 计算链表B的节点总数while (temp2) {size2++;temp2 = temp2->next;}// 重新初始化两个指针指向头结点temp1 = headA;temp2 = headB;// 始终让链表A为长度较长的那个链表,如果不是,则将两个链表进行交换if (size1 < size2) {swap(size1, size2);swap(temp1, temp2);}// 计算两个链表的节点个数差int gap = size1 - size2;// 让长的链表即链表A提前走gap步while (gap--) {temp1 = temp1->next;}// 开始逐一遍历比较两个链表是否相等while (temp1) {// 如果相等,则直接返回if (temp1 == temp2) {return temp1;}temp1 = temp1->next;temp2 = temp2->next;}// 注意链表不相交的情况返回NULLreturn NULL;}
};
题目链接:142. 环形链表 II
题目描述:给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
解题思路:可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
动画如下:
如果有环,如何找到这个环的入口
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: slow指针走过的节点数为: x + y
, fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
动画如下:
那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。
其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。
代码
class Solution {
public:ListNode *detectCycle(ListNode *head) {ListNode* slow = head;ListNode* fast = head;while (fast && fast->next) {fast = fast->next->next;slow = slow->next;// 快慢指针相遇时,此时从head指针和快指针同时查找,直到相遇if (fast == slow) {ListNode* index1 = fast;ListNode* index2 = head;while (index1 != index2) {index1 = index1->next;index2 = index2->next;}// 返回环的入口return index2;}}return NULL;}
};
补充
在推理过程中,可能有一个疑问:为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?
首先slow进环的时候,fast一定是先进环来了。
如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。
重点来了,slow进环的时候,fast一定是在环的任意一个位置,如图:
那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。
因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。
也就是说slow一定没有走到环入口3,而fast已经到环入口3了。
这说明什么呢?
在slow开始走的那一环已经和fast相遇了。
那有同学又说了,为什么fast不能跳过去呢? 在刚刚已经说过一次了,fast相对于slow是一次移动一个节点,所以不可能跳过去。
首先介绍了链表的基础知识:
链表操作中一个非常总要的技巧:虚拟头节点。
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
在【一、删除链表】此题中,给出了用虚拟头结点和没用虚拟头结点的代码,对比发现使用虚拟头结点的好处。
在【四、设计链表(增、删、查)】把链表常见的五个操作练习了一遍。
在【二、反转链表】中,给出了两种反转的方式,迭代法和递归法。
建议先学透迭代法,然后再看递归法,因为递归法比较绕。可以先通过迭代法,彻底弄清楚链表反转的过程!
在【五、删除链表倒数第n个节点】中结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。
在【六、链表相交】中使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)
在【七、环形链表】中,讲解了在链表如何找环,以及如何找环的入口位置。
这道题目主要在于一些数学证明。
这个图是海螺人所画。
203. 移除链表元素
206. 反转链表
24. 两两交换链表中的节点
707. 设计链表
19. 删除链表的倒数第 N 个结点
面试题 02.07. 链表相交
142. 环形链表 II
本文发布于:2024-02-01 19:42:46,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170678776839003.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |