Java算法——6检测一个较大的单链表是否有环(蛮力法、快慢指针遍历法)

题目描述

单链表有环指的是单链表中某个结点的next域指向链表中在它之前的某一个结点,这样在链表的尾部形成了一个环形结构。如何判断单链表是否有环存在?

方法一:蛮力法

定义一个HashSet用来存放结点的引用,并将其初始化为空,从链表的头结点开始向后遍历,没遍历到一个结点就判断HashSet中是否有这个结点的引用。如果没有,说明这个结点是第一次访问,还没有形成环,那么将这个结点的引用添加到HashSet中去。如果在HashSet中找到了同样的结点,那么说明这个结点已经被访问过了,于是形成了环。这种方法时间复杂度为O(N),空间复杂度为O(N).

方法二:快慢指针遍历法

定义两个指针fast(快)和slow(慢),两者的初始值都指向链表头,指针slow每次前进一步,指针fast每次前进两步。两个指针同时向前移动,快指针每移动一次都要和慢指针比较,如果快指针等于慢指针,就证明这个链表是带环的单向链表,否则证明这个链表是不带环的循环链表。

引申:如果链表存在环,那么如何找到环的入口点?

当链表有环时,如果知道环的入口点,那么在需要遍历链表或释放链表所占的空间时方法将会非常简单,查找链表环入口的思路如下:

如果单链表有环,按照方法二的思路,当走得快的指针fast与走得慢的指针slow相遇时,slow指针肯定没有遍历完链表,而fast指针已经在环内循环了n圈(n>=1)。如果slow指针走了s步,则fast指针走了2s步(fast步数还等于s加上在环内多转的n圈),假设环长为r,则满足以下关系表达式:

2s = s + nr

由此可得:s = nr

设整个链表长为L,入口环与相遇点距离为x,起点到环入口点的距离为a。则满足以下关系表达式:

a + x = nr
a + x = (n-1)r + r =(n-1)r + L - a
a = (n-1)r + (L - a - x)

(L - a - x)为相遇点到环入口点的距离,从链表头到环入口点的距离=(n-1)环长+相遇点到环入口的长度,于是从链表头与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇的第一点为环入口点。
在这里插入图片描述

class LNode {
    /**
     * 数据域
     */
    int data;
    /**
     * 下一个结点的引用
     */
    LNode next;
}
public class Test6 {
    public static LNode constructList(){
       /**
        * @Author: JavaRecord
        * @Description:构造链表
        * @Param []
        * @Return linked.list1.LNode
        * @Date 2020/8/11
        * @Time 10:22
        */
        int i = 1;
        LNode head = new LNode();
        head.next = null;
        LNode tmp = null;
        LNode cur = head;
        //构造第一个链表
        for(;i<8;i++){
            tmp = new LNode();
            tmp.data = i;
            tmp.next = null;
            cur.next = tmp;
            cur = tmp;
        }
        cur.next = head.next.next.next;
        return head;
    }

    public static LNode isLoop(LNode head){
        /**
         * @Author: JavaRecord
         * @Description:判断单链表是否有环
         * @Param [head]
         * @Return linked.list1.LNode
         * @Date 2020/8/11
         * @Time 10:23
         */
        if(head==null || head.next==null){
            return null;
        }
        LNode slow = head.next;
        LNode fast = head.next;
        while(fast!=null&&fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow==fast){
                return slow;
            }
        }
        return null;
    }

    public static LNode findLoopNode(LNode head,LNode meetNode){
        /**
         * @Author: JavaRecord
         * @Description:找出环的入口点
         * @Param [head, meetNode]
         * @Return linked.list1.LNode
         * @Date 2020/8/11
         * @Time 10:30
         */
        LNode first = head.next;
        LNode second = meetNode;
        while(first!=second){
            first = first.next;
            second = second.next;
        }
        return first;
    }

    public static void main(String[] args){
        //头结点
        LNode head = constructList();
        LNode meetNode = isLoop(head);
        LNode loopNode = null;
        if(meetNode!=null){
            System.out.println("有环");
            loopNode = findLoopNode(head,meetNode);
            System.out.println("环的入口点为:"+loopNode.data);
        }else {
            System.out.println("无环");
        }
    }
}

程序运行结果:

有环
环的入口点为:3

算法性能分析

对链表遍历一次,时间复杂度为O(N)。只需要几个指针变量来保存结点的地址信息,空间复杂度为O(1).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值