题目
1.链表的中间结点( LeetCode 876 )
难度: 简单
题目表述:
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
代码(C++):
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
题解: 快慢指针
2.排序链表( LeetCode 148 )
难度: 中等
题目表述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
代码(C++):
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (head == nullptr) return nullptr;
ListNode* p = head;
int length = 0;
while (p != nullptr) {
p = p->next;
length++;
}
ListNode* dummyHead = new ListNode(0, head);
for (int sublength = 1; sublength < length; sublength <<= 1) {
ListNode* prev = dummyHead;
ListNode* curr = dummyHead->next;
while (curr != nullptr) {
ListNode* head1 = curr;
for (int i = 1; i < sublength && curr->next != nullptr; i++) {
curr = curr->next;
}
ListNode* head2 = curr->next;
curr->next = nullptr;
curr = head2;
for (int i = 1; i < sublength && curr !=nullptr && curr->next != nullptr; i++) {
curr = curr->next;
}
ListNode* next = nullptr;
if (curr != nullptr) {
next = curr->next;
curr->next = nullptr;
}
prev->next = merge(head1, head2);
while (prev->next != nullptr) {
prev= prev->next;
}
curr = next;
}
}
return dummyHead->next;
}
ListNode* merge(ListNode* head1, ListNode* head2) {
ListNode* dummyHead = new ListNode();
ListNode* tmp =dummyHead;
ListNode* tmp1 = head1;
ListNode* tmp2 = head2;
while (tmp1 != nullptr && tmp2 != nullptr) {
if (tmp1->val < tmp2->val) {
tmp->next = tmp1;
tmp1 = tmp1->next;
} else {
tmp->next = tmp2;
tmp2 = tmp2->next;
}
tmp = tmp->next;
}
if (tmp1 != nullptr) {
tmp->next = tmp1;
} else if (tmp2 != nullptr) {
tmp->next = tmp2;
}
return dummyHead->next;
}
};
题解:
时间复杂度是O(nlogn) 的排序算法包括 归并排序、堆排序 和 快速排序(快速排序的最差时间复杂度是 O(n^2)),其中最适合链表的排序算法是归并排序。
使用自底向上的方法实现归并排序,则可以达到 O(1) 的空间复杂度。
⭐3.重排链表( LeetCode 143 )
难度: 中等
题目表述:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
代码(C++):
class Solution {
public:
void reorderList(ListNode* head) {
if (head == nullptr) return;
ListNode* mid = middleNode(head);
ListNode* head2 = reverseList(mid->next);
mid->next = nullptr;
mergeList(head, head2);
}
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
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;
}
void mergeList(ListNode* head1, ListNode* head2) {
ListNode* p1 = head1;
ListNode* p2 = head2;
while (p1 != nullptr && p2 != nullptr) {
ListNode* tmp_p1 = p1->next;
ListNode* tmp_p2 = p2->next;
p1->next = p2;
p1 = tmp_p1;
p2->next = p1;
p2 = tmp_p2;
}
}
};
题解:
寻找链表中点 + 链表逆序 + 合并链表
寻找链表中点:若是偶数个节点,则
fast->next != nullptr && fast->next->next != nullptr找的是前半段的尾
fast != nullptr && fast->next != nullptr找的是后半段的首
4.旋转链表( LeetCode 61 )
难度: 中等
题目表述:
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
代码(C++):
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr || k == 0) return head;
int length = 1;
ListNode* tail = head;
while (tail->next != nullptr) {
tail = tail->next;
length++;
}
int add = length - (k % length);
if (add == length) return head;
tail->next = head;
while (add--) {
tail = tail->next;
}
head = tail->next;
tail->next = nullptr;
return head;
}
};
题解:
闭合为环
5.两两交换链表中的节点( LeetCode 24 )
难度: 中等
题目表述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
代码(C++):
class Solution {
public:
// 迭代
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0, head);
ListNode* pre = dummyHead;
while (pre->next && pre->next->next) {
ListNode* node1 = pre->next;
ListNode* node2 = pre->next->next;
node1->next = node2->next;
node2->next = node1;
pre->next = node2;
pre = node1;
}
return dummyHead->next;
}
// 递归
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* newHead = head->next;
head->next = swapPairs(newHead->next);
newHead->next = head;
return newHead;
}
};
题解: 迭代 / 递归
⭐6.合并K个升序链表( LeetCode 23 )
难度: 困难
题目表述:
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
代码(C++):
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.size() == 0) return nullptr;
return merge(lists, 0, lists.size() - 1);
}
ListNode* merge(vector<ListNode*>& lists, int l, int r) {
if (l == r) {
return lists[l];
}
int mid = (l + r) >> 1;
return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
}
ListNode* mergeTwoLists(ListNode* head1, ListNode* head2) {
ListNode* dummyHead = new ListNode();
ListNode *p = dummyHead, *p1 = head1, *p2 = head2;
while (p1 != nullptr && p2 != nullptr ) {
if (p1->val < p2->val) {
p->next = p1;
p1 = p1->next;
} else {
p->next = p2;
p2 = p2->next;
}
p = p->next;
}
p->next = p1 ? p1 : p2;
return dummyHead->next;
}
};
题解: 分支合并
7.删除排序链表中的重复元素 II( LeetCode 82 )
难度: 中等
题目表述:
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
代码(C++):
class Solution {
public:
// 迭代
ListNode* deleteDuplicates(ListNode* head) {
if (!head) return head;
ListNode* node = head;
ListNode* dummyHead = new ListNode(0, head);
ListNode* pre = dummyHead;
while (pre->next && pre->next->next) {
if (pre->next->val == pre->next->next->val) {
int x = pre->next->val;
while (pre->next && pre->next->val == x) {
pre->next = pre->next->next;
}
} else {
pre = pre->next;
}
}
return dummyHead->next;
}
// 递归
ListNode* deleteDuplicates(ListNode* head) {
if (!head || !head->next) return head;
if (head->val == head->next->val) {
int x = head->val;
while (head && head->val == x) {
head = head->next;
}
head = deleteDuplicates(head);
} else {
head->next = deleteDuplicates(head->next);
}
return head;
}
};
题解: 头节点不一定会保留
链表和树的问题,一般都可以有递归和迭代两种写法。
8.删除排序链表中的重复元素( LeetCode 83 )
难度: 简单
题目表述:
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
代码(C++):
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* cur = head;
while (cur->next) {
if (cur->val == cur->next->val) {
cur->next = cur->next->next;
}
else {
cur = cur->next;
}
}
return head;
}
};
题解: 头节点一定会保留
⭐9.环形链表 II ( LeetCode 142 )
难度: 中等
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
代码(C++):
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if(slow == fast) {
ListNode *p = head;
while (p != slow){
slow = slow->next;
p = p->next;
}
return p;
}
}
return NULL;
}
};
题解: 快慢指针
fast += 2, slow += 1,fast 和 slow 相差一步最合理
,fast 跑的越慢,越能在环中少跑几圈等到还在外面直线跑的 slow
外循环结束条件:fast != NULL && fast->next != NULL
慢指针第一圈走不完一定会和快指针相遇
。设链表中环外部分的长度为 a,slow 指针进入环后,又走了 b 的距离与 fast 相遇,此时,fast 指针已经走完了环的 n 圈。任意时刻,fast 指针走过的距离都为slow 指针的 2 倍,故可得
a + n(b + c) + b = 2(a + b) ⟹ a = c + (n - 1)(b + c)
10.回文链表( LeetCode 234 )
难度: 简单
题目描述:
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表,如 [1,2,2,1]
代码(C++):
/** 快慢指针
1.找到前半部分链表的尾节点。
2.反转后半部分链表。
3.判断是否回文。
4.恢复链表。
5.返回结果。
**/
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode* lEnd = getMiddleNode(head);
ListNode* reverseRHead = reverseList(lEnd->next);
ListNode* p1 = head, *p2 = reverseRHead;
bool res = true;
while (res && p2 != nullptr) {
if (p1->val != p2->val) {
res = false;
}
p1 = p1->next;
p2 = p2->next;
}
lEnd->next= reverseList(reverseRHead);
return res;
}
ListNode* getMiddleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* reverseList(ListNode* head) {
ListNode *pre = nullptr, *cur = head;
while (cur) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
// 利用递归的特性:从后往前
class Solution {
ListNode* firstNode;
public:
bool isPalindrome(ListNode* head) {
firstNode = head;
return recursivelyCheck(head);
}
bool recursivelyCheck(ListNode* currentNode) {
if (currentNode != nullptr) {
if (!recursivelyCheck(currentNode->next)) {
return false;
}
if (currentNode->val != firstNode->val) {
return false;
}
firstNode = firstNode->next;
}
return true;
}
};
题解: 快慢指针 / 递归
11.相交链表( LeetCode 160 )
难度: 简单
代码(C++):
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL)
return NULL;
ListNode *pA = headA;
ListNode *pB = headB;
while (pA != pB){
if (pA == NULL)
pA = headB;
else
pA = pA->next;
if (pB == NULL)
pB = headA;
else
pB = pB->next;
}
return pA;
}
};
题解: 双指针交叉循环遍历
无论 A、B 两个链表是否有相交点,最终都会指向一个相同的节点,要么是它们的公共尾部,要么是 NULL。
12.奇偶链表( LeetCode 328 )
难度: 中等
代码(C++):
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (!head) return head;
ListNode* oddP = head;
ListNode* evenHead = head->next;
ListNode* evenP = evenHead;
while (evenP && evenP->next) {
oddP->next = evenP->next;
oddP = oddP->next;
evenP->next = oddP->next;
evenP = evenP->next;
}
oddP->next = evenHead;
return head;
}
};
题解: 分离后合并
13.K 个一组翻转链表( LeetCode 25 )
难度: 困难
题目描述:
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
代码(C++):
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* dummyHead = new ListNode(0, head);
ListNode* pre = dummyHead;
while (pre->next) {
ListNode* h = pre->next;
ListNode* t = h;
int i = 1;
while (i++ < k && t) {
t = t->next;
}
if (t) {
ListNode* next = t->next;
t->next = nullptr;
pre->next = reverseList(h);
h->next = next;
pre = h;
} else {
break;
}
}
return dummyHead->next;
}
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
while (cur) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
题解: 反转链表
14.反转链表 II ( LeetCode 92 )
难度: 中等
题目表述:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表 。
代码(C++):
class Solution {
public:
// 递归 O(n) O(n)
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) {
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return ret;
}
// 迭代 O(n) O(1)
void reverseList(ListNode* head) {
ListNode *pre = nullptr;
ListNode *cur = head;
while (cur != nullptr) {
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
head = pre;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummyNode = new ListNode();
dummyNode->next = head;
ListNode *pre = dummyNode;
for (int i = 1; i < left; i++) {
pre = pre->next;
}
ListNode *leftNode = pre->next;
ListNode *rightNode = pre;
for (int i = 0; i < right - left + 1; i++) {
rightNode = rightNode->next;
}
ListNode *tail = rightNode->next;
pre->next = nullptr;
rightNode->next = nullptr;
reverseList(leftNode);
leftNode->next = tail;
pre->next = rightNode;
return dummyNode->next;
}
};
题解:
因为头节点有可能发生变化,使用虚拟头节点dummyNode可以避免复杂的分类讨论
⭐15.复制带随机指针的链表( LeetCode 138 )
难度: 中等
题目表述:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
代码(C++):
class Solution {
private:
unordered_map<Node*, Node*> map;
public:
Node* copyRandomList(Node* head) {
if (head == NULL) {
return head;
}
if (map.count(head) == 0) {
Node* newNode = new Node(head->val);
map[head] = newNode;
newNode->next = copyRandomList(head->next);
newNode->random = copyRandomList(head->random);
}
return map[head];
}
};
题解: 回溯 + 哈希表 / 迭代 + 节点拆分
利用回溯的方式,让每个节点的拷贝操作相互独立
16.删除链表的倒数第 N 个结点( LeetCode 19 )
难度: 中等
题目表述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
代码(C++):
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyNode = new ListNode(0, head);
ListNode* fast = head, *slow = dummyNode;
for (int i = 0; i < n; i++) {
fast = fast->next;
}
while (fast) {
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete tmp;
return dummyNode->next;
}
};
题解: 快慢指针
小结
迭代、递归、哈希表、双指针、快慢指针