链表相关总结
链表问题极容易弄错,请一定要在纸上把过程先画出来,请一定手撕,请一定画示意图
虚拟头节点+迭代+递归
- 链表的种类主要为:单链表,双链表,循环链表
- 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
- 链表是如何进行增删改查的。
- 数组和链表在不同场景下的性能分析。
0、一道题目考察链表五个常见操作!
1、链表问题——穿针引线,头插法
不懂会很烦(递归),搞懂会很爽(头插法)
【206. 反转链表】
——方法一:三指针迭代:翻转当前节点,需要保存的是:下一个节点和上一个节点

class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = nullptr;//dummyHead
ListNode *cur = head;
while(cur!=nullptr){
ListNode *tmp = cur->next;//保存cur->next
cur->next = pre;//reverse
pre = cur;//pre往后走
cur = tmp;//cur往后走
}
return pre;//最后结束循环cur到尾后了,所以return pre
}
};
——方法二:递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//递归终止条件是当前为空,或者下一个节点为空
if(head == nullptr||head->next == nullptr){
return head;
}
//这里的cur就是最后一个节点
ListNode *cur = reverseList(head->next);//一直递归,直到触发终止条件
head->next->next = head;//如果链表是 1->2->3->4->5,那么此时的cur就是5//而head是4,head的下一个是5,下下一个是空
head->next = nullptr;//防止链表循环,需要将head.next设置为空
return cur;//每层递归函数都返回cur,也就是最后一个节点
}
};
【92. 反转链表 II】——精彩,极其精彩!
——方法一:穿针引线
class Solution {
public:
ListNode *reverseList(ListNode *head){
ListNode *pre = nullptr;
ListNode *cur = head;
while(cur!=nullptr){
ListNode *Next = cur->next;
cur->next = pre;
pre = cur;
cur = Next;
}
return pre;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummyHead = new ListNode(-1);//这里不可以是nullptr
dummyHead->next = head;
ListNode *pre = dummyHead;
for(int i = 0;i<left-1;i++){pre = pre->next;}//找到left的pre
ListNode *LeftNode = pre -> next;
ListNode *RightNode = pre;//利用下面的循环找RightNode
for(int i = 0;i<right-left+1;i++){RightNode = RightNode->next;}
ListNode *curr = RightNode->next;//先记录RightNode->next!不然找不到了
RightNode -> next = nullptr;//截断头尾
pre->next = nullptr;
reverseList(LeftNode);//reverse要求部分
pre->next = RightNode;//重新续上头尾
LeftNode->next = curr;
return dummyHead->next;
}
};
——方法二:头插法!妙啊妙啊!
看图看图
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummyHead = new ListNode(-1);//这里不可以是nullptr,括号里是0或者-1都行
dummyHead->next = head;
ListNode *pre = dummyHead;
for(int i = 0;i<left-1;i++){pre = pre->next;}
ListNode *leftNode = pre->next;
for(int i = 0;i<right-left;i++){//以left处的值 = 2为例
ListNode *remove = leftNode->next;//记录好原来的下一个位置 3
leftNode->next = leftNode->next->next;//2指向3的next 2->4
remove->next = pre->next;//3->2
pre->next = remove;//1->3
//第一轮后就变成了1->3 3->2 2->4
}
return dummyHead->next;
}
};

【203. 移除链表元素】
——删除问题的两种方法:迭代和递归
用dummy 来解决问题,这样就不需要多考虑一个头节点问题;
用迭代的方式。
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy = new ListNode(-1);
dummy ->next = head;
ListNode *tail = dummy;
while(tail ->next){//就算链表的题刷少了,不熟练
if(tail ->next ->val ==val){tail->next = tail->next->next;}
else{tail = tail->next;}
}
return dummy->next;
}
};
【82. 删除排序链表中的重复元素 II】
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。
返回同样按升序排列的结果链表。
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
基本思路
几乎所有的链表题目,都具有相似的解题思路。
1.建一个「虚拟头节点」dummy 以减少边界判断,往后的答案链表会接在 dummy 后面
2.使用 tail 代表当前有效链表的结尾
3.通过原输入的 head 指针进行链表扫描
我们会确保「进入外层循环时 head 不会与上一节点相同」,因此插入时机:
1.head 已经没有下一个节点,head 可以被插入
2.head 有一下个节点,但是值与 head 不相同,head 可以被插入
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
auto dummy = new ListNode(-1),tail = dummy;
while(head){
if(head->next == nullptr or head->val != head->next->val){
tail->next = head;
tail = head;
}
while(head->next and head->val == head->next->val) head = head->next;
head = head->next;
}
tail->next = nullptr;//用tail后面这个指向空就不会忘
return dummy->next;
}
};
【83. 删除排序链表中的重复元素】
输入:head = [1,1,2]
输出:[1,2]
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* dummy = new ListNode(109);
ListNode* tail = dummy;
while(head){
if(tail->val!=head->val){
tail->next = head;
tail = head;
}
head = head->next;
}
tail->next = nullptr;
return dummy->next;
}
};
【86. 分隔链表】
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。

