之前我的写文章《链表在STM32中的应用》6 Z. Q3 _+ `8 ]5 U+ D! u
# R }- g- z# {& F* N2 H Y# s: L/ B
! Y2 [" ^$ g# O$ o5 b+ u
不论在面试或者刷题过程中,很大概率都会遇到环形链表这种类型的题目,例如 leetcode 141. 环形链表 以及 leetcode 142. 环形链表 II 等,本文主要介绍通过快慢指针法来解决此类题型,以供大家参考。2 B" ~/ d5 v" D2 W& l3 w
+ X" _& D1 D1 Q3 j
) R1 Z# e+ K! D4 D环形链表2 @- }3 W( S; u; ?
环形链表大致样子如下图所示:
5 ~- R K6 ~2 ]% H* U( [9 K: b
+ z* k" u! \8 w; f
0 q5 d# k0 x4 x$ X1 }0 Q
, D4 d7 q& s1 _# b& N8 P快慢指针法* p: F8 }! J D: ^7 F9 n/ w
判断链表是否是环形链表,一般通过快慢指针法。- U9 l: z& F5 {
, D" o) A; T+ g
# ^7 t) l8 U3 G) u- e- ?2 U
操作步骤
3 V+ T( W7 {7 `: K5 v I: D一、分别定义两个均指向头节点的指针(fast/slow);+ C, Q0 y! Y5 K) W, {
二、快指针每次走两步,慢指针每次走一步;
# ?, h% N6 O7 a7 M5 a三、如果链表存在环,则快慢指针一定会在环中相遇。. v9 N( [4 D: |6 d& y
# d" z _. y$ m f9 |& m
! x9 r- x7 ^6 x6 v/ T7 P! m+ I! L& o' w
如下图示,快慢指针行走的轨迹如下:; E2 K/ H: g( [1 t# G
faster:1--->3--->5--->7
3 [! F" w: E( K4 ^, l& xslower:1--->2--->3--->4" U, N/ H4 h2 @1 o! D' `
$ `* Z' K8 Y: R; `; ~faster:7--->5
! b! R) O) L7 f! Uslower:4--->5
9 p+ ~+ T) k& r' ?' q当他们同时到达节点 5 时,完美相遇。$ Y" z' v& c$ @4 g( b
, c! C9 D. e6 H3 F- U' ~" k% P, u: I" D4 @- s7 Z; S
& |: x) K! f* c9 o) o. }
举栗1:判断链表是否有环, a3 t4 m% q5 W, z
/ g4 n" j' ~8 N题目
9 |/ Q7 c% K3 G3 C3 k5 \ Show me the Code
, h# y2 ^7 z- {- w% F& j- <font face="微软雅黑" size="3">// C 语言
' `/ x$ r7 q1 ~. V5 U+ u - bool hasCycle(struct ListNode *head) {1 X: _4 ~& q! l3 y- E
- /* 特判 */, l8 s# I5 B1 u% i7 g
- if (head == NULL || head->next == NULL) {9 S1 |5 w. t6 a3 V" m" _, `5 `
- return false;9 y4 v! n2 g, u1 `' t) \
- }+ R- U" j6 s. T
- struct ListNode *slow = head, *fast = head;+ G5 a9 l5 {8 q* d
- /* fast指针走过链表末端,说明链表无环*/3 K7 l+ A& u3 _8 o# R
- while((fast != NULL) && (fast->next != NULL)) {, W% K; |7 W3 Q9 K* R4 T) [ g! x5 t
- slow = slow->next;
5 ^0 E5 ]; e8 M) a - fast = fast->next->next;
0 c- S1 N% S" E! A% m c - if(slow == fast) {
" M7 K3 `* ~) {2 X* Y1 ^ - return true;! [3 ^, R' v' f' q8 h: j
- }
6 ~- |. L" t( ? G4 O U) B - }
, a+ q/ X; k; P1 i' `* ~, Q - return false;
B. L3 z) e0 b; W - }</font>
复制代码- <font face="微软雅黑" size="3"># python3" n0 j5 j0 o' V- t6 ?9 J
- class Solution:* h. _' f( o1 W
- def hasCycle(self, head: ListNode) -> bool:' X8 ~, `) L3 I' L+ U) h9 ~" i
- if not head or not head.next: return False
, ?, h9 k+ C3 W( A: h, Q+ A - slow, fast = head, head
' w* g5 @+ N- C1 p- z - while fast and fast.next:
5 r9 x+ T. Q! d! P8 z/ [& h - slow = slow.next5 \" g7 c# I1 q, L4 W" J
- fast = fast.next.next ) i t' v6 a# b7 ^1 q; B6 x
- if slow is fast: return True, Q7 d$ V% b( W" v) p% d( ` n; V
- return False </font>
复制代码 举例2:求入环的第一个节点4 z* ]. T$ Y7 ~
( E1 x# c4 J, `) y; B9 f6 U1 S6 w
题目
1 ^7 u: ?, G A7 M, Z E/ _" k% |. t4 c1 a) {" a
思路
5 o" S1 {6 D" M本题除了需要判断链表是否有环外,如果有环还要求入环的第一个节点,因此是上一个题目的升级版本,还是以快慢指针中举例的那个链表作为示例,下图将描述当链表有环的时候,如何求出入环的第一个节点,见下图示: s. d, t& N8 B$ @+ o m
已判断链表有环; z+ @0 ]8 Q+ ?6 R! Y" k* n! j0 a
. |" Z( y3 ~# {6 f求入环的第一个节点
8 |' u* l5 b# a" w让慢指针重新指向链表的头节点,并让快慢指针同时每次只走一步; [ o: p4 X5 l9 [0 |4 ~' z
faster:5--->6--->7
) s( ^3 n% R! g* e$ Q9 t; Y2 \slower:1--->2--->3
' n6 x' {3 |7 |9 d4 F! O3 E- B
8 d$ M. o# E- `0 X7 p继续走
H7 X5 ]: u% F: e2 v. f7 x% R3 Wfaster:7--->4( K4 g' C- j/ F2 |/ t* L# ]
slower:3--->45 b& v0 C2 [- \3 s: X/ X: [
当他们同时到达节点 4 时,再次完美相遇。# ~! O3 y8 f0 |& e0 l5 A
c. i8 b- E3 D" L6 r$ v5 a% K+ y9 l+ U1 |7 m h! f+ o
8 D# ]3 E. m4 {2 U, |0 P+ S3 M& w6 ^; \
4 u$ v( A. ?/ j6 V- A因此,由上面的图可知,当判断链表有环(快慢指针相遇)之后,再让快(或者慢)指针重新指向链表头节点,另外一个指针仍保持指向不变,然后让快/慢指针同时走,且每次只走一步,当他们再次相遇时,此时相遇的节点就是入环的第一个节点。
' {$ c6 [ z/ i9 z6 ?. M; \! j% V6 A! Y0 D# l- u5 R
% ^/ r& w" K" DShow me the Code
/ U# J }" q0 T$ n5 K2 b0 `- <font face="微软雅黑" size="3">// c 语言
V6 y3 q8 j0 {8 A, ` - struct ListNode *detectCycle(struct ListNode *head) {' E5 T9 f3 o1 g! P1 ?
- /* 记录是否有环 */! e" s; j5 Z6 ]
- bool flag = false;
( J+ t7 V5 N& X+ Y - struct ListNode *slow = head, *fast = head;
: H/ B+ ^! k/ e. n8 c3 M7 v1 S - while (fast != NULL && fast->next != NULL) {
8 [" h1 \* o8 G* ~) i9 F# u" A - slow = slow->next;
' f% h" V e% m( O7 W - fast = fast->next->next;
( y; `4 r. N w - /* 有环则更新 flag 的值,并跳出循环寻找入环第一个节点 */' y, I( ]( M% W1 g3 _
- if (slow == fast) {, F; q: w2 v- W
- flag = true;
/ r' }. |& Z; a7 z/ Y5 A# m0 r* {5 B - break;, {/ q$ Y% ?5 B4 q/ j
- }
5 c2 w( K" J. h# ~( Q - }. b6 m6 c# { h
- /* 无环则直接返回空 */
. \" U5 g/ r7 f! z$ ^; K - if (flag == false) {
8 ~. K+ M! L$ G' d0 k; [ - return NULL;
& F( o8 D- d: Q- y - }: ^, X+ n; ^: Q& s9 ?7 e
- /* 慢指针重新指向链表头节点,快指针仍指向相遇节点 */
: X2 ]: I+ K) b, X2 j - slow = head;
, @% O$ Z& A: h# V - /* 快慢指针每次走一步,相遇节点即为入环第一个节点 *// o" q* [- D0 \9 r7 D! A
- while (fast != slow) {
4 j9 |- H7 n" W9 J5 | - fast = fast->next;& h1 }$ }- J9 a8 n
- slow = slow->next;
- {2 \2 N& t% n, T1 Q - }# O+ A& b, K( F- P, b
- return slow;, q; X$ s V7 f$ T
- }</font>
复制代码- <font face="微软雅黑" size="3">// go 语言+ }9 V/ I' ]- q# _. j) M+ s
# W) u9 F! p u) X- func detectCycle(head *ListNode) *ListNode {. C7 P, f+ R# p2 _7 @! v
- slow, fast := head, head
! _" h) S) V8 {/ Y. l5 l3 l - for fast != nil && fast.Next != nil {" p, s7 S+ `+ x4 g
- fast, slow = fast.Next.Next, slow.Next
, A& C2 j. k& j! H - if fast == slow { \! ` c3 F/ r) I/ V
- break' V8 h& W( \; X
- }
2 l2 X: }6 X+ p2 ?5 T. ~' Y - }
u6 R4 X. b2 S7 [. C1 k - if fast == nil || fast.Next == nil {
3 p! A: x2 v) P1 U: _ - return nil
$ I' u/ y' o) Z7 g& E. m - }( Z0 @9 m7 z' T5 z6 L1 @3 H
- slow = head
6 e! M& ?4 }8 l" |$ o - for fast != slow {, h1 I- h8 X" b
- fast, slow = fast.Next, slow.Next
) G$ j* [: _+ H) k2 K - }
% ]' W* t; N/ ~. @ - return fast
! M4 s& u' I9 D2 u - }</font>
复制代码 - [9 S! H e# n @! Y' j3 ]
& S6 J3 e% T3 V( R/ @( z( d |