▶ 有两个单链表,从中间某个节点开始结点完全相同,找出该节点。
A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3
● 神奇的双指针法,34 ms,,详细步骤见代码注释(设两个单链表结点个数分别为 m 和 n,公共部分长为 k),时间复杂度 O(n),空间复杂度 O(1)
1 class Solution 2 { 3 public: 4 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 5 { 6 if (headA == nullptr || headB == nullptr) 7 return nullptr; 8 ListNode *pa, *pb; 9 for (pa = headA, pb = headB; pa != pb;) // 两指针分别指向两个单链表头部,每次循环将两指针向后一移一个结点 10 { 11 if (pa->next == nullptr && pb->next == nullptr)// 两个指针同时为空指针,说明两单链表没有公共结点(发生在第 m + n + 1 次循环) 12 return nullptr; 13 pa = pa->next, pb = pb->next; // 两指针分别向后移动一个结点 14 if (pa == nullptr) // 其中一个指针超出某一条单链表的尾部的时候,让它指向另一条单链表的头部 15 pa = headB; 16 if (pb == nullptr) 17 pb = headA; 18 } 19 return pa; // 两指针指向同一结点的时候,该节点就是公共链表部分的头结点(发生在第 m + n - k + 1 次循环) 20 } 21 };
● 双指针法高压版,36 ms
1 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 2 { 3 ListNode *cur1, *cur2; 4 for(cur1 = headA, cur2 = headB;cur1 != cur2;) 5 { 6 cur1 = cur1 ? cur1->next : headB; 7 cur2 = cur2 ? cur2->next : headA; 8 } 9 return cur1; 10 }
● 代码,55 ms,便于理解的一个双指针法,加上蜜汁优化以后 24 ms,时间复杂度 O(n)
1 static int x = []() \ 2 { 3 std::ios::sync_with_stdio(false); // 关闭 cout 和 cin,使用 printf 和 scanf 4 cin.tie(NULL); // 解除绑定 cin 和 cout 5 return 0; 6 } (); 7 8 class Solution 9 { 10 public: 11 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 12 { 13 ListNode *cur1, *cur2; 14 int la, lb, diff; 15 for (la = 0, cur1 = headA; cur1 != nullptr; cur1 = cur1->next, la++);// 统计两单链表的结点数 16 for (lb = 0, cur2 = headB; cur2 != nullptr; cur2 = cur2->next, lb++); 17 cur1 = headA, cur2 = headB; 18 diff = abs(la - lb); 19 if (la > lb) 20 for (; diff > 0; cur1 = cur1->next, diff--); // 把公共结点之前不等长的部分跳过 21 else 22 for (; diff > 0; cur2 = cur2->next, diff--); 23 for (; cur1 != cur2; cur1 = cur1->next, cur2 = cur2->next); // 共同前进直到两指针指向的结点相同,即为公共单链表头结点 24 return cur1; 25 } 26 };
● 代码,35 ms,强改指针地址
1 class Solution//35 2 { 3 public: 4 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 5 { 6 ListNode *cur, *result; 7 unsigned long *ptr; 8 for (cur = headA; cur != nullptr;)// 默认结点二进制地址的末位为 0,把单链表 a 中各节点的 next 值二进制末位强行改成 1 9 { 10 ptr = (unsigned long *)&cur->next; 11 cur = cur->next; 12 *ptr |= 1; 13 } 14 for (cur = headB, result = nullptr; cur; cur = cur->next)// 沿着单链表 b 寻找 next 值被修改的结点,即为公共单链表的头结点 15 { 16 if ((unsigned long)cur->next & 1) 17 { 18 result = cur; 19 break; 20 } 21 } 22 for (cur = headA; cur; cur = cur->next)// 把单链表 a 中各节点的 next 值改回去 23 { 24 ptr = (unsigned long *)&cur->next; 25 *ptr &= (~0ULL << 1); 26 } 27 return result; 28 } 29 };
● 代码,38 ms,强改指针地址高压版,注释为等价的改地址方法
1 class Solution//38 2 { 3 public: 4 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 5 { 6 if (headA == nullptr || headB == nullptr) 7 return NULL; 8 ListNode **pp, *q; 9 for (pp = &(headA->next); (*(long*)pp |= 1L) != 1L;) 10 pp = &(((ListNode*)((char*)*pp - 1))->next); // pp = (ListNode**)((char*)*pp - 1 + sizeof(int)); 11 for (q = headB; q != NULL && !((long)(q->next) & 1L); q = q->next); 12 for(pp = &(headA->next); (*(long*)pp &= -2L) != 0L;) 13 pp = &((*pp)->next); // pp = (ListNode**)((char*)*pp + sizeof(int)); 14 return q; 15 } 16 };
● 其他方法:暴力枚举,逐对检验,时间复杂度 O(mn),空间复杂度 O(1);其中一支单链表加入 hash 表,依另一支来查找,时间复杂度 O(m+n),空间复杂度 O(m) 或 O(n)