——设立两个虚拟头节点smallHead bigHead
然后两个节点去穿针引线弄好小的和大的list
class Solution {
public:
ListNode* partition(ListNode* head, int x) {//要两个虚拟头节点smallHead bigHead ,然后两个节点去穿针引线弄好小的和大的list,
ListNode* smallHead = new ListNode(0);
ListNode* small = smallHead;
ListNode* bigHead = new ListNode(0);
ListNode* big = bigHead;
while(head){
if(head->val<x){
small->next = head;
small = head;
}
else{
big->next = head;
big = head;
}
head = head->next;
}
small->next = bigHead->next;//这就是bigHead的作用
big->next = nullptr;
return smallHead->next;
}
};
【21. 合并两个有序链表】
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:就是比较,注意是比较完一个list后有指向空的,这时候就要加上下面那两个while了!!!
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* tail = dummy;
if(!l1 and !l2) return nullptr;
if(!l1 and l2) return l2;
if(l1 and !l2) return l1;
while(l1 and l2){
if(l1->val <= l2->val){
tail->next = l1;
tail = l1;
l1 = l1->next;
}
else{
tail->next = l2;
tail = l2;
l2 = l2->next;
}
}
while(l1){
tail->next = l1;
tail = l1;
l1 = l1->next;
}
while(l2){
tail->next = l2;
tail = l2;
l2 = l2->next;
}
tail->next = nullptr;
return dummy->next;
}
};
【328. 奇偶链表】
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head==nullptr) return head;
ListNode *dummyHead = new ListNode (-1);
dummyHead->next = head;
ListNode *evenHead = head->next;//标记even的节点
ListNode *even = evenHead;//用even来移动
while(even!=nullptr && even->next!=nullptr){//更新条件要判断好
head ->next = even ->next;
head = head ->next;
even ->next = head ->next;
even = even ->next;
}
head ->next = evenHead;
return dummyHead->next;
}
};
【2. 两数相加】

思路:
1.用a和b分别存l1和l2的val(要先判断存在与否)
2.用 t 存进位
3.cur来存放当前值(也许叫sum更好)
4.更新
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *dummy = new ListNode(0);
ListNode *cur = dummy;
int t = 0;
while(l1 or l2){
int a = l1?l1->val:0;
int b = l2?l2->val:0;
t = a+b+t;
cur -> next = new ListNode(t%10);
t /= 10;
cur = cur->next;
if(l1) l1 = l1->next;
if(l2) l2 = l2->next;
}
if(t!=0) cur ->next = new ListNode(t);
return dummy->next;
}
};
【445. 两数相加 II】
——stack+头插法
思路:
这里是要变个方向,要求不能反转,所以用stack是最好的。把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。最后得到答案反转那里很妙,有点像头插法。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1,s2;
while(l1) {s1.push(l1->val);l1 = l1->next;}
while(l2) {s2.push(l2->val);l2 = l2->next;}
int t = 0;//进位
ListNode *tail = nullptr;//用于后面反转
while(!s1.empty() or !s2.empty() or t!=0){//注意t!=0也要放作条件
int a = s1.empty()?0:s1.top();
int b = s2.empty()?0:s2.top();
if(!s1.empty()) s1.pop();
if(!s2.empty()) s2.pop();
int cur = (a+b+t)%10;//return链表的val
t = (a+b+t)/10;//更新进位
ListNode *curnode = new ListNode(cur);//开一个新curnode来放cur的val
curnode ->next = tail;//画图理解下这里的反转,和之前头插法挺像的
tail = curnode;
}
return tail;
}
};
【24. 两两交换链表中的节点】

FN:node1 CN:node2 n:next
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* pre = dummy;
while(pre->next && pre->next->next){
ListNode* FN = pre->next;
ListNode* CN = FN ->next;
ListNode* n = CN->next;
CN ->next = FN;
FN ->next = n;
pre->next = CN;
pre = FN;
}
return dummy->next;
}
};
2、链表排序问题(待做)
【147. 对链表进行插入排序】
【148. 排序链表】
3、不仅是穿针引线的链表问题
【237. 删除链表中的节点】
——引出C++内存管理和设计原则(不太理解)链接
4、双指针
【19.删除链表的倒数第N个节点】

class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(-1);
dummy ->next = head;
ListNode* fast = dummy;
ListNode* slow = dummy;
while(n-- && fast!=nullptr){fast =fast->next;}
fast = fast->next;
while(fast!=nullptr){
fast = fast->next;
slow = slow->next;
}
ListNode* del_node = slow->next;//C++特色,要学会释放内存
slow->next = del_node->next;
delete del_node;
return dummy->next;
}
};
本文深入解析链表算法,涵盖单链表、双链表、循环链表等类型,详解链表的增删改查操作及性能分析。通过经典题目如反转链表、合并有序链表等,展示迭代和递归方法的应用。

254

被折叠的 条评论
为什么被折叠?



