求一个链表是否有环、环的长度、环的入口(Java实现)

求一个链表是否有环是比较常问的面试题,下面我们来看下三种常见的思路,假定节点结构为:

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> hs = new HashSet<node>();

     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,Integer> map = new HashMap<node,Integer>();

     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;

}

如果你有更好的思路欢迎在评论区留言,欢迎点赞转发,感谢阅读。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值