LeetCode系列之链表专题
1. 链表题目概述
https://leetcode-cn.com/tag/linked-list/
链表有多重类型:单链表、双链表、循环链表等。使用链表,需要注意头结点的用法。
2. 典型题目
2.1 删除链表中的节点
https://leetcode-cn.com/problems/delete-node-in-a-linked-list/
题目比较无趣,各种条件限制之后,取巧的办法几乎成了唯一。
值得学习的是,对于不能回溯到前一个节点的场景,和next节点的值进行交换,然后删除next,是个不错的方法。
2.2 环形链表
https://leetcode-cn.com/problems/linked-list-cycle/
快慢指针法
bool hasCycle(ListNode *head) { ListNode* tortoise = head; ListNode* bunny = head; while (tortoise && bunny && bunny->next) { tortoise = tortoise->next; bunny = bunny->next->next; if (tortoise == bunny) { return true; } } return false; }
时间复杂度O(N),空间复杂度O(1)。
https://leetcode-cn.com/problems/linked-list-cycle-ii/
ListNode *detectCycle(ListNode *head) { ListNode* tortoise = head; ListNode* bunny = head; bool hasCycle = false; // 找到相遇点 while (tortoise && bunny && bunny->next) { tortoise = tortoise->next; bunny = bunny->next->next; if (tortoise == bunny) { hasCycle = true; break; } } if (!hasCycle) return nullptr; // 从相遇点和头结点同时出发,相遇点即是环的入口点 while (head && tortoise) { if (head == tortoise) { return tortoise; } else { head = head->next; tortoise = tortoise->next; } } return nullptr; }
第二阶段的证明过程
时间复杂度O(N),空间复杂度O(1)。
2.3 排序链表
https://leetcode-cn.com/problems/sort-list/
ListNode* sortList(ListNode* head) { if (head == nullptr || head->next == nullptr) return head; // 快慢指针分割链表 ListNode* fast = head->next; ListNode* slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } ListNode* h1 = head; ListNode* h2 = slow->next; slow->next = nullptr; // 分别排序 h1 = sortList(h1); h2 = sortList(h2); // 合并排序结果 return mergeLists(h1, h2); } ListNode* mergeLists(ListNode* l1, ListNode* l2) { ListNode dummy = ListNode(0); ListNode* node = &dummy; while (l1 && l2) { if (l1->val < l2->val) { node->next = l1; l1 = l1->next; } else { node->next = l2; l2 = l2->next; } node = node->next; } node->next = (l1 == nullptr ? l2 : l1); return dummy.next; }
时间复杂度:O(N x logN),空间复杂度O(1)。
2.4 两数相加
https://leetcode-cn.com/problems/add-two-numbers/
2.5 回文链表
https://leetcode-cn.com/problems/palindrome-linked-list/
先放一个错误答案在这儿
bool isPalindrome(ListNode* head) { vector<int> ivec; ListNode* node = head; while (node) { ivec.push_back(node->val); node = node->next; } bool isPalindrome = true; int start = 0, end = ivec.size() - 1; while (start <= end) { if (ivec[start] != ivec[end]) { isPalindrome = false; break; } start++; end--; } return isPalindrome; }
时间复杂度O(N),空间复杂度O(N)。
2.6 删除排序链表中的重复元素
https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
ListNode* deleteDuplicates(ListNode* head) { if (head == nullptr) return nullptr; int latestVal = head->val; ListNode* node = head->next; ListNode* prev = head; while (node) { if (node->val == latestVal) { node = deleteCurrentNode(node); prev->next = node; } else { latestVal = node->val; prev = node; node = node->next; } } return head; } ListNode* deleteCurrentNode(ListNode* node) { ListNode* next = node->next; delete node; return next; }
时间复杂度O(N),空间复杂度O(1)。
2.7 合并两个有序链表
https://leetcode-cn.com/problems/merge-two-sorted-lists/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode dummy = ListNode(); ListNode* current = &dummy; while (l1 != nullptr && l2 != nullptr) { if (l1->val < l2->val) { current->next = l1; l1 = l1->next; } else { current->next = l2; l2 = l2->next; } current = current->next; } current->next = (l1 == nullptr ? l2 : l1); return dummy.next; }
时间复杂度O(N),空间复杂度O(1)。
2.8 相交链表
https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
注意写的时候,判断 pA == pB,而不是 pA->val == pB->val。看官方题解里,skipA和skipB与此相关。
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode* pA = headA; ListNode* pB = headB; bool isFirstA = true, isFirstB = true; while (pA && pB) { if (pA == pB) { return pA; } pA = pA->next; pB = pB->next; if (pA == nullptr && isFirstA) { pA = headB; isFirstA = false; } if (pB == nullptr && isFirstB) { pB = headA; isFirstB = false; } } return nullptr; }
时间复杂度O(m+n),空间复杂度O(1)。
2.9 二进制链表转整数
https://leetcode-cn.com/problems/convert-binary-number-in-a-linked-list-to-integer/
int getDecimalValue(ListNode* head) { ListNode* node = head; int res = 0; while (node) { res = (res << 1) + node->val; node = node->next; } return res; }
时间复杂度O(N),空间复杂度O(1)。
2.10 反转链表(常考题目)
https://leetcode-cn.com/problems/reverse-linked-list/
迭代版本
ListNode* reverseList(ListNode* head) { ListNode* prev = nullptr; ListNode* current = head; while (current != nullptr) { ListNode* next = current->next; current->next = prev; prev = current; current = next; } return prev; }
时间复杂度O(N),空间复杂度O(1)。
递归版本
ListNode* reverseList(ListNode* head) { if (head == nullptr || head->next == nullptr) return head; ListNode* p = reverseList(head->next); head->next->next = head; head->next = nullptr; return p; }
时间复杂度O(N),空间复杂度O(N)。
https://leetcode-cn.com/problems/reverse-linked-list-ii/
题目有额外要求,“一遍扫描”解决问题。看了自己很久以前的一次提交,虽然AC了,但是不符合这个要求。
// TODO:不太常见的题目,稍后回来补充完整
面试实战中遇到了一个变形题目:链表元素三个一组,按组打包之后进行反转;不满三个的元素,单独打包成一组。如[1, 2, 3, 4, 5, 6, 7] => [7, 4, 5 ,6, 1, 2, 3]。
ListNode* reverse3Elements(ListNode* head) { if (head && head->next && head->next->next) { std::swap(head->val, head->next->next->val); } else if (head && head->next) { // 只有两个元素的时候,交换这两个元素 std::swap(head->val, head->next->val); } } ListNode* reverseListEvery3Elements(ListNode* head) { ListNode* current = head; while (current) { reverse3Elements(current); if (current && current->next && current->next->next) { current = current->next->next->next; } else { break; } } return reverseList(head); }
这里容易失误的地方是,[1, 2, 3, 4, 5, 6, 7, 8] => [7, 8, 4, 5 ,6, 1, 2, 3],所以说,在只有两个元素的时候,也要反转。
3. 总结
做算法,背单词
tortoise 乌龟
hare 野兔
bunny 可爱的小兔子
rabbit 泛指兔子