算法通关村第一关 —— 链表黄金挑战笔记
本文就关于环的两个问题进行讨论
链表是否有环
确定环的入口
测试代码
public static void main(String[] args) {
Node<Integer> one = new Node<>(11);
Node<Integer> two = new Node<>(22);
Node<Integer> three = new Node<>(33);
Node<Integer> four = new Node<>(44);
Node<Integer> five = new Node<>(55);
Node<Integer> six = new Node<>(66);
Node<Integer> seven = new Node<>(77);
Node<Integer> eight = new Node<>(88);
Link link = Struct.getRang(new Node[]{one, two, three, four, five, six, seven},eight);
System.out.println(isRangHash(link));
System.out.println(isRang(link));
System.out.println(getStart().getVal());
}
public static Link<Integer> getRang(Node<Integer>[] nodes,Node<Integer> start) {
Link<Integer> link = new Link<>();
Arrays.asList(nodes).forEach(link::append);
link.add(link.getHead(),start, link.getLength() - 1);
return link;
}
链表是否有环
方法一
把链表中的元素放到hash中,如果存在环,则一定会碰撞
/**
* 是否环
* 使用hash
**/
public static boolean isRangHash(Link<Integer> link) {
if (Struct.isLinkBlank(link)){
return false;
}
HashSet<Node<Integer>> nodes = new HashSet<>();
Node<Integer> node = link.getHead();
while (node != null){
nodes.add(node);
node = node.getNext();
//存在则会发生碰撞
if (nodes.contains(node)){
return true;
}
}
return false;
}
方法二
使用快慢指针,慢指针每次走一步,快指针每次走两步,若链表存在环,则快慢指针必会相遇;
因为快指针只比满指针快一步,当满指针到打环的起点时,快指针必定在环的某个节点上,此时快指针则会一步一步的向慢指针追去,直至相遇;
/**
* 是否环
* 使用快慢指针,慢指针每次走一步,快指针每次走两步,若链表存在环结构,则必然会相交
**/
public static boolean isRang(Link<Integer> link) {
if (Struct.isLinkBlank(link)){
return false;
}
Node<Integer> slow = link.getHead();
Node<Integer> fast = link.getHead();
//fast.getNext() != null 链表长度 = 1
while (fast != null && fast.getNext() != null){
slow = slow.getNext();
fast = fast.getNext().getNext();
//相遇则为环
if (slow == fast){
return true;
}
}
//若链表不存在环,快指针会先到达链尾
return false;
}
确定入口
如图,先说方法,同样是快慢指针,两指针同时开始前进,记录第一次相遇的节点,然后两指针分别于头节点和相遇节点以step = 1的速度向前,当他们再次相遇的同时,就是环的入口
换成字母描述,满指针slow,快指针fast,slow和fast同时从x出发,在z点相遇,然后slow重新回到x点,fast则从z点开始,同时以同样的速度(step = 1)前进,最终第一次相遇必会在Y(环入口)处;
这是为什么呢?看起来似乎是这么回事,但又有哪里说不上来的样子
别急,我们从数学的角度分析一下,该场景是否合理;
此时,我们不妨把该场景换成一道数学的证明题:
如上图,|XY| = a,|YZ| = b,|ZY| = c,设环的长度d = |YZ| + |ZY| = b + c
∵环两节点必会相遇,∴2(a + b) = a + b + nd (n∈Z & n > 1)
证明:a = c + md (m,n ∈ Z)
证:2(a + b) = a + b + nd
2a = a - b + nd
a = - b + (n-1)d + d
a = - b + b + c + (n-1)d
a = c + (n-1)d
令m = n-1 ,证得 a = c + md
其中n和m分别是相遇前和第二次同速度开始跑的fast在环中跑的次数,可以看出,当b < d,这时fast会比一开始的时候少走一圈;
/**
* 环的起点
* 快慢指针先走到相遇点,然后慢指针回到头节点,快指针仍在相遇点,然后两指针以step=1的速度前进,相遇时即为起点
*/
public static Node<Integer> getStart(Link<Integer> link) {
if (Struct.isLinkBlank(link)) {
return null;
}
Node<Integer> slow = link.getHead().getNext();
Node<Integer> fast = link.getHead().getNext().getNext();
//前提条件:链表一定存在环,长度大于2
while (fast != slow) {
slow = slow.getNext();
fast = fast.getNext().getNext();
}
//慢指针重新回到头节点
slow = link.getHead();
while (fast != slow) {
slow = slow.getNext();
fast = fast.getNext();
}
return fast;
}