之前我的写文章《链表在STM32中的应用》
5 |& E% K) \, Q# c) W6 W
, A2 Q. J( m0 |% G3 D/ _ s( M( G1 Z5 k
不论在面试或者刷题过程中,很大概率都会遇到环形链表这种类型的题目,例如 leetcode 141. 环形链表 以及 leetcode 142. 环形链表 II 等,本文主要介绍通过快慢指针法来解决此类题型,以供大家参考。; {! L* D, r) q2 r% {- e
1 u: R+ Y+ o. M" q6 a2 y* {
; B7 w" l8 a5 X环形链表1 a* o9 y+ b, H0 b; S
环形链表大致样子如下图所示:) o& r* q$ G# ?/ \' J L8 X
7 y% y2 J) j; x* _- w* g( R3 g. l7 ?+ {; j% w9 Y& _5 t8 `# u/ @
. {# J5 r; m/ n3 ?% C
快慢指针法" a2 N" D# @' y) c# m' u1 ^
判断链表是否是环形链表,一般通过快慢指针法。
8 b8 l) [# Y7 q5 b: a9 ]3 J P: {# L8 [/ @+ g! ]; i$ |
5 w& F, [: }$ _! F: ~' t操作步骤- A) j8 {' s: X8 R) r2 G
一、分别定义两个均指向头节点的指针(fast/slow);
! B% n/ A Z5 c! O0 b1 ]3 Z二、快指针每次走两步,慢指针每次走一步;
/ x6 y! ~8 r+ c0 N# }5 K三、如果链表存在环,则快慢指针一定会在环中相遇。* e* Z: t7 I8 V' V( E4 `0 x
4 c# S. b6 X4 k5 a6 s
' |# F) x) F4 ]' n! B7 J9 l: F8 J. p 如下图示,快慢指针行走的轨迹如下:
( s& M4 f9 ^3 t8 Nfaster:1--->3--->5--->7- ?. ^/ j+ x9 X( s& A& _
slower:1--->2--->3--->43 u( V$ i/ [6 Y
: U. _0 ^. N, A1 I* Ufaster:7--->50 j. {& D6 o0 ?8 ^7 v8 ?
slower:4--->58 u6 @" ?; U9 N, w
当他们同时到达节点 5 时,完美相遇。
% n/ X4 x- q: S* r4 |9 U4 Z" s" X
% b- i' H0 [) K* n* f! a3 `, o5 w/ _- e, [
$ @" q+ x8 v2 q9 [
举栗1:判断链表是否有环: Z! z3 }( f; Z" z" L4 N2 ?
/ b- \& ~- Y# b
题目
8 ?& q8 w! Y- E2 o3 a0 d+ i Show me the Code6 K1 W: m: y' t& z1 f5 o
- <font face="微软雅黑" size="3">// C 语言3 c) f0 k4 w8 Q
- bool hasCycle(struct ListNode *head) {' N! U5 i" y) g. u& A: z* @
- /* 特判 */
9 _6 [! O/ b) q5 `! X - if (head == NULL || head->next == NULL) {" S' S6 n; t" \
- return false;5 y0 B: l7 ^9 Q( W8 W2 k' W! c
- } V2 I' ?& @9 |4 P
- struct ListNode *slow = head, *fast = head;
/ S, }4 C d% N7 B: ]% F - /* fast指针走过链表末端,说明链表无环*/! T7 }7 P, m9 ^& H& [
- while((fast != NULL) && (fast->next != NULL)) {
0 A( P- N0 W# I: r# P3 \' g8 G& c - slow = slow->next;
4 L. |7 Z. ~2 B& w/ V# w - fast = fast->next->next;
+ h. r6 b ]2 l! D! z - if(slow == fast) {+ Z& u( ^1 T7 i: u7 E
- return true;8 {3 |( W+ [/ ^7 s, b4 b
- }
/ a9 s; w- E4 T9 }; x - }' u3 D' [$ _+ T6 c) R. A" s# f
- return false;7 R( C4 N2 G( `$ s& m( \$ p
- }</font>
复制代码- <font face="微软雅黑" size="3"># python3
/ \9 p9 ?/ ?* q V) F* Y, {( d' G - class Solution:
$ e" j8 U0 E% B$ K9 } - def hasCycle(self, head: ListNode) -> bool:7 _6 e1 J' l U$ D0 N/ v
- if not head or not head.next: return False
. n: c8 H5 ?1 N1 {9 K - slow, fast = head, head% Y! {+ u/ h2 F, ^+ c% _' X
- while fast and fast.next:. K) L& S8 ?/ i/ e4 y- C
- slow = slow.next$ J _2 r- f- g( Q0 i
- fast = fast.next.next
" O0 J1 @+ ^7 m$ s0 q& O - if slow is fast: return True
" ]8 Z# I0 h n, u1 }$ Q - return False </font>
复制代码 举例2:求入环的第一个节点
% V- d, }: t* K" s
- _/ q; `# ~$ q+ @+ I
题目 & W3 k. w& X8 J8 n5 z4 g. q
/ Q6 e" e2 t2 _6 ^+ O思路 ^- ]- N2 s8 g
本题除了需要判断链表是否有环外,如果有环还要求入环的第一个节点,因此是上一个题目的升级版本,还是以快慢指针中举例的那个链表作为示例,下图将描述当链表有环的时候,如何求出入环的第一个节点,见下图示:" z, o- y7 U9 v4 |4 c* B" P
已判断链表有环
4 E" d P7 w) r h
2 z# p/ m0 ^* C
求入环的第一个节点, x# E u7 v& z# U! u/ Y
让慢指针重新指向链表的头节点,并让快慢指针同时每次只走一步
* |4 K2 W, g4 F' `7 ?* B0 ~" T$ F7 Ifaster:5--->6--->7) n6 ]* T; \( H; w
slower:1--->2--->3
; x/ K' b, _9 v/ Y0 G
! A7 @: N! A" _1 L7 w. W
继续走
7 {6 }' R/ Z7 [faster:7--->4/ i* p8 s1 V0 a, A2 O; X! y
slower:3--->43 w8 C% F* N' F$ K% v- S- L* ~
当他们同时到达节点 4 时,再次完美相遇。
2 t+ x" X4 @/ H [- q8 j
7 Q2 |2 i7 } X6 F3 E6 H
% @$ D2 U# ^0 j1 a% Y6 t' w
' D7 c- n& B" p
8 a D6 N) B8 a( Q* L7 i" Z3 v8 Q6 i3 O: y! b& P
因此,由上面的图可知,当判断链表有环(快慢指针相遇)之后,再让快(或者慢)指针重新指向链表头节点,另外一个指针仍保持指向不变,然后让快/慢指针同时走,且每次只走一步,当他们再次相遇时,此时相遇的节点就是入环的第一个节点。, v8 B# i8 }4 m& ]8 K
4 N5 X' p% C% @$ d8 @& N
$ Q% C3 r3 G, B* U& K5 b5 x
Show me the Code( t- Y4 \8 p0 S
- <font face="微软雅黑" size="3">// c 语言
, s# i' ]. Q0 C7 H* g - struct ListNode *detectCycle(struct ListNode *head) {
: ^1 s. m1 `9 s' w1 P! H, G - /* 记录是否有环 */
1 N1 W+ o9 y& T: H5 D - bool flag = false; 8 t" I% M! ~9 j0 t
- struct ListNode *slow = head, *fast = head;
3 Z7 [" V" D0 S& R! I0 W+ O - while (fast != NULL && fast->next != NULL) {0 B% m* c; i" i. u- V
- slow = slow->next;0 D |2 d6 K! q( K+ Z
- fast = fast->next->next;
2 {6 X9 K6 h d6 p( _- S. X - /* 有环则更新 flag 的值,并跳出循环寻找入环第一个节点 */
' h" v( L; R" A3 m7 z4 q! v - if (slow == fast) {$ {* e4 K4 j+ R# b4 t3 T
- flag = true;
$ l- w$ P0 v3 b% E: Y7 F2 z. q - break;
3 P9 ~, {$ w1 U5 ^! o3 \/ B2 V# | - }
3 i. V' U8 H- E2 q9 a. J - }
& v4 w; g2 w p: z - /* 无环则直接返回空 */
+ {1 Y; F$ z' i. @" E - if (flag == false) {! R1 o1 n8 I" @) n m4 A% r% A
- return NULL;2 _. Z+ x2 a: J; ?+ y+ G8 e: g
- }
/ O& U3 {" d. m' k2 U) }% t N7 ] - /* 慢指针重新指向链表头节点,快指针仍指向相遇节点 */$ j0 C; R4 @ k& b, M+ j& B7 t
- slow = head;
$ s j: R) \/ C; D - /* 快慢指针每次走一步,相遇节点即为入环第一个节点 */& Z4 h- G$ I/ e* n) B. Z% } |9 F1 R; K
- while (fast != slow) {" l! t3 `# k/ ~* ], h% y* {
- fast = fast->next;& ?3 @0 Y' a" A& d! b* V+ n
- slow = slow->next;" y9 e# j }8 o& ?& j
- }$ n/ o4 N& }2 ]* j
- return slow;; R& e( J7 c% w2 m( f
- }</font>
复制代码- <font face="微软雅黑" size="3">// go 语言
8 i; Z7 J( M; e! J# ^7 E5 a+ P# r - " ^. H5 Y, ?9 z
- func detectCycle(head *ListNode) *ListNode {
( [, l3 l6 ?$ b! A# C9 e9 r2 R" z1 v8 l - slow, fast := head, head
0 \. N8 E$ `" S; B" O+ \& @/ Z - for fast != nil && fast.Next != nil {
, ]* p0 y, s3 ?1 P: w$ h K4 E r; h x - fast, slow = fast.Next.Next, slow.Next6 \5 U _; u7 w7 U9 B+ W# X
- if fast == slow {
1 B7 n3 ^2 K# c" u0 A @- Y' k - break, I, _8 M, J4 X3 o2 s
- }, r/ p; t" w X T& {
- }7 L2 V$ U* Y' e+ J9 y
- if fast == nil || fast.Next == nil {4 X* j, ?' ^: h* E% _0 J3 P
- return nil, w: N3 a! {, h$ F( ~1 l
- }5 W. _. L! K( _0 U+ Q
- slow = head
8 s" }( o$ }& z3 n( \ - for fast != slow {8 d( Y3 g ?" N/ K8 A
- fast, slow = fast.Next, slow.Next* C5 Z+ x: j, G
- }
4 O# z! v% L; H! J# ? - return fast
5 X5 H o8 n' s% [7 u# W - }</font>
复制代码 % }- d+ G$ T! b+ U
/ u$ p2 s3 _; Q7 n |