1. 程式人生 > >Floyd判圈演算法 leetcode 202題Happy Number

Floyd判圈演算法 leetcode 202題Happy Number

今天,日常刷leetcode,遇到202問題如下:

Write an algorithm to determine if a number is "happy".

A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.

Example: 19 is a happy number

  • 12 + 92 = 82
  • 82 + 22 = 68
  • 62 + 82 = 100
  • 12 + 02 + 02 = 1

Credits:
Special thanks to @mithmatt and @ts for adding this problem and creating all test cases.

題目大意是輸入一個正數,不斷求其各位數字的平方和,如果最後得到的平方和為1則為“happy number”,若計算出的平方和各中間狀態出現無限迴圈,則這個數不是“happy number”。

做這個題的主流思路是利用迴圈不斷求所給正數的各位數字平方和,並利用Set記錄每個中間結果,利用兩個標準來控制迴圈的進行:

1. 所求得的各位數字平方和是否為1,若為1,返回結果;

2. 所求得的平方和是否與Set中已有的某個狀態一致,如果出現相同的平方和,說明出現了迴圈,返回結果;

具體實現程式碼如下:


這個思路是比較容易理解的, 利用Set的特性來判斷是否出現了迴圈。

但是這個題有更好的解決方法,利用了 Floyed判圈演算法,程式碼如下:


程式碼比較容易懂,就是找到求平方和過程中出現閉環的部分。我不明白的地方在於,假設輸入的正數狀態為編號為1,後續n次求得的平方和為2到n,那麼第一次slow和fast比較的是狀態2和狀態3,第二次為3和5,第三次為4和7,第四次為5和9,即每次比較,比較的兩個狀態為n和2n-1,我不明白為什麼要這樣比較。

查閱了維基百科對該演算法的解釋如下:

Floyd判圈演算法(Floyd Cycle Detection Algorithm),又稱龜兔賽跑演算法(Tortoise and Hare Algorithm),是一個可以在有限狀態機迭代函式或者連結串列上判斷是否存在,求出該環的起點與長度的演算法。該演算法據高德納稱由美國科學家羅伯特·弗洛伊德發明,但這一演算法並沒有出現在羅伯特·弗洛伊德公開發表的著作中[1]

如果有限狀態機、迭代函式或者連結串列上存在環,那麼在某個環上以不同速度前進的2個指標必定會在某個時刻相遇。同時顯然地,如果從同一個起點(即使這個起點不在某個環上)同時開始以不同速度前進的2個指標最終相遇,那麼可以判定存在一個環,且可以求出2者相遇處所在的環的起點與長度。

具體演算法描述:

如果有限狀態機、迭代函式或者連結串列存在環,那麼一定存在一個起點可以到達某個環的某處(這個起點也可以在某個環上)。

初始狀態下,假設已知某個起點節點為節點S。現設兩個指標t和h,將它們均指向S。

接著,同時讓t和h往前推進,但是二者的速度不同:t每前進1步,h前進2步。只要二者都可以前進而且沒有相遇,就如此保持二者的推進。當h無法前進,即到達某個沒有後繼的節點時,就可以確定從S出發不會遇到環。反之當t與h再次相遇時,就可以確定從S出發一定會進入某個環,設其為環C。

如果確定了存在某個環,就可以求此環的起點與長度。

上述演算法剛判斷出存在環C時,顯然t和h位於同一節點,設其為節點M。顯然,僅需令h不動,而t不斷推進,最終又會返回節點M,統計這一次t推進的步數,顯然這就是環C的長度。

為了求出環C的起點,只要令h仍均位於節點M,而令t返回起點節點S,此時h與t之間距為環C長度的整數倍。隨後,同時讓t和h往前推進,且保持二者的速度相同:t每前進1步,h前進1步。持續該過程直至t與h再一次相遇,設此次相遇時位於同一節點P,則節點P即為從節點S出發所到達的環C的第一個節點,即環C的一個起點。

在另一篇部落格上,我找到了相關公式的推導:http://blog.csdn.net/javasus/article/details/50015687


通過對原理的理解,之所以slow選用步長1,fast選用步長2,是為了在出現環的情況下能讓fast趕上slow,在這個題只需要判斷是否有環,那麼只要fast的步長比slow的步長長即可,為了驗證我把程式碼改為:


也通過了。

Floyd演算法還有很多點值得去探究,在後續用到的時候再研究。