# 纲要 > 主干纲要、Hint/线索/路标 - **链表节点操作** - **获取节点**: - 获取链表中**第 k 个节点** - 获取链表**倒数第 k 个节点** - 获取链表**中间节点** ⭐ - 获取链表**尾节点** - **删除节点**: - 删除**第 k 个节点** - 删除**倒数第 k 个节点** - 删除**中间节点** ⭐ - 删除**给定节点**(非尾节点) ⭐ - 删除指定值的所有节点 - 删除**有序**链表中的**重复节点**(重复值保留一个,或者全删) - **交换节点**: - 两两交换链表中的节点 - **链表整体操作** - **反转链表** - 反转整个链表; - 反转链表指定区段 `[left, right]` - k 个一组反转链表; - **旋转链表** - **链表拆分** - **拆出头部长度为 L 的一段** - **均等拆分**(拆为前后近似均等的两段) - **奇偶节点拆分** - **按值拆分** - **链表合并**: - **插空合并两个链表** - 合并两个升序链表 - 合并 k 个升序链表 - **链表排序** - 插入排序 - 归并排序 - 链表专题问题 - **环形链表**:判断链表是否有环,求环的入口节点,求环长等; - **相交链表**:判断两链表是否相交 - **判断回文链表** - 链表模拟 - **模拟两数相加**(逆序 / 正序表示两个数) %% # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later %% <br><br> # 链表节点操作 ## 获取节点 ##### 获取链表中第 k 个节点 ```cpp // 获取链表中的第k个节点, 不存在时返回null; ListNode* GetKthNode(ListNode * head, int k) { if (!head || k <= 0) return nullptr; int step = k - 1; while (head && step--) { // 节点数量足够时, 最后step会减为-1而非0. head = head->next; } return head; // head可能为空 } ``` ##### 获取链表中倒数第 k 个节点 - `ahead` 指针从 `head` 开始**先走 k 步后**,再让 `cur` 指针从`head` 开始,二者同步走; - 两指针每次走一步,当 `ahead` 指向 `nullptr` 时,`cur` 恰好指向倒数第 k 个节点。 ```cpp // 获取链表中的倒数第k个节点 ListNode* GetKthNodeFromEnd(ListNode *head, int k){ if (!head || k <= 0) return nullptr; ListNode *cur = head, *ahead = head; while (ahead && k--) { // ahead比cur先走k步, 再同步走; 当ahead走向nullptr时, cur正好指向倒数第k个节点; ahead = ahead->next; } // 注意这里k可能为-1, 不能用if(k)判断! if (k > 0) return nullptr; // ahead没走完k步, 即链表长度小于k, 不存在倒数第k个节点. while (ahead) { cur = cur->next; ahead = ahead->next; } return cur; } ``` ##### 获取链表中间节点 **应用快慢指针**: ![[Excalidraw-Solution/链表_1.excalidraw.svg|729]] %%[[Excalidraw-Solution/链表_1.excalidraw.md|🖋 Edit in Excalidraw]]%% ```cpp ListNode* GetMiddleNode(ListNode *head) { if (!head || !head->next) return head; ListNode *slow = head, *fast = head->next; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } return slow; // 链表偶数长时, slow指向前半段尾节点, fast恰指向尾节点. // 如果希望链表偶数长时, slow指向后半段首节点, 则再移动一步. // if (fast) { // slow = slow->next; // } // return slow; } ``` ##### 获取链表尾节点 ```cpp ListNode * GetTailNode(ListNode * head) { while (head && head->next) { head = head->next; } return head; } ``` <br> ## 删除节点 ##### 删除第 k 个节点 ```cpp ListNode* DeleteKthNode(ListNode *head, int k) { if (!head || k <= 0) return head; // 无操作 ListNode dummy(0, head), *prev = &dummy; int step = k - 1; // 删除第k个节点, p从dummy出发, 走k-1步, 指向第k-1个节点 while (prev && step--) { prev = prev->next; } if (prev && prev->next) { // 第k-1个节点, 第k个节点均存在 ListNode *del = prev->next; prev->next = prev->next->next; delete del; } return dummy.next; } ``` ##### 删除倒数第 k 个节点 ```cpp // 删除链表中的倒数第k个节点 ListNode* DeleteKthNodeFromEnd(ListNode *head, int k){ if (!head || k <= 0) return head; ListNode dummy(0, head); ListNode *prev = &dummy, *ahead = head; while (ahead && k--) { // ahead从head起先走k步, prevDel再从dummy出发, 与ahead同步走. ahead = ahead->next; } if (k > 0) return dummy.next; // 若k未能减到小于0(上面循环结束后k==-1), 意味着链表长度小于k, 不存在倒数第k个节点 while (ahead) { ahead = ahead->next; prev = prev->next; } ListNode *del = prev->next; prev->next = prev->next->next; // 链表长度大于等于k, 倒数第k个节点必然存在 delete del; return dummy.next; } ``` ##### 删除中间节点 沿用"**快慢指针**"获取**链表中点**的思路: - 若链表长度为偶数,当 `fast` 指向尾节点时,**`slow` 恰好指向前半段尾节点, 即为需删除的后半段首节点的前驱节点**; - 若链表长度为奇数,当 `fast` 指向空时,**`slow` 恰好指向待删除的链表中间节点**。 - => **要使 slow 少走一步而指向前驱节点**, 故 **`fast` 需要在停留在倒数第二个节点**, 因此循环条件改为 `fast->next && fast->next->next`,仅当 `fast` **后两个节点均存在时才后移**。 > [!summary] > > - 定位 "**中点**" 时,是**快慢指针==分别从 `head` , `head->next` 出发==**, `while(fast && fast->next)` 时移动; > - 定位 "**中点的前驱顶点**",则**快慢指针==同时从 `dummy` 出发==**,`while (fast && fast->next && fast->next->next)` 时移动。 > ```cpp // 删除链表中间节点 (特指"下标为n/2下取整"的节点, 即偶数长时, 后半段的首节点) ListNode* DeleteMiddleNode(ListNode *head) { if (!head) return nullptr; // if (!head->next) { delete head; return nullptr; } ListNode *slow = head, *fast = head->next; while (fast && fast->next && fast->next->next) { slow = slow->next; fast = fast->next->next; } ListNode *del = slow->next; slow->next = slow->next->next; delete del; return head; // 当链表长度>=2时, 删除的必然不是头结点, 故head必然存在. } ``` ##### 删除给定链表节点 (非尾节点) ```cpp // 删除链表中某个给定节点(非尾节点), 头节点未知 => 替换该节点与后一节点的值, 然后删掉后一节点. void DeleteTargetNode(ListNode *node) { if (node && node->next) { node->val = node->next->val; ListNode *tmp = node->next; node->next = node->next->next; delete tmp; } } ``` ##### 删除指定值的所有节点 ```cpp // 删除链表中值为给定值的所有节点 ListNode* RemoveValueFromList(ListNode *head, int val) { if (!head) return nullptr; ListNode dummy(0, head), *prev = &dummy; while (prev->next) { if (prev->next->val == val) { ListNode *tmp = prev->next; prev->next = prev->next->next; delete tmp; } else { prev = prev->next; } } return dummy.next; } ``` ##### 删除有序链表中的重复节点 - 对于存在重复值的节点,**保留其中一个** ```cpp // 删除"有序"链表中的多余重复节点, 使得每个值只保留一次 ListNode* RemoveDuplicateNode(ListNode *head) { if (!head || !head->next) return head; ListNode *p = head; while (p->next) { if (p->next->val == p->val) { ListNode *tmp = p->next; p->next = p->next->next; delete tmp; } else { p = p->next; } } return head; } ``` - 对于存在重复值的节点,**全部删除** ```cpp // 删除"有序"链表中的所有重复节点. 即出现1次以上的值, 全部删除, 一个也不保留 ListNode* RemoveAllDuplicateNode(ListNode *head) { if (!head || !head->next) return head; ListNode dummy(-1, head), *prev = &dummy; while (prev->next && prev->next->next) { if (prev->next->val == prev->next->next->val) { int x = prev->next->val; while (prev->next && prev->next->val == x) { ListNode *tmp = prev->next; prev->next = prev->next->next; delete tmp; } } else { prev = prev->next; } } return dummy.next; } ``` <br> ## 交换节点 ##### 两两一组交换组内的相邻两节点 ```cpp // 迭代的写法 ListNode *SwapPairs(ListNode *head) { if (!head || !head->next) return head; ListNode dummy(-1, head), *prev = &dummy; while (prev->next && prev->next->next) { ListNode *p1 = prev->next, *p2 = prev->next->next; p1->next = p2->next; p2->next = p1; prev->next = p2; prev = p1; } return dummy.next; } // 递归的写法 ListNode *SwapPairsRecur(ListNode *head) { if (!head || !head->next) return head; ListNode *p1 = head, *p2 = head->next; p1->next = SwapPairsRecur(p2->next); p2->next = p1; return p2; } ``` <br><br><br> # 链表操作 ## 反转链表 ##### 反转整个链表 ```cpp // 思路一: 三指针原地反转 ListNode* ReverseList(ListNode *head) { ListNode *prev = nullptr, *cur = head; while (cur) { ListNode *nxt = cur->next; cur->next = prev; prev = cur; cur = nxt; } return prev; } // 思路二: 头插法反转 ListNode* ReverseListByInsertToHead(ListNode *head) { if (!head) return nullptr; ListNode dummy(-1, head); while (head->next) { ListNode *p = head->next; head->next = head->next->next; p->next = dummy.next; dummy.next = p; } return dummy.next; } // 思路二: 头插法反转, 写法二 ListNode* ReverseListByInsertToHead(ListNode *head) { if (!head) return nullptr; ListNode dummy(-1, nullptr); while (head) { ListNode *p = head; head = head->next; p->next = dummy.next; dummy.next = p; } return dummy.next; } // 思路三: 递归 ListNode* reverseListReCur(ListNode* head) { if (!head || !head->next) return head; ListNode* rest = head->next; // 暂存head->next, 同时对head->next起始的部分进行反转. ListNode* new_head = reverseListRecur(rest); rest->next = head; head->next = nullptr; return new_head; } ``` ##### 反转部分链表 ```cpp // 反转[left, right]区间的链表. 节点下标从1开始, 若left或right越界则返回, 不操作) // 头插法反转 ListNode* reverseBetween(ListNode* head, int left, int right) { ListNode dummy(0, head), *prev = &dummy; int step = left - 1; while (prev && step--) { // 从dummy起走left-1步, prev_l指向l的前驱节点. prev = prev->next; } if (!prev || !prev->next) return head; // 头插法: 每次将l->next插入到prev->next; ListNode* l = prev->next; for (int i = 0; l->next && i < right - left; ++i) { // 初始时l=prev->next, 共需要对prev->next右侧的right-left个节点进行头插反转. auto node = l->next; l->next = node->next; node->next = prev->next; prev->next = node; } return dummy.next; } ``` ##### K 个一组反转链表 ```cpp ListNode* reverseKGroup(ListNode* head, int k) { if (!head || k <= 0) return head; ListNode dummy(0, nullptr), *prev = &dummy; while (head) { // head指向剩余待处理链表头部. ListNode* ahead = head; // ahead从head出发, 走k步, 确保存在k个节点. int step = k; while (ahead && step--) { ahead = ahead->next; } if (step > 0) { // 若剩余链表不足k个节点, 不进行处理, 直接将head接给prev->next. prev->next = head; break; } ListNode *tail = head; // tail指向剩余链表头结点, 将k个节点反转后即位于该组尾部. while (head != ahead) { // 当存在至少k个节点时, ahead位于这k个节点之后. ListNode *cur = head; head = head->next; cur->next = prev->next; prev->next = cur; } prev = tail; } return dummy.next; } ``` <br> ## 旋转链表 1. 首尾连成环; 2. 右移 k 位,则需要找到**原链表中倒数第 k 个节点的前驱节点**,在其后断开,以其后的**倒数第 k 个节点为新的头结点**。 ```cpp /* 链表右移k位 * 每个节点右移k位, 即原链表中最后k个节点移动到前面, 倒数第k个节点变成头节点. * 因此处理步骤是: * 1. 将链表首尾相连, 得到环, 以及链表总长L; * 2. 从原始的头节点开始, 移动L-k-1步, 指向原链表中的第L-k个节点, 其后一节点即为倒数第k个节点. * 3. 断开第L-K个节点与倒数第K个节点之间的连边, 以后者为头节点返回, 即得到旋转后的新链表 * 4. 如果k值为负, 则整个链表将会向左旋转. */ ListNode* RotateRightList(ListNode* head, int k) { if (!head || !head->next) return head; ListNode *tail = head; int length = 1; while (tail->next) { // 获取尾节点和链表长 ++length; tail = tail->next; } k %= length; if (k == 0) return head; tail->next = head; // 首尾相连 int step = length - k - 1; // prev从head起走n-k-1步, 即指向倒数第k个节点的前驱节点. ListNode *prev = head; while (prev && step--) { prev = prev->next; } head = prev->next; prev->next = nullptr; return head; } ``` <br> ## 合并链表 ##### 插空合并两条链表 要求:**将 `l2` 插空合并到 `l1` 之中**。若 `l2` 较长,则插空完成后,剩余部分直接接到 `l1` 尾部。 - 思路一:当 `l1` 与 `l2` 均存在时,**每次取出两链表头部节点 `p1` 和 `p2`** ,并将 `l1` 与 `l2` 后移。 - 令 `p1` 指向 `p2`,此时再判断 `p2` 的指向:若 `l1` 还有剩余则指向 `l1`,否则指向 `p2`; - 思路二:令 `prev` 在`l1` 链表节点上移动,每次从 `l2` 中取出一个节点,插入到 `prev` 之后。 - 最后若 `l2` 还有剩余,则将剩余的 `l2` 链表整个接在`prev` 之后。 ```cpp // 插空合并两个链表 (将l2插空合并到l1之中, 若l2较长, 则插空完成后,剩余部分直接接到l1尾部) // 实现一: 初始时用head指向l1. 每次取出l1和l2头部的节点p1和p2, 将p1指向p2, p2再指向剩余的l1(如果存在)或l2. ListNode* MergeTwoListNodeCutIn(ListNode *l1, ListNode *l2) { if (!l1) return l2; if (!l2) return l1; ListNode *head = l1; while (l1 && l2) { ListNode *p1 = l1, *p2 = l2; // 取出l1和l2头部的两个节点p1, p2; l1 = l1->next; l2 = l2->next; p1->next = p2; p2->next = (l1 ? l1 : l2); // 如果l1为空, 则p2指向剩余的l2 } return head; } // 插空合并两个链表 (将l2插空合并到l1之中, 若l2较长, 则插空完成后,剩余部分直接接到l1尾部) // 实现二: p1在原l1的节点上移动, 如果l2中还存在节点, 就将其插入p1之后 ListNode* MergeTwoListNodeCutIn2(ListNode *l1, ListNode *l2) { if (!l1) return l2; if (!l2) return l1; ListNode *prev = l1; // prev原l1的节点上移动. 如果l2中还存在节点, 就将其插入prev之后. while (prev->next && l2) { // 注意, 避免prev指向空, 因为最后可能要插入剩余的l2 ListNode *p = l2; l2 = l2->next; p->next = prev->next; prev->next = p; prev = prev->next->next; // prev跳过刚插入其后的p2节点. } if (l2) { // 如果l1还有剩余, 无需处理; 如果l2还有剩余, 直接追加到p1之后 prev->next = l2; } return l1; } ``` ##### 合并两条有序链表 ```cpp ListNode * MergeTwoSortedList(ListNode *l1, ListNode *l2) { if (l1) return l2; if (l2) return l1; ListNode dummy(0, nullptr), *prev = &dummy; while (l1 && l2) { if (l1->val <= l2->val) { prev->next = l1; l1 = l1->next; } else { prev->next = l2; l2 = l2->next; } prev = prev->next; } prev->next = l1 ? l1 : l2; return dummy.next; } ``` ##### 合并 k 条升序链表 > 参见 [[99-题解/leetcode/lc-23 合并 K 个升序链表|lc-23 合并 K 个升序链表]],可采用 "堆" 的方式。 <br> <br> ## 拆分链表 #### 均等拆分 偶数长度时,拆分为**等长两段**;奇数长度时,拆分为**前一段比后一段长 1 的两段**。 ```cpp // 使用快慢指针确定链表中点, 由此进行拆分. pair<ListNode*, ListNode*> splitList(ListNode* head) { if (!head || !head->next) return { head, nullptr }; ListNode *slow = head, *fast = head->next; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } ListNode *new_head = slow->next; slow->next = nullptr; return { head, new_head } ; } ``` ##### 拆出头部指定长度的链表 ```cpp // 拆分出头部一段长度为n的链表, 返回剩余链表的头节点. ListNode* curFront(ListNode* head, int n) { if (n <= 0) return head; int step = n - 1; while (head && step--) { head = head->next; } if (head) { ListNode* rest = head->next; head->next = nullptr; return rest; } return nullptr; } ``` ##### 按奇偶节点拆分成两条链表 ```cpp // 拆分链表: 按奇偶拆分, 返回偶数节点拆分得到的链表 // 拆分链表: 按奇偶拆分, 返回偶数节点拆分得到的链表 ListNode* PartitionEven(ListNode* head) { if (!head || !head->next) return nullptr; ListNode dummy(0, nullptr), *prev = &dummy; while (head && head->next) { // 每次处理两个节点, 将其中第二个追加到prev->next. prev->next = head->next; prev = prev->next; head->next = head->next->next; head = head->next; } prev->next = nullptr;   // 记得置空! 其后可能还连有节点. return dummy.next; } ``` ##### 按值拆分 小于 `x` 的为一段,大于等于 `x` 的为另一段。 > [!caution] 注意最后将两条链表尾置空! ```cpp pair<ListNode*, ListNode*> splitListByValue(ListNode* head, int x) { ListNode dummy1(0, nullptr), dummy2(0, nullptr); ListNode *prev1 = &dummy1, *prev2 = &dummy2; while (head) { if (head->val < x) { prev1->next = head; prev1 = prev1->next; } else { prev2->next = head; prev2 = prev2->next; } head = head->next; } prev1->next = nullptr; // 注意将链表尾置空!!确保断开. prev2->next = nullptr; return { dummy1.next, dummy1.next }; } ``` <br><br> ## 排序链表 > 参见 [[99-题解/leetcode/lc-148 排序链表|lc-148 排序链表]] ##### 插入排序 ```cpp ListNode* sortList(ListNode* head) { if (!head || !head->next) return head; ListNode dummy(0, head), *prev = head; while (prev->next) { ListNode *prev2 = &dummy; while (prev2 != prev && prev2->next->val < prev->next->val) { // 找到插入位置: prev2->next prev2 = prev2->next; } if (prev2 != prev) { // 可插入 ListNode *cur = prev->next; prev->next = prev->next->next; cur->next = prev2->next; prev2->next = cur; } else { // 当前节点及前方节点有序 prev = prev->next; } } return dummy.next; } ``` ##### 归并排序 自顶向下递归: - **拆分成均等的两段链表** - **==递归==地将两段链表分别排序**,再 "**合并两条有序链表**" ```cpp // 思路一: 自顶向下递归的归并排序 class Solution1 { public: ListNode* merge_two_lists(ListNode* list1, ListNode* list2) { if (!list1) return list2; if (!list2) return list1; ListNode dummy(0), *prev = &dummy; while (list1 && list2) { if (list1->val <= list2->val) { prev->next = list1; list1 = list1->next; } else { prev->next = list2; list2 = list2->next; } prev = prev->next; } prev->next = list1 ? list1 : list2; return dummy.next; } ListNode* sortList(ListNode* head) { if (!head || !head->next) return head; ListNode *slow = head, *fast = head->next; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } ListNode* new_head = slow->next; slow->next = nullptr; return merge_two_lists(sortList(head), sortList(new_head)); } }; ``` 自底向上归并: ```cpp // 思路二: 自底向上迭代的归并排序 class Solution2 { public: ListNode* sortList(ListNode* head) { if (!head || !head->next) return head; int n = getLength(head); ListNode dummy(0, head), *prev = &dummy; int seq = 1; while (seq < n) { ListNode *prev = &dummy, *rest = dummy.next; while (rest) { ListNode *h1 = rest; ListNode *h2 = cutListHead(h1, seq); rest = cutListHead(h2, seq); prev->next = mergeTwoList(h1, h2); while (prev->next) { prev = prev->next; } } seq *= 2; } return dummy.next; } ListNode* mergeTwoList(ListNode* list1, ListNode* list2) { if (!list1) return list2; if (!list2) return list1; ListNode dummy(0), *prev = &dummy; while (list1 && list2) { if (list1->val <= list2->val) { prev->next = list1; list1 = list1->next; } else { prev->next = list2; list2 = list2->next; } prev = prev->next; } prev->next = list1 ? list1 : list2; return dummy.next; } ListNode* cutListHead(ListNode* head, int len) { if (len <= 0) return head; int step = len - 1; while (head && step--) { head = head->next; } if (head) { ListNode* rest = head->next; head->next = nullptr; return rest; } return nullptr; } int getLength(ListNode* head) { int n = 0; for (; head; ++n, head = head->next); return n; } }; ``` <br> # 相交链表 > 参见 [[99-题解/leetcode/lc-160 相交链表|lc-160 相交链表]] <br><br> # 环形链表专题 > 参见 [[99-题解/leetcode/lc-141 环形链表|lc-141 环形链表]]、[[99-题解/leetcode/lc-142 环形链表II|lc-142 环形链表II]] 系列问题: - 判断链表**是否有环** - 求链表中**环的入口节点** - 求环形链表中的**环长**、**直链长度**,**链表总长** ## (1)判断环形链表 ![[Excalidraw-Solution/链表_0.excalidraw.svg|335]] %%[[Excalidraw-Solution/链表_0.excalidraw.md|🖋 Edit in Excalidraw]]%% #### 方法 快慢指针从原点开始走,**快指针每次走 2 步,慢指针每次走 1 步**,若链表有环,**则两指针最终必然相交**,且相交时**慢指针还未走完环的一圈**。 #### 证明 如果存在环,快指针 `fast` 一定会先进入环中。当**慢指针 `slow` 进入环中时,两指针距离 $d$ 小于环长 $c$**。 由于慢指针步长为 1,快指针步长为 2,**因此每走一步,==二者间的距离减 1==**,所以,**在慢指针 `slow` 走完第一圈前,必然会被快指针 `fast` 追上,故==二者必定会相遇==**。 #### 扩展 > [!faq] ❓若快慢指针的**步长分别为 `f` 、`s`**,则**两指针是否可能相遇?即能否用以判断环形链表**? 具体取决于 **"二者步长差值 `f-s`"**、**"环长 `c`"**、**"慢指针入环时二者的初始距离 `d`"** 三个因素。 **若两指针能够相遇,假设慢指针入环后,二者行走 `k` 步后相遇,则有等式 $fk-sk=nc+d$ 成立,其中 `n` 为快指针多走的圈数**。 在已知 $f,s,d,c$ 时,上式是**关于 `k` 与 `n` 的二元一次方程**,**若存在==整数解== `k` 与 `n` 使上式成立,则两指针能够相遇**。 > [!example] 一些反例 > > - $s=1$,$f=c+1$,即**慢指针步长为 1,快指针步长为环长+1,则只要慢指针入环时二者距离 $d\neq 0$,则两指针始终保持初始距离 $d$,永远不可能相遇**。 > - 代入式子 $fk-sk=nc+d$,得到 $kc=nc+d$,因此除非 $d==0$,否则无解。 > >> > > - $s=3$,$f=c=6$,**则慢指针每次落脚点只有两个:初始位置&相较于初始位置的半环长**; **快指针落脚点只有一个:初始位置**,**如果两指针的落脚点无重合,则永不可能相遇**。 > - 代入式子 $fk-sk=nc+d$,得到 $3k=3n+d$,由于 $d< c=6$, 因此除非 $d==0$ 或 $d==3$,否则无解。 > >> > > - $s=2$,$f=4$,则每次移动时**二者间距离-2**,取决于两指针**在环内的距奇偶性**, > - 若**距离为偶数**,则快指针必然能追上慢指针; > - 若**距离为奇数**,则最后两指针会相邻,再下一步**快指针越过慢指针**,二者**间距始终为奇数**,无法相遇。 <br><br> ## (2)求环形链表的入口节点 #### 方法 1. 快慢指针从原点开始走,**快指针每次走 2 步,慢指针每次走 1 步**,若链表有环,**则两指针最终必然相遇**。 2. **快慢指针首次相交后**,**慢指针 `s1`从该==相交点==起始继续每次走一步**,**另一个慢指针 `s2` 从==链表头==起始每次也走一步**,当 `s1` 与 `s2` 相交时,**二者所指向的即为==链表入口节点==**。 #### 证明 ![[Excalidraw-Solution/链表_2.excalidraw.svg|417]] %%[[Excalidraw-Solution/链表_2.excalidraw.md|🖋 Edit in Excalidraw]]%% 已知快慢指针必然会相遇,且**二者相遇时慢指针还未走完 1 圈**。 相遇时,**快指针所走路长为慢指针的两倍**,故有等式:$L+nc+d=2(L+d)$ 成立,其中 $n\geq 0$ 是**快指针多走的圈数**。 **移项得到 $L=nc-d$**,即当**慢指针 `s1` 从相交点开始走,距离走完第 $n$ 圈还差 $d$ 长度时**(此时**慢指针==恰落在环入口点==**,见上图),**其走过的距离恰为 $L$**。 因此,**令第二个慢指针 `s2` 从链表头开始走,==走完 $L$ 长度==后**,**两指针 `s1` 与`s2` ==恰好同时落在环入口节点==**,即**两指针 "==相遇点==" 即为环入口点**。 <br> ## (3)求环形链表中各段长度,总长 #### 方法 - 采用 "**求环的入口节点**" 的方法,从**链表头走到入口节点**,得到 "**直链部分**" 的长度 $L$。 - 从**环入口节点**起始,每次走一步,**绕圈回到入口处可得到环长** $C$。 <br><br> # 参考资料 # Footnotes