閱讀229 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Cracking the Coding Interview:: 尋找有環鏈表的環路起始節點

給定一個有環鏈表,實現一個算法返回環路的開頭節點。 這個問題是由經典麵試題-檢測鏈表是否存在環路演變而來。這個問題也是編程之美的判斷兩個鏈表是否相交的擴展問題。

首先回顧一下編程之美的問題。 由於如果兩個鏈表如果相交,那麼交點之後node都是共享(地址相同)的,因此最簡單暴力的方法就是兩個for循環,判斷該鏈表的node是否屬於另外一個鏈表。但是這個算法複雜度是O(length1 * length2)。如果鏈表較長,這個複雜度有點高了。

當然也可以遍曆其中某個鏈表,將node的地址存儲hash table中;然後接下來遍曆另外一個鏈表,查找node是否在這個hash table中。這樣的話時間複雜度就是O(length1 + length2)。但是需要額外的O(length1)的空間。在C++ STL Java等主流語言中,hash table都有很好的實現,因此編程也比較簡單。

注意我們這個方法是認定如果兩個鏈表相交,那麼肯定交點以後的點都是共享的,包括最後一個點。那麼找到鏈表的最後一個節點,如果節點相同,那麼就相交。當然了,以上算法需要處理有環存在的情況。

本文的問題是尋找有環鏈表的開頭節點,那麼如何判斷一個鏈表是否有環?

我們可以用快慢遊標的方法。具體來說,就是使用兩個遊標遍曆鏈表,其中快遊標(快指針,fastRunner)每次經過兩個node,慢遊標(慢指針,slowRunner)每次經過一個node。那麼如果有環,那麼這兩個遊標肯定會相遇。使用反證法可以推理這個推論是正確的,假如fastRunner正好經過了slowRunner,而沒有相遇,那麼上一部,就是fastRunner後退兩步,slowRunner後退一步,兩個runner必定相遇。如果現在slowRunner正好領先一步fastRunner,那麼下一步二者相遇。

bool checkLinkRing(const Node *head)
{
  if( head == nullptr )
    return false;

  auto fastRunner = head;
  auto slowRunner = head;
  while( fastRunner->next != nullptr && fastRunner->next->next != nullptr )
  {
    fastRunner = fastRunner->next->next;
    slowRunner = slowRunner->next;
    if( fastRunner == slowRunner )
      return true;
  }

  return false;
}
接下來的問題,如何找到環的起始點?看下圖,假設交點在K處

那麼在slowRunner走了K步到達交點,那麼此時fastRunner走了2K步到達黃圈處。那麼fastRunner距離追上slowRunner還有RingSize - K步。 注意,隻能順時針前進。fastRunner相對slowRunner的速度是每步經過一個node(fastRunner雖然每次經過2個node,但是slowRunner也向前走了一個node,因此每步二者的距離僅僅減少一個node),因此在走了RingSize - K時相遇。也就是slowRunner在進入環後,再走RingSize - K 步後二者相遇於綠處。此時,綠點順時針距離交點有RingSize - ( RingSize - K) = K。注意括號內的RingSize - K是slowRunner走的。

關鍵問題出來了,在二者相遇的點,距離交點也是K: 與head到交點的距離相同。因此,我們可以調整兩個遊標,使slowRunnder從頭開始,fastRunnder從相遇點開始,二者以相同的速度前行,必然在相交點再次相遇!

auto findRingCrossPoint(const Node *head) ->decltype(head)
{
  if( head == nullptr )
    return nullptr;

  auto fastRunner = head;// C++11, fastRunner is const Node *
  auto slowRunner = head;
  while( fastRunner->next != nullptr && fastRunner->next->next != nullptr )
  {
    fastRunner = fastRunner->next->next;
    slowRunner = slowRunner->next;
    if( fastRunner == slowRunner )
      break;
  }
  
  // in case that there is no ring.
  if( fastRunner != slowRunner )
    return nullptr;

  slowRunner = head;
  while( true )
  {
    fastRunner = fastRunner->next;
    slowRunner = slowRunner->next;
    if( fastRunner == slowRunner )
    {
      break;
    }
  }

  return fastRunner;
}


最後更新:2017-04-03 12:53:57

  上一篇:go JSP中獲取各種路徑的方法
  下一篇:go 靜態資源緩存控製grunt插件