给定一个链表,判断它是否有环。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