漫画算法-学习笔记(18)

漫画算法-小灰的算法之旅(18)

1. 如何判断链表有环

Q: 有一个单向链表,链表中有可能出现"环",像下图一样。那么,如何用程序来判断该链表是否为有环链表呢?

方法一

首先从头节点开始,依次遍历单链表中的每一个节点。每遍历一个新节点,就从头检查新节点之前的所有节点,用新节点和此节点之前所有节点依次做比较。如果发现新节点和之前的某个节点相同,则说明该节点被遍历过两次,链表有环;如果之前的所有节点中不存在与新节点相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。

就像图中这样,当遍历链表节点7时,从头访问节点5和节点3,发现已遍历的节点中并不存在节点7,则继续往下遍历。

当第二次遍历到节点2时,从头访问曾经遍历过的节点,发现已经遍历过节点2,说明链表有环

假设链表的节点数量为n,则该解法的时间复杂度为O(n^2)。由于并没有创建额外的存储空间,因此空间复杂度为O(1)。

方法二

首先创建一个以节点ID为key的hashSet集合,用来存储曾经遍历过的节点。然后同样从头节点开始,依次遍历单链表中的每一个节点。每遍历一个新节点,都用新节点和HashSet集合中存储的节点进行比较,如果发现HashSet中存在与之相同的节点ID,则说明链表有环,如果HashSet中不存在与新节点相同的节点ID,就把这个新节点ID存入HashSet中,之后进入下一个节点,继续重复刚才的操作。

遍历过5,3

遍历过5,3,7,2,6,8,1

当再次遍历节点2时,查找HashSet,发现节点已经存在。

由此可见,链表有环。

这个方法在流程上和方法一类似,本质的区别是使用了HashSet作为额外的缓存。假设链表的节点数量为n,则该解法的时间复杂度是O(n),由于使用了额外的存储空间,所以算法的空间复杂度同样是O(n).

方法三(快慢指针追及)

首先创建两个指针slow和fast;让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让slow指针每次向后移动一个节点,让fast指针每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则链表有环。如果不相同,则继续下一次循环。

第一步,slow和fast都指向节点5

第二步,slow指针指向节点3,fast指针指向节点7

第三步,slow指针指向节点7,fast指针指向节点6

第四步,slow指针指向节点2,fast指针指向节点1

第五步,slow指针指向节点6,fast指针也指向节点6,slow和fast指针重合,说明链表有环

此方法就类似于一个追及问题:在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员;原因很简单,因为跑道是环形的。

假设链表的节点数量是N,则该算法的时间复杂度为O(n).除了两个指针外,没有使用任何额外的存储空间,所以空间复杂度是O(1).

代码实现

/**
* 判断是否有环
* param head 链表头节点
*/
public static boolean isCycle(Node head){
  
  Node slow =head;
  Node fast =head;
  while(fast!=null && fast.next!=null){
    slow=slow.next;
    fast=fast.next.next;
    if(slow==fast){
      return true;
    }
  }
  return false;
}

/**
* 链表节点
*/
private static class Node(){
  int data;
  Node next;
  Node(int data){
    this.data=data;
  }
}

public static void main(String[] args) throws Exception{
  
  Node node1=new Node(5);
  Node node2=new Node(3);
  Node node3=new Node(7);
  Node node4=new Node(2);
  Node node5=new Node(6);
  node1.next=node2;
  node2.next=node3;
  node3.next=node4;
  node4.next=node5;
  node5.next=node2;
  
  System.out.println(isCycle(node1));
  
}

2. 如何求链表环的长度

如果链表有环,如何求出环的长度?

当两个指针首次相遇,证明链表有环的时候,让两个指针从相遇点继续循环前进,并统计前进的循环次数,知道两个指针第二次相遇。此时,统计出来的前进次数就是环长。

因为指针slow每次走1步,指针fast每次走2步,两者的速度差是1步。当两个指针再次相遇时,fast比slow多走了整整1圈。因此,环长=每一次速度差x前进次数=前进次数。

3. 如何求出环形俩表的入环点

如果链表有环,如何求出如环节点?

上图是对有环链表所做的一个抽象示意图。假设从链表头节点到入环点点距离是D,从入环点到两个指针首次相遇点的距离是S1,从首次相遇点回到入环点的距离是S2.

那么,当两个指针首次相遇时,各自所走的距离是多少呢?

指针slow一次只走1步,所走的距离为D+S1

指针fast一次走2步,多走了n(n>=1)整圈,所走的距离是D+S1+n(S1+S2)

由于fast指针的速度是slow指针的2倍,因此所走的距离也是slow的2倍:故存在如下等式

2(D+S1)=D+S1+n(S1+S2)

等式转换之后:

D=(n-1)(S1+S2)+S2

也就是说,从链表头节点到入环点的距离:等于从首次相遇点绕环(n-1)圈再回到入环点的距离。

因此,当发现slow指针和fast指针相遇时,我们再额外使用一个指针ptr.指向链表头部,随后他和slow指针每次向后移动一个位置。最终,它们相遇的节点,就是入环点。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值