21. 合并两个有序链表 | 力扣 | LeetCode | 【easy】
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4] 示例 2:
输入:l1 = [], l2 = [] 输出:[] 示例 3:
输入:l1 = [], l2 = [0] 输出:[0] 提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100 l1 和 l2 均按 非递减顺序 排列 题目来源:力扣 21. 合并两个有序链表。

我们的 while 循环每次比较 p1 和 p2 的大小,把较小的节点接到结果链表上

迭代
cpp
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
//虚拟头结点,更加便利的返回合并后的链表
ListNode* preHead = new ListNode(-1);
//维护prev指针
ListNode* prev = preHead;
while (l1 != nullptr && l2 != nullptr) {
if (l1->val < l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
if(l1==nullptr) prev->next=l2;
else prev->next=l1;
return preHead->next;
}
};
递归
cpp
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==nullptr)
return list2;
if(list2==nullptr)
return list1;
if(list1->val<=list2->val){
list1->next = mergeTwoLists(list1->next,list2);
return list1;
}
else{
list2->next = mergeTwoLists(list1,list2->next);
return list2;
}
}
};
86. 分隔链表 | 力扣 | LeetCode | 【medium】
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3 输出:[1,2,2,4,3,5] 示例 2:
输入:head = [2,1], x = 2 输出:[1,2] 提示:
链表中节点的数目在范围 [0, 200] 内
-100 <= Node.val <= 100
-200 <= x <= 200 题目来源:力扣 86. 分隔链表
尝试一下
cpp
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
//初始化两个虚拟头结点分别代表大于val和小于val
ListNode s(-1),l(-1);
ListNode* sL = &s;
ListNode* lL = &l;
while(head){
if(head->val<x){
sL->next=head;
sL=sL->next;
}
else{
lL->next=head;
lL=lL->next;
}
head=head->next;
}
//防止回环
lL->next=nullptr;
访问头结点
sL->next=l.next;
return s.next;
}
};
//写法二
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
// 存放小于 x 的链表的虚拟头结点
ListNode* dummy1 = new ListNode(-1);
// 存放大于等于 x 的链表的虚拟头结点
ListNode* dummy2 = new ListNode(-1);
// p1, p2 指针负责生成结果链表
ListNode* p1 = dummy1, *p2 = dummy2;
// p 负责遍历原链表,类似合并两个有序链表的逻辑
// 这里是将一个链表分解成两个链表
ListNode* p = head;
while (p != nullptr) {
if (p->val >= x) {
p2->next = p;
p2 = p2->next;
} else {
p1->next = p;
p1 = p1->next;
}
// 不能直接让 p 指针前进,
// p = p->next
// 断开原链表中的每个节点的 next 指针
ListNode* temp = p->next;
p->next = nullptr;
p = temp;
}
// 连接两个链表
p1->next = dummy2->next;
return dummy1->next;
}
};
很好思路一样,但是在.和->使用出错
什么时候使用->或者什么时候使用.?
你问得好,关键在于:什么时候用 .,什么时候用 ->。
✅ 一句话总结:
.(点):用于访问「对象本身」的成员。->(箭头):用于访问「指针所指对象」的成员。
📘 举个例子:
cpp
ListNode node; // 这是一个对象
ListNode* p = &node; // 这是一个指针
-
如果是对象:
cppnode.val; // ✅ 用 . 访问成员 node.next; // ✅ 用 . 访问成员 -
如果是指针:
cppp->val; // ✅ 用 -> 访问成员 p->next; // ✅ 用 -> 访问成员 (*p).val; // ✅ 这是等价写法,但繁琐
💡 原理解释:
p->val 等价于 (*p).val。
也就是说:
箭头
->是语法糖(语法简化),表示"先解引用,再访问成员"。
⚠️ 在你的代码里:
head 是一个指针(ListNode*),
所以必须用:
cpp
head->val
head->next
如果写成 head.val,编译器会报错:
"request for member 'val' in pointer type ..."
✅ 小总结:
| 变量类型 | 访问成员写法 | 示例 |
|---|---|---|
| 对象(非指针) | . |
node.val |
| 指针(指向对象) | -> |
head->val |
所以在链表题中(比如 LeetCode 的大多数链表题),
你几乎总是操作指针,自然就得用 ->。
23. 合并 K 个升序链表 | 力扣 | LeetCode | (hard)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [
1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
示例 2:
输入:lists = [] 输出:[] 示例 3:
输入:lists = [[]] 输出:[] 提示:
k == lists.length 0 <= k <= 10^4 0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4 lists[i] 按 升序 排列 lists[i].length 的总和不超过 10^4 题目来源:力扣 23. 合并 K 个升序链表。
合并两个链表的扩展
尝试使用递归:
cpp
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//初始化为空
ListNode *head=nullptr;
for(int i=0;i<lists.size();i++){
head=mergeTwoLists(head,lists[i]);
}
return head;
}
ListNode* mergeTwoLists(ListNode *a,ListNode *b){
if(a==nullptr) return b;
if(b==nullptr) return a;
ListNode res;
ListNode *prev = &res;
ListNode *aL=a,*bL=b;
while(aL&&bL){
if(aL->val<bL->val){
prev->next=aL;
aL=aL->next;
}
else{
prev->next=bL;
bL=bL->next;
}
prev = prev->next;
}
prev->next=(aL?aL:bL);
return res.next;
}
};
尝试成功,看了题解......优化的方法暂时看不懂
141. 环形链表 | 力扣 | LeetCode | (easy)
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
输
输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
判断链表是否包含环属于经典问题了,解决方案也是用快慢指针:
每当慢指针 slow 前进一步,快指针 fast 就前进两步。
如果 fast 最终能正常走到链表末尾,说明链表中没有环;如果 fast 走着走着竟然和 slow 相遇了,那肯定是 fast 在链表中转圈了,说明链表中含有环。
cpp
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == NULL || head->next == NULL)
return false;
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == NULL || fast->next == NULL) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
142. 环形链表(||) | 力扣 | LeetCode | (medium)
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
尝试用哈希集合统计已经遍历过的结点,一旦重复遇到,则即刻成环,输出索引
cpp
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *>L;
while(head){
if(L.count(head)){
return head;
}
else{
L.insert(head);
head=head->next;
}
}
return nullptr;
}
};
双(快慢)指针优化后:
cpp
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
ListNode *s = head, *f = head, *res = head;
while (f && f->next) {
s = s->next;
f = f->next->next;
if (s == f) {
while (res != s) {
res = res->next;
s = s->next;
}
return res;
}
}
return nullptr;
}
};
根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c) 有了 a=c+(n−1)(b+c)
的等量关系,我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow
每次向后移动一个位置。最终,它们会在入环点相遇。
876. 单链表的中点 | 力扣 | LeetCode | (easy)
- 给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。

