算法总结之 两个单链表相交的一些列问题
单链表,可能有环,也可能无环。给定两个单链表的头节点head1 和 head2 这两个链表可能交也可能不交。实现函数,如果相交,请返回相交的第一个节点,不交返回null
这道题目需要分析的情况非常
本题拆分长三个子问题,每一个子问题都可以作为一道独立的算法题。
问题一、 如何判断有环 有则返回第一个进入环的节点 没有返回null
问题二、 如何判断两个无环链表是否相交。相交返回第一个相交节,不相交则返回null
问题三、 如何判断两个有环链表是否相交。相交返回第一个相交节,不相交则返回null
注意! 如果一个链表有环,另一个链表无环,他们是不可能相交的。
如果一个链表没有环,遍历一遍可以遇到链表的终点。有环,那么。。。。永远在环里。。。。 如何找到第一个入环节点
思想!!!!! 设置一个慢指针 slow 和一个快指针 fast 开始都是head位置,然后slow 走一步 fast走两步 遍历
如果无环,fast一定先遇到终点,一旦fast到达终点,说明链表是无环的。
如果有环,fast和slow一定会在环中的某个位置相遇,当fast和slow相遇时候,fast指针重新回到head位置,slow不动。fast从每次移动两步,改为一步,slow依然一步。继续遍历
fast和slow一定会再次相遇。并且在第一个入环的节点处相遇。
瞅瞅 getLoopNode方法
package TT; import TT.Test84.Node; public class Test99 { public Node getLoopNode(Node head){ if(head==null || head.next==null || head.next.next==null){ return null; } Node n1 = head.next; Node n2 = head.next.next; while(n1!=n2){ if(n2.next == null || n2.next.next==null){ return null; } n2 = n2.next; n1 = n1.next; } n2 = head; while(n1 != n2){ n1=n1.next; n2=n2.next; } return n1; } }
如果解决了问题一,则知道了两个链表有环和无环的情况。
一个有环 一个无环 那么这两个链表是无论如何也不可能相交的
能相交的情况分两种: 一种是 都无环 一种是都有环
问题二 判断两个无环链表相交 交则返回第一个相交节点 不则返回null
如果两个无环链表相交 那么从相交节点开始一直到两个链表终止的这一段 是连个链表共享的
解决:
1 链表从1从头节点开始,走到最后一个节点(不是结束) 统计链表1长度len1 同时记录链表1的最后一个节点end1
2 链表从2从头节点开始,走到最后一个节点(不是结束) 统计链表2长度len2 同时记录链表1的最后一个节点end2
3 如果 end1 != end2 说明两个链表不交 返回null 如果end1=end2 说明相交
4 如果链表1比较长,链表1就先走len1-len2步。如果链表2比较长,链表1就先走len2-len1步。 然后两个链表一起走,第一次走到一起的那个就是所求
实现代码:
package TT; public class Test { public class Node{ public int value; public Node next; public Node(int data){ this.value=data; } } public Node noLoop(Node head1, Node head2){ if(head1==null || head2 ==null){ return null; } Node cur1 = head1; Node cur2 = head2; int n =0; while(cur1.next != null){ n++; cur1 = cur1.next; } while(cur2.next != null){ n--; cur2=cur2.next; } if(cur1 != cur2){ return null; } cur1 = n>0 ? head1 : head2; cur2 = cur1 == head1 ? head2 : head1; n=Math.abs(n); while(n!=0){ n--; cur1 = cur1.next; } while(cur1 != cur2){ cur1 = cur1.next; cur2 = cur2.next; } return cur1; } }
问题三,如何判断两个有环链表是否相交,交则返回第一个相交节点,不则返回null
考虑问题三时候,已经得到了两个链表各自的第一个入环节点
拓扑结构的判断:
让链表1从loop1出发,因为loop1之后的所有节点都在环上,所以将来一定能回到loop1 如果回到loop1之前没有遇到loop2 不想交(左图)
如果遇到了loop2 说明相交(右图) 此时返回loop1 跟 loop2 都可以
代码:
package TT; public class Test100 { public class Node{ public int value; public Node next; public Node(int data){ this.value=data; } } public Node bothLoop(Node head1 ,Node loop1, Node head2, Node loop2){ Node cur1 = null; Node cur2 = null; if(loop1 == loop2){ cur1 = head1; cur2 = head2; int n=0; while(cur1 != loop1){ n++; cur1 = cur1.next; } while(cur2 !=loop2){ n--; cur2=cur2.next; } cur1 = n>0 ? head1 : head2; cur2 = cur1==head1? head2 : head1; while(n!=0){ n--; cur1=cur1.next; } while(cur1 != cur2){ cur1 =cur1.next; cur2 =cur2.next; } return cur1; }else{ cur1 = loop1.next; //进环测试 while(cur1 != loop1){ if(cur1==loop2){ return loop1; } cur1=cur1.next; } return null; } } }
全部代码:
package TT; public class Test101 { public class Node{ public int value; public Node nextNode; public Node(int data){ this.value=data; } } public Node getIntersectNode(Node head1, Node head2){ if(head1 == null || head2==null){ return null; } Node loop1= getLoopNode(head1); //获得环形节点 Node loop2 =getLoopNode(head2); if(loop1==null && loop2==null){ return noLoop(head1, head2); } if(loop1 !=null && loop2 != null){ return bothLoop(head1, loop1, head1 loop2); } return null; } }