之前我的写文章《链表在STM32中的应用》2 K( [& w& c) Z
! E/ b8 L& a' x& ~. ?( c' Z. o" ~" h% m% |' O9 g
不论在面试或者刷题过程中,很大概率都会遇到环形链表这种类型的题目,例如 leetcode 141. 环形链表 以及 leetcode 142. 环形链表 II 等,本文主要介绍通过快慢指针法来解决此类题型,以供大家参考。
9 g- P: f: A. I# o. w/ m* K3 u0 Y9 D7 C# B9 M% f/ ^/ R
F' X' Q; P! K* G) t
环形链表0 }/ v& z5 ] _# m" q: R# ?
环形链表大致样子如下图所示:/ [4 \( a) q' t. l; N+ |: p& b
$ F( j1 D# s+ I
( [% [; }3 T5 \& f" F! ?
8 f3 U! g% ]1 r- k9 x2 o* `& O' A2 V快慢指针法 i+ k6 _; v9 K, g
判断链表是否是环形链表,一般通过快慢指针法。1 L! ?+ z# w ]* x
. u! X p5 _+ Z. w# R( a3 a+ }" L
* A) |5 N' b: z: m$ p! U操作步骤
; `$ D8 s3 t* g& U1 `4 v一、分别定义两个均指向头节点的指针(fast/slow);+ @+ A$ F% V, }. a# C& _
二、快指针每次走两步,慢指针每次走一步;
. H$ }! J- |: k& }# C; e6 L三、如果链表存在环,则快慢指针一定会在环中相遇。2 L# O, k! {- V* k( i
7 t* A. h2 s; Z) z2 H8 |+ A: t: l* v; h7 X7 T! g& C2 Y
如下图示,快慢指针行走的轨迹如下:. V2 T t1 n( f& h7 o( H
faster:1--->3--->5--->7- V1 |5 M6 k& q1 s- \, ^
slower:1--->2--->3--->4; w+ @0 k% @( l6 h6 m3 {2 o
1 b Z2 d: `2 V& G; `faster:7--->57 w, j5 ~1 G+ c7 @* K, W# A6 Q7 t
slower:4--->5
& o8 V2 W7 l$ V, D当他们同时到达节点 5 时,完美相遇。: t% V( q; b# v# R5 W- `( O
" [- F9 o0 f" c. ?
0 s. s0 W1 ?/ q" C8 v2 H9 x9 i
0 e9 \3 i( M1 L6 r! r举栗1:判断链表是否有环
1 ?. A1 W2 [4 c6 G7 f' y: i
8 k$ E$ i4 y; }! `# u
题目 2 H* X6 h q1 K
Show me the Code
6 e: M/ E4 `" [$ Z$ m' S- <font face="微软雅黑" size="3">// C 语言
" v1 Z5 |- n% S$ F; M1 R: A - bool hasCycle(struct ListNode *head) {
& h6 Z' ~) g- @0 ` - /* 特判 */
/ z1 y0 j, Q2 a% g9 `! ] - if (head == NULL || head->next == NULL) {
! h% W" D6 [1 c) @8 P' } - return false;$ v+ d2 t; {0 _. D l3 ]
- }
! d2 L. x9 v) I+ M9 d9 B1 Q - struct ListNode *slow = head, *fast = head;% m5 X6 f" j) y
- /* fast指针走过链表末端,说明链表无环*/
) z/ ^+ K0 C5 Q! c" M - while((fast != NULL) && (fast->next != NULL)) {
; `) t( |$ L0 o0 g$ U - slow = slow->next;# K* [8 v2 m- F; U+ _- \4 Y" H
- fast = fast->next->next;
) A* }& b8 |* X$ g$ ` - if(slow == fast) {! F8 U, h& D5 v# P. H! t1 W7 Q/ W) G x/ ~5 C
- return true;
9 @0 X5 y. G/ [ - }
+ c- } ]# n+ O0 Q4 e2 ?, I - }3 g6 y$ e+ m1 K! p. E/ n
- return false;% A2 { D3 W: ^ U0 n! ` \& D
- }</font>
复制代码- <font face="微软雅黑" size="3"># python3
5 K, s5 D- ?2 N0 E$ {/ [& S, r - class Solution:) R% {5 v( U6 ]& V
- def hasCycle(self, head: ListNode) -> bool:
1 f5 z2 G5 S4 D( P+ \ - if not head or not head.next: return False * g- i, ^* m! h+ Q! x
- slow, fast = head, head
, L" K6 x$ L- h) l9 W8 ]0 v) F - while fast and fast.next:( l4 I2 Z" H" H3 g
- slow = slow.next, t# m7 H+ m. z. \
- fast = fast.next.next , a- N" g, R1 `8 E- H* `9 X% h0 o
- if slow is fast: return True# H1 R3 }. [2 ^+ q2 S
- return False </font>
复制代码 举例2:求入环的第一个节点
% o2 A3 t' M; _* L4 y- D5 a; \+ \
" [3 A4 q) ~0 K: R& C
题目 . J, r9 t- Y: |2 h" }, c. x
5 h' q- ]9 m: l% Z% X% z& p思路
; d/ i4 k& Y( o# B' i本题除了需要判断链表是否有环外,如果有环还要求入环的第一个节点,因此是上一个题目的升级版本,还是以快慢指针中举例的那个链表作为示例,下图将描述当链表有环的时候,如何求出入环的第一个节点,见下图示:
/ |/ M* m3 R* W$ T6 o已判断链表有环
7 M* _" r/ ?( ~! l" q4 b. E k8 J/ j
0 N1 _9 [# j# a. Z) R求入环的第一个节点
4 q1 p/ H9 N) j3 l; e6 {' U, @让慢指针重新指向链表的头节点,并让快慢指针同时每次只走一步
& b9 d @- a. Q$ ~; I, a& Z: Kfaster:5--->6--->7
; ~5 V! n! @( qslower:1--->2--->3
/ c6 V+ \8 _2 a5 h3 @
\: z$ G1 t. y% O% ?3 Y o1 b继续走
6 i5 F8 b# _( u# U# ffaster:7--->4
6 ~# C4 M3 r g; J. [3 Y0 i% fslower:3--->4
: u8 e$ l- u/ O当他们同时到达节点 4 时,再次完美相遇。
; Y0 e# C, `0 u$ a
- o6 V$ c1 T8 m! N' D' a. h4 a
* X: n( F+ z, w! f" D3 s; ~" F4 v) o! V" o
( \9 N# D- }. c: A7 F% ^
0 H d0 t" ~! ^7 o& M因此,由上面的图可知,当判断链表有环(快慢指针相遇)之后,再让快(或者慢)指针重新指向链表头节点,另外一个指针仍保持指向不变,然后让快/慢指针同时走,且每次只走一步,当他们再次相遇时,此时相遇的节点就是入环的第一个节点。
7 E! {0 Q+ {, X! u0 g6 H' f% A9 `. m; D- [
1 }. q7 G, R0 s8 t+ G2 g" U7 N2 UShow me the Code
1 T! M. ~8 F1 U" r9 l U' D1 Q0 ^; i- <font face="微软雅黑" size="3">// c 语言6 p/ |1 q) B: p0 q% b
- struct ListNode *detectCycle(struct ListNode *head) {8 d3 A2 J* C. j4 z% A6 p' V
- /* 记录是否有环 */
3 ^2 d! h; N) |! @ - bool flag = false;
( Q, i0 j* S& G! w) v - struct ListNode *slow = head, *fast = head;( j- e0 {+ h& r/ O4 L
- while (fast != NULL && fast->next != NULL) {' Q5 G8 L8 c2 ^% Q, ]. W: i$ J
- slow = slow->next;9 @+ h o1 O* r$ B6 C- G
- fast = fast->next->next;$ F R+ H3 t* ^5 F+ [# m( }9 K
- /* 有环则更新 flag 的值,并跳出循环寻找入环第一个节点 */
/ ^9 R7 j$ `1 k" q9 R - if (slow == fast) {8 x# d3 {9 S# [: r: W
- flag = true;
X# ?1 h, K: U1 ~/ |: ^9 d - break;
3 B3 P1 l0 W! _# n( }) F3 V* F - }
6 o9 g) g) I4 L& ` o9 I - }9 Y1 @& j5 B% Y1 k4 p/ ^* S/ z
- /* 无环则直接返回空 */
' ]5 |; A. g# z. N% p+ ~2 e - if (flag == false) {; B$ [& `; A" S% H, r3 @- Q
- return NULL;
4 f- h; f; R" s" K - }
# j7 a# a* ?1 k0 M - /* 慢指针重新指向链表头节点,快指针仍指向相遇节点 */ b% Y0 S y; K0 [
- slow = head;# L( p4 @( M' Z9 O3 S: D
- /* 快慢指针每次走一步,相遇节点即为入环第一个节点 */
: y% R, W$ ]5 U: a - while (fast != slow) { U/ k, R0 v. u. `3 H7 q
- fast = fast->next;6 k( ~! f% l- G4 {
- slow = slow->next;1 D0 v K5 V: k4 I4 f+ P
- }
, t5 p& k w& [3 N - return slow;* Y; H# K; n; V& O- M; J1 D# f
- }</font>
复制代码- <font face="微软雅黑" size="3">// go 语言0 ~$ I! b9 X2 m. H# {% U
- " O a; V0 b5 z% x, m. q2 M2 ?
- func detectCycle(head *ListNode) *ListNode {
& D; O- U& r4 g( J J( z - slow, fast := head, head
& \* O9 C( O3 [1 h/ _ - for fast != nil && fast.Next != nil {
. @) v. K* J% A1 \$ s+ x! t- m - fast, slow = fast.Next.Next, slow.Next
9 i; ]% {+ ~2 L9 @* m2 v - if fast == slow {+ S1 U- _1 _6 k$ w: X
- break
( M' H9 i; ^8 Q2 ` - }
5 D4 v6 Z: w& }. A: d - }
6 m* I+ M0 q9 S - if fast == nil || fast.Next == nil {- E; _6 _: @+ i0 j
- return nil
3 H4 c" K; t$ F4 u- N - }
0 Q, a" c! H; x8 y- I- e7 O& F! W - slow = head$ W3 n. J* R* U1 _
- for fast != slow {
9 w( r3 x. p. z9 D' V9 j4 [ - fast, slow = fast.Next, slow.Next
0 U6 @; |, b& e2 q1 W: ~ U - }
1 ]2 ]. |$ l2 |( b - return fast5 d; [% [3 F F/ a* w# T
- }</font>
复制代码 2 s! Z, u' v4 }0 o, O
- {8 p& I* p8 B; l) z' J3 g5 M
|