1. 程式人生 > >[LintCode] Linked List Cycle(帶環連結串列)

[LintCode] Linked List Cycle(帶環連結串列)

描述

給定一個連結串列,判斷它是否有環。

樣例

給出 -21->10->4->5, tail connects to node index 1,返回 true。

這裡解釋下,題目的意思,在英文原題中,tail connects to node index 1 表示的是節點 5 還要連結回索引號 為 1 的節點。

一個典型的帶環連結串列如下:

挑戰

不要使用額外的空間

程式碼

GitHub 的原始碼,請訪問下面的連結:

https://github.com/cwiki-us/java-tutorial/blob/master/src/test/java/com/ossez/lang/tutorial/tests/lintcode/LintCode0102HasCycleTest.java

package com.ossez.lang.tutorial.tests.lintcode;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ossez.lang.tutorial.models.ListNode;

/**
 * <p>
 * 102
 * <ul>
 * <li>@see <a href=
 * "https://www.cwiki.us/display/ITCLASSIFICATION/Linked+List+Cycle">https://www.cwiki.us/display/ITCLASSIFICATION/Linked+List+Cycle</a>
 * <li>@see<a href= "https://www.lintcode.com/problem/linked-list-cycle/">https://www.lintcode.com/problem/linked-list-cycle/</a>
 * </ul>
 * </p>
 * 
 * @author YuCheng
 *
 */
public class LintCode0102HasCycleTest {

  private final static Logger logger = LoggerFactory.getLogger(LintCode0102HasCycleTest.class);

  /**
   * 
   */
  @Test
  public void testMain() {
    logger.debug("BEGIN");
    // INIT LINKED LIST
    ListNode head = new ListNode(1);
    head.next = new ListNode(2);
    head.next.next = new ListNode(3);
    head.next.next.next = new ListNode(4);

    // CREATE A LOOP
    head.next.next.next.next = head.next.next.next;

    boolean retResult = false;

    // LIKED LIST MAY NULL:
    if (!(head == null || head.next == null)) {
      ListNode s = head;
      ListNode f = head.next;

      while (f.next != null && f.next.next != null) {

        s = s.next;
        f = f.next.next;

        if (f == s) {
          retResult = true;
          break;
        }
      }
    }

    System.out.println(retResult);

  }
}

 

 

 

點評

連結串列(Linked list)是一種常見的基礎資料結構,是一種線性表,但是並不會按線性的順序儲存資料,而是在每一個節點裡存到下一個節點的指標(Pointer)。由於不必須按順序儲存,連結串列在插入的時候可以達到O(1)的複雜度,比另一種線性表順序錶快得多,但是查詢一個節點或者訪問特定編號的節點則需要O(n)的時間,而順序表相應的時間複雜度分別是O(logn)和O(1)。

使用連結串列結構可以克服陣列連結串列需要預先知道資料大小的缺點,連結串列結構可以充分利用計算機記憶體空間,實現靈活的記憶體動態管理。但是連結串列失去了陣列隨機讀取的優點,同時連結串列由於增加了結點的指標域,空間開銷比較大。

在電腦科學中,連結串列作為一種基礎的資料結構可以用來生成其它型別的資料結構。連結串列通常由一連串節點組成,每個節點包含任意的例項資料(data fields)和一或兩個用來指向上一個/或下一個節點的位置的連結(”links”)。連結串列最明顯的好處就是,常規陣列排列關聯專案的方式可能不同於這些資料專案在記憶體或磁碟上順序,資料的訪問往往要在不同的排列順序中轉換。而連結串列是一種自我指示資料型別,因為它包含指向另一個相同型別的資料的指標(連結)。連結串列允許插入和移除表上任意位置上的節點,但是不允許隨機存取。連結串列有很多種不同的型別:單向連結串列,雙向連結串列以及迴圈連結串列。

要判斷一個連結串列中是否有迴圈,可以藉助額外的儲存空間,將連結串列插入到 HashSet 中。建立一個以節點ID為鍵的HashSet集合,用來儲存曾經遍歷過的節點。然後同樣是從頭節點開始,依次遍歷單鏈表的每一個節點。每遍歷到一個新節點,就用新節點和HashSet集合當中儲存的節點作比較,如果發現HashSet當中存在相同節點ID,則說明連結串列有環,如果HashSet當中不存在相同的節點ID,就把這個新節點ID存入HashSet,之後進入下一節點,繼續重複剛才的操作。

這個方法在流程上和方法一類似,本質的區別是使用了HashSet作為額外的快取。

假設從連結串列頭節點到入環點的距離是D,連結串列的環長是S。而每一次HashSet查詢元素的時間複雜度是O(1), 所以總體的時間複雜度是1*(D+S)=D+S,可以簡單理解為O(N)。而演算法的空間複雜度還是D+S-1,可以簡單地理解成O(N)。

也可以採用指標的方式。

首先建立兩個指標1和2(在java裡就是兩個物件引用),同時指向這個連結串列的頭節點。然後開始一個大迴圈,在迴圈體中,讓指標1每次向下移動一個節點,讓指標2每次向下移動兩個節點,然後比較兩個指標指向的節點是否相同。如果相同,則判斷出連結串列有環,如果不同,則繼續下一次迴圈。

例如連結串列A->B->C->D->B->C->D,兩個指標最初都指向節點A,進入第一輪迴圈,指標1移動到了節點B,指標2移動到了C。第二輪迴圈,指標1移動到了節點C,指標2移動到了節點B。第三輪迴圈,指標1移動到了節點D,指標2移動到了節點D,此時兩指標指向同一節點,判斷出連結串列有環。

此方法也可以用一個更生動的例子來形容:在一個環形跑道上,兩個運動員在同一地點起跑,一個運動員速度快,一個運動員速度慢。當兩人跑了一段時間,速度快的運動員必然會從速度慢的運動員身後再次追上並超過,原因很簡單,因為跑道是環形的。

https://www.cwiki.us/display/ITCLASSIFICATION/Linked+List+Cycle