面试经常考察链表题型!
✅ 高频链表面试题 相关例题:
1 2 3 输入:head = [3 ,2 ,0 ,-4 ], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
1️⃣ 快慢指针 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : bool hasCycle (ListNode* head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) return true ; } return false ; } };
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始 )。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递 ,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
1 2 3 输入:head = [3 ,2 ,0 ,-4 ], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
1️⃣ 快慢指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : ListNode* detectCycle (ListNode* head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) { ListNode* node = head; while (node != slow) { node = node->next; slow = slow->next; } return slow; } } return nullptr ; } };
1 2 3 4 5 6 7 8 9 10 11 class Solution {public : ListNode* getIntersectionNode (ListNode* headA, ListNode* headB) { ListNode *p = headA, *q = headB; while (p != q) { p = p ? p->next : headB; q = q ? q->next : headA; } return p; } };
1️⃣ 迭代|三指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* pre = nullptr ; ListNode* cur = head; while (cur) { ListNode* nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } };
2️⃣ 递归 1 2 3 4 5 6 7 8 9 10 11 class Solution {public : ListNode* reverseList (ListNode* head) { if (!head || !head->next) return head; ListNode* new_head = reverseList (head->next); head->next->next = head; head->next = nullptr ; return new_head; } };
反转链表进阶:反转部分区间,找到区间 leftNode 与 rightNode,以及 leftNode 左节点 pre 与 rightNode 右节点 nxt,独立区间(断开连接)后反转再接回。
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 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* pre = nullptr ; ListNode* cur = head; while (cur) { ListNode* nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } ListNode* reverseBetween (ListNode* head, int left, int right) { ListNode dummy (0 , head) ; ListNode* pre = &dummy; int t = left; while (--t) { pre = pre->next; } ListNode* leftNode = pre->next; ListNode* rightNode = leftNode; t = right - left; while (t--) { rightNode = rightNode->next; } ListNode* nxt = rightNode->next; pre->next = nullptr ; rightNode->next = nullptr ; ListNode* node = reverseList (leftNode); pre->next = node; leftNode->next = nxt; return dummy.next; } };
1 2 3 输入:head = [1 ,2 ,3 ,4 ,5 ] 输出:[3 ,4 ,5 ] 解释:链表只有一个中间结点,值为 3 。
1 2 3 输入:head = [1 ,2 ,3 ,4 ,5 ,6 ] 输出:[4 ,5 ,6 ] 解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
1️⃣ 快慢指针 1 2 3 4 5 6 7 8 9 10 11 class Solution {public : ListNode* middleNode (ListNode* head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } return slow; } };
1️⃣ 回文链表判断 = 寻找中间节点 + 反转链表 前置题目:
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 class Solution {public : ListNode* middleNode (ListNode* head) { ListNode *slow = head, *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } return slow; } ListNode* reverseList (ListNode* head) { ListNode *pre = nullptr , *cur = head; while (cur) { ListNode* nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } bool isPalindrome (ListNode* head) { ListNode* mid = middleNode (head); ListNode* head2 = reverseList (mid); while (head2) { if (head->val != head2->val) { return false ; } head = head->next; head2 = head2->next; } return true ; } };
1 2 输入:l1 = [1 ,2 ,4 ], l2 = [1 ,3 ,4 ] 输出:[1 ,1 ,2 ,3 ,4 ,4 ]
1️⃣ 迭代(常用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : 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; return dummy.next; } };
2️⃣ 递归 1 2 3 4 5 6 7 8 9 10 11 12 13 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; } list2->next = mergeTwoLists (list1, list2->next); return list2; } };
1️⃣ 迭代 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { ListNode dummy; ListNode* cur = &dummy; int carry = 0 ; while (l1 || l2 || carry) { if (l1) { carry += l1->val; l1 = l1->next; } if (l2) { carry += l2->val; l2 = l2->next; } cur = cur->next = new ListNode (carry % 10 ); carry /= 10 ; } return dummy.next; } };
2️⃣ 递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2, int carry = 0 ) { if (l1 == nullptr && l2 == nullptr ) { return carry ? new ListNode (carry) : nullptr ; } if (l1 == nullptr ) { swap (l1, l2); } int sum = carry + l1->val + (l2 ? l2->val : 0 ); l1->val = sum % 10 ; l1->next = addTwoNumbers (l1->next, (l2 ? l2->next : nullptr ), sum / 10 ); return l1; } };
1️⃣ 迭代|反转链表 + 两数相加 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 class Solution { ListNode* reverseList (ListNode* head) { ListNode* pre = nullptr , *cur = head; while (cur) { ListNode* nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } ListNode* addTwo (ListNode* l1, ListNode* l2) { ListNode dummy; auto cur = &dummy; int carry = 0 ; while (l1 || l2 || carry) { if (l1) carry += l1->val; if (l2) carry += l2->val; cur = cur->next = new ListNode (carry % 10 ); carry /= 10 ; if (l1) l1 = l1->next; if (l2) l2 = l2->next; } return dummy.next; } public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { l1 = reverseList (l1); l2 = reverseList (l2); auto l3 = addTwo (l1, l2); return reverseList (l3); } };
2️⃣ 递归|反转链表 + 两数相加 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 class Solution { ListNode* reverseList (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return head; } auto new_head = reverseList (head->next); head->next->next = head; head->next = nullptr ; return new_head; } ListNode* addTwo (ListNode* l1, ListNode* l2, int carry = 0 ) { if (l1 == nullptr && l2 == nullptr ) { return carry ? new ListNode (carry) : nullptr ; } if (l1 == nullptr ) { swap (l1, l2); } carry += l1->val + (l2 ? l2->val : 0 ); l1->val = carry % 10 ; l1->next = addTwo (l1->next, (l2 ? l2->next : nullptr ), carry / 10 ); return l1; } public : ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { l1 = reverseList (l1); l2 = reverseList (l2); auto l3 = addTwo (l1, l2); return reverseList (l3); } };
1️⃣ 快慢指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode dummy (0 , head) ; ListNode *fast = &dummy, *slow = &dummy; while (n--) { fast = fast->next; } while (fast->next) { slow = slow->next; fast = fast->next; } ListNode* d = slow->next; slow->next = d->next; delete d; return dummy.next; } };
1️⃣ 迭代|四指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : ListNode* swapPairs (ListNode* head) { ListNode dummy (0 , head) ; ListNode* node0 = &dummy; ListNode* node1 = head; while (node1 && node1->next) { ListNode* node2 = node1->next; ListNode* node3 = node2->next; node0->next = node2; node2->next = node1; node1->next = node3; node0 = node1; node1 = node3; } return dummy.next; } };
2️⃣ 递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : ListNode* swapPairs (ListNode* head) { if (head == nullptr || head->next == nullptr ) { return head; } ListNode* node1 = head; ListNode* node2 = head->next; ListNode* node3 = node2->next; node1->next = swapPairs (node3); node2->next = node1; return node2; } };
1️⃣ 从「反转链表 · 迭代版」到「K 个一组翻转链表」 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 class Solution {public : ListNode* reverseKGroup (ListNode* head, int k) { int n = 0 ; for (ListNode* cur = head; cur; cur = cur->next) { n++; } ListNode dummy (0 , head) ; ListNode* p0 = &dummy; ListNode* prev = nullptr ; ListNode* cur = head; for (; n >= k; n -= k) { for (int i = 0 ; i < k; i++) { ListNode* nxt = cur->next; cur->next = prev; prev = cur; cur = nxt; } ListNode* last = p0->next; p0->next = prev; last->next = cur; p0 = last; } return dummy.next; } };
2️⃣「206. 反转链表」+「92. 反转链表 II」 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 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* pre = nullptr ; ListNode* cur = head; while (cur) { ListNode* nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } ListNode* reverseBetween (ListNode* head, int left, int right) { ListNode dummy (0 , head) ; ListNode* pre = &dummy; for (int i = 0 ; i < left - 1 ; i++) { pre = pre->next; } ListNode* leftNode = pre->next; ListNode* rightNode = leftNode; for (int i = left; i < right; i++) { rightNode = rightNode->next; } ListNode* nxt = rightNode->next; rightNode->next = nullptr ; reverseList (leftNode); pre->next = rightNode; leftNode->next = nxt; return dummy.next; } ListNode* reverseKGroup (ListNode* head, int k) { int n = 0 ; ListNode* cur = head; while (cur) { n++; cur = cur->next; } if (n < k) { return head; } ListNode* new_head = head; int times = n / k; for (int i = 0 ; times--; i += k) { ListNode* node = reverseBetween (new_head, i + 1 , i + k); if (i == 0 ) { new_head = node; } } return new_head; } };
1 2 3 4 5 6 7 8 9 10 输入: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
✅ 快手一面 ,面试官来一题简单题😊,最后还问了时间复杂度:假设 k 个链表,共 n 个节点,那时间复杂度为 $O(k·logk+n·logk)$ 即 $O(n·logk)$。
1️⃣ 最小堆 时间复杂度分析:假设 $k$ 个链表, 共 $n$ 个节点, 最小堆单次操作 $O(log k)$, 初始化堆需要 $O(k·logk)$, 那时间复杂度为 $O(k·logk + n·logk)$,即 $O(n·logk)$。
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 class Solution {public : ListNode* mergeKLists (vector<ListNode*>& lists) { auto cmp = [](const ListNode* a, const ListNode* b) { return a->val > b->val; }; priority_queue<ListNode*, vector<ListNode*>, decltype (cmp)> pq; for (auto & head : lists) { if (head) { pq.push (head); } } ListNode dummy{}; ListNode* head = &dummy; while (!pq.empty ()) { ListNode* node = pq.top (); pq.pop (); head->next = node; head = head->next; if (node->next) pq.push (node->next); } return dummy.next; } };
2️⃣ 分治法 前置题目:21. 合并两个有序链表
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 class Solution {public : 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; return dummy.next; } ListNode* mergeKLists (vector<ListNode*>& lists, int l, int r) { if (l == r) return lists[l]; if (l > r) return nullptr ; int m = (l + r) >> 1 ; auto left = mergeKLists (lists, l, m); auto right = mergeKLists (lists, m + 1 , r); return mergeTwoLists (left, right); } ListNode* mergeKLists (vector<ListNode*>& lists) { return mergeKLists (lists, 0 , lists.size () - 1 ); } };
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 58 59 60 61 62 63 64 struct Node { int key; int value; Node* prev; Node* next; Node (int k = 0 , int v = 0 ) : key (k), value (v) {} }; class LRUCache {private : Node* dummy; int capacity; unordered_map<int , Node*> key_to_node; void remove (Node* node) { node->prev->next = node->next; node->next->prev = node->prev; } void push_front (Node* node) { node->next = dummy->next; node->prev = dummy; dummy->next->prev = node; dummy->next = node; } Node* get_node (int key) { if (!key_to_node.count (key)) { return nullptr ; } Node* node = key_to_node[key]; remove (node); push_front (node); return node; } public : LRUCache (int capacity) : capacity (capacity), dummy (new Node ()) { dummy->next = dummy; dummy->prev = dummy; } int get (int key) { Node* node = get_node (key); return node == nullptr ? -1 : node->value; } void put (int key, int value) { Node* node = get_node (key); if (node != nullptr ) { node->value = value; return ; } node = new Node (key, value); if (key_to_node.size () == capacity) { Node* last = dummy->prev; remove (last); key_to_node.erase (last->key); delete last; } key_to_node[key] = node; push_front (node); } };
1 2 输入:head = [[7 ,null],[13 ,0 ],[11 ,4 ],[10 ,2 ],[1 ,0 ]] 输出:[[7 ,null],[13 ,0 ],[11 ,4 ],[10 ,2 ],[1 ,0 ]]
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 class Solution {public : unordered_map<Node*, Node*> cacheNode; Node* copyRandomList (Node* head) { if (head == nullptr ) return nullptr ; if (!cacheNode.count (head)) { Node* newHead = new Node (head->val); cacheNode[head] = newHead; newHead->next = copyRandomList (head->next); newHead->random = copyRandomList (head->random); } return cacheNode[head]; } };
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : ListNode* deleteDuplicates (ListNode* head) { ListNode dummy (0 , head) ; auto cur = &dummy; while (cur->next && cur->next->next) { int val = cur->next->val; if (cur->next->next->val == val) { while (cur->next && cur->next->val == val) { cur->next = cur->next->next; } } else { cur = cur->next; } } return dummy.next; } };
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
1 2 输入:head = [1 ,2 ,3 ,4 ,5 ], k = 2 输出:[4 ,5 ,1 ,2 ,3 ]
思路:我们可以先将给定的链表连接成环,然后将指定位置断开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : ListNode* rotateRight (ListNode* head, int k) { if (k == 0 || !head || !head->next) { return head; } int n = 1 ; ListNode* iter = head; while (iter->next) { iter = iter->next; n++; } int t = n - k % n; if (t == n) return head; iter->next = head; while (t--) { iter = iter->next; } ListNode* ret = iter->next; iter->next = nullptr ; return ret; } };
给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
1 2 输入:head = [1 ,4 ,3 ,2 ,5 ,2 ], x = 3 输出:[1 ,2 ,2 ,4 ,3 ,5 ]
1️⃣ 模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : ListNode* partition (ListNode* head, int x) { ListNode largeDummy (0 ) ; ListNode* large = &largeDummy; ListNode smallDummy (0 ) ; ListNode* small = &smallDummy; while (head) { if (head->val < x) { small->next = head; small = small->next; } else { large->next = head; large = large->next; } head = head->next; } small->next = largeDummy.next; large->next = nullptr ; return smallDummy.next; } };
1️⃣ 归并排序(分治法)|链表的中间结点 + 合并两个有序链表 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 class Solution {public : ListNode* middleNode (ListNode* head) { ListNode* pre = head; ListNode* slow = head; ListNode* fast = head; while (fast && fast->next) { pre = slow; slow = slow->next; fast = fast->next->next; } pre->next = nullptr ; return slow; } 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; return dummy.next; } ListNode* sortList (ListNode* head) { if (!head || !head->next) return head; ListNode* mid = middleNode (head); ListNode* head1 = sortList (head); ListNode* head2 = sortList (mid); return mergeTwoLists (head1, head2); } };