输入:head = [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点,值为 3 。
快慢指针(慢走一步,快走两步),快指针到末尾,慢指针到中间,考虑到两个中间节点的话,偶数个结点。
因为快指针和快指针的下一个节点不为空,所以仍然需要遍历一遍while循环,s指针往后移动到第二个中间节点。
cpp
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode *s=head,*f=head;
while(f&&f->next){
s=s->next;
f=f->next->next;
}
return s;
}
};
160. 相交链表 | 力扣 | LeetCode | (easy)
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0 listA - 第一个链表 listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数 skipB - 在 listB
中(从头节点开始)跳到交叉节点的节点数 评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA
= 2, skipB = 3 输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 在 A 中,相交节点前有 2
个节点;在 B 中,相交节点前有 3 个节点。 --- 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A
中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A
中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
尝试失败,看灵神题解后只能说NB: 
具体算法如下:
初始化两个指针 p=headA, q=headB。
不断循环,直到 p=q。
每次循环,p 和 q 各向后走一步。具体来说,如果 p 不是空节点,那么更新 p 为 p.next,否则更新 p 为 headB;如果 q 不是空节点,那么更新 q 为 q.next,否则更新 q 为 headA。
循环结束时,如果两条链表相交,那么此时 p 和 q 都在相交的起始节点处,返回 p;如果两条链表不相交,那么 p 和 q 都走到空节点,所以也可以返回 p,即空节点。
cpp
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
ListNode *a = headA, *b = headB;
while (a != b) {
a = a ? a->next : headB;
b = b ? b->next : headA;
}
return a;
}
};
19. 删除链表的倒数第N个结点 | 力扣 | LeetCode | (medium)
给你一个链表,删除链表的倒数第 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]
快慢指针解决
想象有一把长度固定的尺子,左端点在链表头部,右端点在正数第 n 个节点。向右移动尺子,当尺子右端点到达链表末尾时,左端点就在倒数第 n
个节点。
cpp
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//虚拟头结点
ListNode dummy(0,head);
ListNode *s=&dummy,*f=&dummy,*del;
while(n--){
//快指针先走n步
f=f->next;
}
while(f->next){
s=s->next;
f=f->next;
}
del=s->next;
s->next=s->next->next;
delete del;
return dummy.next;
}
};