求一个链表是否有环是比较常问的面试题,下面我们来看下三种常见的思路,假定节点结构为:
static class node{
Object value;
node next;
node(Object o){
this.value = o;
}
}
头结点也已经声明好了:
static node head;
思路1:用两个指针指向头结点,一个指针每次向前走一步,然后另一个指针从头结点出发直到前面的节点为止,如果两个节点的前进的步数不一致,则说明链表有环。如链表
A->B->C->D->E->B,当先走的节点走到第二个B的时候共走了5步,另外一个节点从头结点出发,在B点相遇时只走了两步,即可得出链表有环的结论。平均的时间复杂度为O(n^2),空间复杂度为O(1)。代码如下:
public static boolean circle1() {
node p1 = head,p2 = head;
int step1 = 0,step2 = 0;
for(;p2!=null;step2++) {
p2 = p2.next;
for(;p1!=p2;step1++)
p1 = p1.next;
if(step1!=step2)
return true;
p1 = head;
step1 = 0;
}
return false;
}
思路2:逐个尝试的方法时间复杂度过高,可以用一个HashSet将链表节点逐个加入,如果存在环时则必然导致put()失败。平均时间复杂度为O(n),空间复杂度为O(n)。代码如下:
public static boolean circle2() {
HashSet
node p = head;
while(p!=null) {
if(!hs.add(p))
return true;
p = p.next;
}
return false;
}
思路3:HashSet与思路1类似,只不过利用空间换取时间。有一种比较巧的方法时使用快慢指针,慢指针每次走一步,快指针每次走两步,当链表有环时,快指针会一直在环中,当慢指针进入环后,快指针最后一定会追上慢指针,就像环形跑道上的两个人一样,如果速度不一致那么最后快者那个会领先一圈追上慢者,当两指针相遇时则说明链表存在环。平均时间复杂度为O(n),空间复杂度为O(1)。代码如下:
public static boolean circle3() {
if(head==null||head.next==null)
return false;
node slow = head,fast = head;
while(fast!=null&&fast.next!=null) {
slow = slow.next;
fast = fast.next.next;
if(slow==fast)
return true;
}
return false;
}
求环的长度:
求环的长度也很简单,同样利用快慢指针的思路,两个指针同时从环上的同一点出发,当它们再次相遇时,慢指针走了s步,快指针走了2s步,它们依然在起点处相遇,环的长度即为s。平均时间复杂度为O(n),空间复杂度为O(1),代码如下:
public static int perimeter() {
node slow = head.next,fast = head.next.next;
while(slow!=fast) {
slow = slow.next;
fast = fast.next.next;
}//先让两个指针指向环上的同一点
int perimeter = 1;
slow = slow.next;fast = fast.next.next;
while(slow!=fast) {
slow = slow.next;
fast = fast.next.next;
perimeter++;
}
return perimeter;
}
求环的入口:
思路1:快慢指针虽然求是否有环以及环的长度很方便,但并不方便求出环的入口。这是可以逐个尝试的方法,当两个节点相遇而步数不一致时,较短的步数即为从头结点到入口的距离。平均时间复杂度为O(n^2),空间复杂度为O(1),代码如下:
public static int intersection() {
node p1 = head,p2 = head;
intstep1 = 0,step2 = 0;
for(;;p1 = head,step1 = 0) {
p2 = p2.next;
step2++;
for(;p1!=p2;step1++,p1 = p1.next);
if(step1!=step2)
return step1;
}
}
逐个尝试的方法找入口依然很慢,上面我们说过HashSet其实和逐个尝试的思路是一致的,只不过利用了Set作为缓存而已。这里我们可以用一个HashMap,以节点为键,节点下标为值,当用put()放入重复节点时,就会返回该节点的下标。平均时间复杂度和空间复杂度均为为O(n),代码如下:
publi cstatic int intersection2() {
HashMap
node p = head;
int step = 0;
Integer tmp;
while(p!=null) {
tmp = map.put(p,step++);
if(tmp!=null)
return tmp;
p = p.next;
}
return step;
}
如果你有更好的思路欢迎在评论区留言,欢迎点赞转发,感谢阅读。