給定一個鏈表,判斷它是否有環。java
給出 -21->10->4->5, tail connects to node index 1,返回 true。node
這裏解釋下,題目的意思,在英文原題中,tail connects to node index 1 表示的是節點 5 還要連接回索引號 爲 1 的節點。git
一個典型的帶環鏈表以下:github
不要使用額外的空間算法
GitHub 的源代碼,請訪問下面的連接:數組
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)。數據結構
使用鏈表結構能夠克服數組鏈表須要預先知道數據大小的缺點,鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。可是鏈表失去了數組隨機讀取的優勢,同時鏈表因爲增長告終點的指針域,空間開銷比較大。spa
在計算機科學中,鏈表做爲一種基礎的數據結構能夠用來生成其它類型的數據結構。鏈表一般由一連串節點組成,每一個節點包含任意的實例數據(data fields)和一或兩個用來指向上一個/或下一個節點的位置的連接(」links」)。鏈表最明顯的好處就是,常規數組排列關聯項目的方式可能不一樣於這些數據項目在記憶體或磁盤上順序,數據的訪問每每要在不一樣的排列順序中轉換。而鏈表是一種自我指示數據類型,由於它包含指向另外一個相同類型的數據的指針(連接)。鏈表容許插入和移除表上任意位置上的節點,可是不容許隨機存取。鏈表有不少種不一樣的類型:單向鏈表,雙向鏈表以及循環鏈表。debug
要判斷一個鏈表中是否有循環,能夠藉助額外的存儲空間,將鏈表插入到 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