链表
基本技能
- NULL异常处理
- dummy node哑巴节点
- 快慢指针
- 插入一个节点到排序链表
- 翻转链表
- 合并两个链表
- 找到链表的中间节点
常见题型
83. 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* current = head;
while (current)
{
while (current->next && current->next->val == current->val)
{
ListNode* rmNode = current->next;
current->next = current->next->next;
delete rmNode;
}
current = current->next;
}
return head;
}
};
82. 删除排序链表中的重复元素 II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (head == NULL)
return head;
// 链表头也可能删除,所以用dummy node进行辅助
ListNode dummy(0);
dummy.next = head;
head = &dummy;
while (head->next && head->next->next)
{
if (head->next->val == head->next->next->val)
{
int rmVal = head->next->val;
while (head->next && head->next->val == rmVal)
{
ListNode* rmNode = head->next;
head->next = head->next->next;
delete rmNode;
}
}
else
head = head->next;
}
return dummy.next;
}
};
206. 反转链表
翻转一个单链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* reverse = NULL;
ListNode* next;
while (head)
{
next = head->next;
head->next = reverse;
reverse = head;
head = next;
}
return reverse;
}
};
92. 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明: 1 ≤ m ≤ n ≤ 链表长度。
示例: 输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL
class Solution {
public:
/**
* 思路:先遍历到m处,翻转,再拼接后续,注意指针处理
* 输入: 1->2->3->4->5->NULL, m = 2, n = 4
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (head == NULL || head->next == NULL)
return head;
// 头部可能变化所以使用dummy node
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
// 最开始:1->2->3->4->5->NULL
for (int i = 1; i < m; ++i)
{
prev = head;
head = head->next;
}
// 遍历之后: 1(pre)->2(head)->3->4->5->NULL
ListNode* reverse = NULL;
ListNode* mid = head;
for (int i = m; i <= n; ++i)
{
// 第一次循环: 1 NULL<-2 3(head)->4->5->NULL
ListNode* next = head->next;
head->next = reverse;
reverse = head;
head = next;
}
/**
* 循环需要执行四次
* 循环结束:1 NULL<-2<-3<-4 5(head)->NULL
*/
prev->next = reverse;
mid->next = head;
return dummy.next;
}
};
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy(0);
ListNode* p = &dummy;
while (l1 && l2)
{
if (l1->val < l2->val)
{
p->next = l1;
l1 = l1->next;
}
else
{
p->next = l2;
l2 = l2->next;
}
p = p->next;
}
p->next = l1 ? l1 : l2;
return dummy.next;
}
};
86. 分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例: 输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode minDummy(0);
ListNode maxDummy(0);
ListNode* min = &minDummy;
ListNode* max = &maxDummy;
while (head)
{
if (head->val < x)
{
min->next = head;
min = min->next;
}
else
{
max->next = head;
max = max->next;
}
head = head->next;
}
max->next = NULL;
min->next = maxDummy.next;
return minDummy.next;
}
};
148. 排序链表
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序 思路:归并排序,找中点和合并操作
class Solution {
public:
ListNode* sortList(ListNode* head)
{
// 如果只有一个节点 直接就返回这个节点
if (head == NULL || head->next == NULL)
return head;
ListNode* middle = findMiddle(head);
ListNode* tail = middle->next;
middle->next = NULL;
head = sortList(head);
tail = sortList(tail);
return mergeTwoLists(head, tail);
}
ListNode* findMiddle(ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head->next;
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy(0);
ListNode* p = &dummy;
while (l1 && l2)
{
if (l1->val < l2->val)
{
p->next = l1;
l1 = l1->next;
}
else
{
p->next = l2;
l2 = l2->next;
}
p = p->next;
}
p->next = l1 ? l1 : l2;
return dummy.next;
}
};
143. 重排链表
给定一个单链表 L:L0→L1→…→Ln-1→Ln , 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1: 给定链表 1->2->3->4, 重新排列为 1->4->2->3.
class Solution {
public:
void reorderList(ListNode* head) {
if (head == NULL ||head->next == NULL)
return;
/* 找中间点,分开两条链表,第二条翻转,然后接替取两条链表 */
ListNode* middle = findMiddle(head);
ListNode* tail = middle->next;
middle->next = NULL;
tail = reverse(tail);
ListNode dummy;
ListNode* p = &dummy;
bool isFirst = true;
while (head && tail)
{
if (isFirst)
{
p->next = head;
head = head->next;
}
else
{
p->next = tail;
tail = tail->next;
}
p = p->next;
isFirst = !isFirst;
}
p->next = head ? head : tail;
head = dummy.next;
}
ListNode* reverse(ListNode* head)
{
ListNode* reverse = NULL;
ListNode* next;
while (head)
{
next = head->next;
head->next = reverse;
reverse = head;
head = next;
}
return reverse;
}
ListNode* findMiddle(ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head->next;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
141. 环形链表
给定一个链表,判断链表中是否有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
};
142. 环形链表 II
证明:让两个指针其中一个从链表头 head 出发,一次走一步,让另一个指针从相遇点出发,也一次走一步,相遇点就是环的入口。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
break;
}
// 无环
if (fast == NULL ||fast->next == NULL)
return NULL;
slow = head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
234. 回文链表
请判断一个链表是否为回文链表
class Solution {
public:
bool isPalindrome(ListNode* head) {
// 1 2 NULL
// 1 2 1 NULL
// 1 2 2 1 NULL
if (head == NULL)
return true;
// fast如果初始化为head.Next则中点在slow.Next
// fast初始化为head,则中点在slow
ListNode* slow = head;
ListNode* fast = head->next;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
// 翻转
ListNode* tail = reverse(slow->next);
slow->next = NULL; // 断开两个链表
while (head && tail)
{
if (head->val != tail->val)
return false;
tail = tail->next;
head = head->next;
}
return true;
}
ListNode* reverse(ListNode* head)
{
ListNode* reverse = NULL;
ListNode* next;
while (head)
{
next = head->next;
head->next = reverse;
reverse = head;
head = next;
}
return reverse;
}
};
138. 复制带随机指针的链表
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝。
我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。 random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。 思路:1、hash 表存储指针,2、复制节点跟在原节点后面
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == NULL)
return head;
// 复制节点,紧挨到到后面
// 1->2->3 ==> 1->1->2->2->3->3
Node* p = head;
while (p)
{
Node* node = new Node(p->val);
node->next = p->next;
p->next = node;
p = node->next;
}
// 处理random指针
p = head;
while (p)
{
if (p->random != NULL)
p->next->random = p->random->next;
p = p->next->next;
}
// 分离两个链表
Node dummy(0);
Node* newList = &dummy;
p = head;
while (p)
{
newList->next = p->next;
p->next = p->next->next;
newList = newList->next;
p = p->next;
}
// 原始链表头:head 1->2->3
// 克隆的链表头:newList 1->2->3
return dummy.next;
}
};
总结
链表必须要掌握的一些点,通过上面的练习题,基本可以应付链表类的各种题目。