這個問題遇到過很多次了,但是並不是說每次都很清楚。所以這次用golang的代碼來實現一遍,加深理解與記憶。
如果一個鏈表上不存在環,那麼一定能夠遍歷完鏈表中所有的Node節點;如果存在環,那麼可以想象成存在一個圓形操場。在一個圓裏面,如果有兩個人,行走的速度不一樣,那麼一定會有相遇的那一刻。最佳的解法便是基於此想法,當然也有其他的解法,比如說將每個遍歷的節點放進一個set裏面,如果存在過,那麼就存在環。
數據結構:
type Node struct {
index int
next *Node
}
構建鏈表,隨機選擇是否生成環
func createNodeList() *Node{
rand.Seed(time.Now().Unix())
length := rand.Intn(100) + 1
head := new(Node)
var tail *Node
// 構建鏈表
for idx ,curNode := 1, head; idx <= length; idx ++ {
curNode.index = idx
if idx != length {
curNode.next = new(Node)
curNode = curNode.next
} else {
tail = curNode
}
}
buildRing := rand.Intn(2)
if buildRing == 0 {
// 構建環
enterRingIndex := rand.Intn(length) + 1
enterRingNode := head
for idx:= 2; idx <= enterRingIndex; idx ++{
enterRingNode = enterRingNode.next
}
tail.next = enterRingNode
fmt.Println("length : ", length, ", enter ring index : ", enterRingIndex, ", Ring length : ", length - enterRingIndex + 1)
} else {
fmt.Println("don't build ring")
}
return head
}
輸出鏈表,用於debug
func outputNodeList(head * Node) {
for head != nil {
fmt.Print(head.index, " ")
head = head.next
}
}
使用兩個下標,用於抽象表示圓形操場中的兩個人,leader走前面,每次走兩步,follower走後面,每次走一步,如果相遇,返回相遇時的Node節點。
func nodeListExistRing(head * Node) *Node {
leader, follower := head, head
for leader != nil && follower != nil {
leader = leader.next
if leader == nil {
return nil
}
leader = leader.next
follower = follower.next
if (leader == follower) {
return leader
}
}
return nil
}
如果鏈表中存在環,那麼環的長度就等於:follower再走一圈時走過的步長。
func calRingLength(meetNode * Node) int {
curNode := meetNode
curNode = curNode.next
count := 1
for ; curNode != meetNode; count ++ {
curNode = curNode.next;
}
return count
}
如果鏈表存在環,那麼入環點等於將leader指向head後,並以每次一步的速度往前,直到與follower再次相遇,此時的節點便是入環點。
如何證明上面的步驟是正確的?
也就是說,入環時走過的路程,等於S2加上(S1+S2)的整數倍,如果leader步長變成1,並且從A點出發到達B點,那麼走過的路程D,一定會等於S2+(n-1)(S1 + S2),也就是說一定會在B點相遇。B點即入環點。
func calEnterRingIndex(meetNode * Node, head * Node) int {
var enterRingIndex = 0;
for head != meetNode {
head = head.next
meetNode = meetNode.next
enterRingIndex ++
}
return enterRingIndex + 1
}
啓動類,將前面過程,按照想要的邏輯拼裝在一起。
func main() {
head := createNodeList()
meetNode := nodeListExistRing(head)
if meetNode == nil {
outputNodeList(head)
} else {
fmt.Println("RING EXIST!", ", Ring length: ", calRingLength(meetNode), ", Enter Ring Index : ", calEnterRingIndex(meetNode, head))
}
}
附錄:
文中公式、圖及推算過程的latex源碼:
\begin{tikzpicture}
% \draw[dashed, file=blue!20]{0, 0} circle(0.5cm);
\draw(0, 0) circle (1.5cm);
\coordinate[label = above:$O$] (O) at (0, 0);
\node at(0, 0)[circle,fill,inner sep=1pt]{};
\coordinate[label = right:$C$] (C) at (-30:1.5);
\node at(C)[circle,fill,inner sep=1pt]{};
\coordinate[label = below right:$S_1$] (S1) at (-60:1.5);
\coordinate[label = left:$S_2$] (S2) at (120:1.5);
\coordinate[label = below: $B$] (B) at (-90:1.5);
\node at(B)[circle,fill,inner sep=1pt]{};
\coordinate[label = below: $A$] (A) at (-7, -1.5);
\node at(A)[circle,fill,inner sep=1pt]{};
\draw (A) -- (B) node[above, midway] (line) {$D$};
\end{tikzpicture}
設n爲leader與follower在相遇前,比follower多走的圈數,那麼便存在
\[ 2(D + S_1) = D + S_1 + n * (S_1 + S_2) \]
化簡得:
\[ 2*D + 2*S_1 = D + S_1 + n * (S_1 + S_2) \]
\[ D + S_1 = n * (S_1 + S_2) \]
\[ D = n * (S_1 + S_2) - S_1 \]
\[ D = (n - 1) * (S_1 + S_2) + S_1 + S_2 - S_1 \]
即:
\[ D = (n - 1) * (S_1 + S_2) + S_2 \]
參考:https://zouyu4524.github.io/blog/tikz-plot/