LeetCode刷题笔记(十二): 链表
算法介绍:链表是由节点和指针构成的数据结构,每个节点存有一个值,和一个指向下一个节点的指针,因此很多链表问题可以用递归来处理。不同于数组,链表并不能直接获取任意节点的值,必须要通过指针找到该节点后才能获取其值。同理,在未遍历到链表结尾时,我们也无法知道链表的长度,除非依赖其他数据结构储存长度。
- 代码实现:
1
2
3
4
5struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
}; - 补充:由于在进行链表操作时,尤其是删除节点时,经常会因为对当前节点进行操作而导致内存或指针出现问题。有两个小技巧可以解决这个问题:一是尽量处理当前节点的下一个节点而非当前节点本身,二是建立一个虚拟节点(dummy node),使其指向当前链表的头节点,这样即使原链表所有节点全被删除,也会有一个dummy存在,返回dummy->next即可。
一、链表的基本操作
题目一:反转链表
翻转一个链表。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* res = nullptr;
while (head != nullptr) {
ListNode* next = head->next;
head->next = res;
res = head;
head = next;
}
return res;
}
};提示:就是简单的模拟,注意操作的先后顺序。
题目二:合并两个有序链表
给定两个增序的链表,试将其合并成一个增序的链表。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* res = new ListNode();
ListNode* opList = res;
while (list1 != nullptr || list2 != nullptr) {
ListNode* temp = nullptr;
if (list1 == nullptr || list2 != nullptr && list1->val >= list2->val) {
temp = new ListNode(list2->val);
list2 = list2->next;
} else if (list2 == nullptr || list1 != nullptr && list1->val < list2->val) {
temp = new ListNode(list1->val);
list1 = list1->next;
}
opList->next = temp;
opList = opList->next;
}
return res->next;
}
};提示:注意边界的nullptr情况。
题目三:两两交换链表中的节点
给定一个链表,交换每个相邻的一对节点。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode* prev = nullptr;
ListNode* res = head->next;
while(head != nullptr && head->next != nullptr) {
ListNode* next = head->next;
head->next = next->next;
next->next = head;
if (prev != nullptr) prev->next = next;
prev = head;
head = head->next;
}
return res;
}
};提示:不是很难,但需要注意的是要记录prev用于把next加入链表。
二、其他链表技巧
题目一:相交链表
给定两个链表,判断它们是否相交于一点,并求这个相交节点。
解答:
1
2
3
4
5
6
7
8
9
10
11class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *list1 = headA, *list2 = headB;
while (list1 != list2) {
list1 = list1 != nullptr ? list1->next : headB;
list2 = list2 != nullptr ? list2->next : headA;
}
return list1;
}
};提示:假设链表A的头节点到相交点的距离是a,链表B的头节点到相交点的距离是b,相交点到链表终点的距离为c。我们使用两个指针,分别指向两个链表的头节点,并以相同的速度前进,若到达链表结尾,则移动到另一条链表的头节点继续前进。按照这种前进方法,两个指针会在a+b+c次前进后同时到达相交节点。
题目二:回文链表
以O(1)的空间复杂度,判断链表是否回文。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> stack;
ListNode *fast = head, *slow = head;
while (fast != nullptr) {
stack.push_back(slow->val);
slow = slow->next;
fast = fast->next;
if (fast != nullptr) fast = fast->next;
else stack.pop_back();
}
while (slow != nullptr) {
if (stack.back() == slow->val)
stack.pop_back();
slow = slow->next;
}
return stack.empty();
}
};提示:使用反转链表即可完成O(1)的空间复杂度,这里给出另一种写法。
题目三:删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// if (head->next == nullptr) return nullptr;
ListNode *list1 = head, *list2 = head;
ListNode *prev = nullptr;
while (n--)
list2 = list2->next;
while (list2 != nullptr) {
prev = list1;
list1 = list1->next;
list2 = list2->next;
}
if (prev == nullptr) return list1->next;
prev->next = list1->next;
delete list1;
return head;
}
};提示:本质上也是一种快慢指针,不知道为什么是中等题。
题目四:排序链表
给你链表的头结点head,请将其按升序排列并返回排序后的链表。
解答:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58class Solution {
int getListLength(ListNode *head) {
int length = 0;
while (head) {
length++;
head = head->next;
}
return length;
}
ListNode *splitList(ListNode *head, int size) {
ListNode *cur = head;
for (int i = 0; i < size - 1 && cur; i++)
cur = cur->next;
if (cur == nullptr || cur->next == nullptr)
return nullptr;
ListNode *next_head = cur->next;
cur->next = nullptr;
return next_head;
}
pair<ListNode *, ListNode *> mergeTwoLists(ListNode *list1, ListNode *list2) {
ListNode dummy;
ListNode *cur = &dummy;
while (list1 && list2) {
if (list1->val < list2->val) {
cur->next = list1;
list1 = list1->next;
} else {
cur->next = list2;
list2 = list2->next;
}
cur = cur->next;
}
cur->next = list1 ? list1 : list2;
while (cur->next) cur = cur->next;
return {dummy.next, cur};
}
public:
ListNode *sortList(ListNode *head) {
int length = getListLength(head);
ListNode dummy(0, head);
for (int step = 1; step < length; step *= 2) {
ListNode *new_list_tail = &dummy;
ListNode *cur = dummy.next;
while (cur) {
ListNode *head1 = cur;
ListNode *head2 = splitList(head1, step);
cur = splitList(head2, step);
auto [head, tail] = mergeTwoLists(head1, head2);
new_list_tail->next = head;
new_list_tail = tail;
}
}
return dummy.next;
}
};提示:为什么这道题不是hard?非常难写,这里粘贴的是别人
(还需要掌握)。主要考点是归并排序自底向上的写法,如果用的是自顶向下的写法的话会使用到栈从而让空间复杂度变为O(logn)。











