剑指offer刷题-链表中环的入口结点

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

小白思路-HashMap:

有环链表如下:
在这里插入图片描述

遍历链表,通过.next不断获取下一个结点,将结点存储在HashMap中,键为结点,值为出现次数,当在keySet中能查找到此结点,说明在链表中已出现过此结点,即构成了环,将其判断为入口节点,否则就将其加入HashMap中。

时间复杂度:O(nlogn)
空间复杂度:O(n)

代码实现:

public class Solution {
	public ListNode EntryNodeOfLoop(ListNode pHead)
   	{
   		HashMap<ListNode,Integer> nodes = new HashMap<ListNode,Integer>();
   		
   		while(pHead != null)
        	{
        		for (ListNode node: nodes.keySet())
            		{
            			//每次都要遍历HashMap,查询是否键中已添加此结点
            			while(node == pHead)
                		{
                    			return pHead;
                		}
            		}
            		//HashMap中没有此结点,则添加
            		nodes.put(pHead,1);  
            		pHead = pHead.next;   //指向下一个结点
       	 	}       	 	
        	return null;
        }
}
            			

虽然采用了HashMap来存储结点,但是仍添加了额外的存储空间。

大牛思路-双指针法

一、首先判断链表中是否存在环

  1. 采用快慢指针来解决这个问题。定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。

  2. 如果走得快的指针追上了走得慢的指针,那么链表就包含环;如果走得快的指针走到了链表的末尾(ListNode的next指向null)都没有追上慢指针,那么链表就不包含环。

为何说二者一定相遇?因为fast会先进入环,在slow进入之后,如果把slow看做在前面,则fast在后面每次循环都会想slow靠近1,所以一定会相遇,而不会出现fast直接跳过slow的情况。

二、找到环中节点的数目
在这里插入图片描述

这一步的目的就是为寻找环的入口做铺垫。快慢两个指针相遇的节点一定是在环中

  1. 可以从这个节点出发,一边继续向前移动一边计数,当再次回到这个节点时,就可以得到环中节点数了。
  2. 在下次相遇时,fast比slow正好多走了一圈,即多走的距离正好等于环长。
    设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2len步,相遇时多走了一圈:
        环长=2
    len-len。

三、找到环的入口

  1. 先定义两个指针P1和P2指向链表的头结点。如果链表中的环有n个节点,则指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动。直到两个指针相遇,即环的入口。
  2. 第一次碰撞点Pos到连接点Join的距离 = 头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。

在这里插入图片描述
时间复杂度:O(n)
空间复杂度:O(1)

代码实现:

public class Solution {
    
    public ListNode meetingNode(ListNode head)
    {
        //判断链表是否存在环
        ListNode fast = head;
        ListNode slow = head;
        if(head == null)
            return null;
        while(fast != null)
        {
            slow = slow.next;
            fast = fast.next;
            if(fast != null)
            	fast = fast.next;
            if( fast!= null && fast == slow && slow != null)
                return fast;
        }
        return null;    
    }
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        ListNode meetingNode = meetingNode(pHead);
        if(meetingNode == null)
            return null;
        int number = 1;
        //number存储环的结点数目
        ListNode nextNode = meetingNode.next;
        while(meetingNode != nextNode)
        {
            nextNode = nextNode.next;
            number++;
        }
        
        //获取环的入口结点
        ListNode fast = pHead;
        ListNode slow = pHead;
        for(int i = number; i > 0; i--)
        {
            fast = fast.next;
        }
        while(fast != slow)
        {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

总结:

链表中环的入口结点问题采用双指针的方法,将问题分为三个小问题:

1、链表中是否存在环;
2、环的长度;
3、环的入口结点。

扩展:有环单链表的链表长
由2已知环的长度,由3已知入口结点的位置,就可以求出头结点到连接点的长度。两者相加就是链表的长度。

PS:关于环形链表

环形链表

类似于单链表,也是一种链式存储结构,环形链表由单链表演化过来。单链表的最后一个结点的链域指向NULL,而环形链表的建立,不要专门的头结点,让最后一个结点的链域指向链表结点。 简单点说链表首位相连,组成环状数据结构。如下图结构:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值