两个单链表相交的一系列问题
- 单链表可能有环,也可能无环
- 两个单链表可能相交也可能不相交。
若相交,请返回相交的第一个节点,若不相交请返回null
逻辑如下:
根据有环和无环可以分为三种情况:
1. 两个链表都无环,可能相交“Y”,可能不相交
2. 一个有环,一个无环,则不可能相交
3. 两个都有环,可能会出现三种情况
如下如所示
class Node{
int data;
Node next;
public Node(int data){
this.data = data;
}
}
1. 判断有环无环: 有环返回第一个进入环的节点,无环返回null
1.1 HashSet方法
将节点一个一个添加进HashSet中,出现重复的元素(不是节点的值,是节点本身)证明有环
public static Node getLoopNode(Node head) {
if(head == null)
return head;
HashSet<Node> set = new HashSet<>();
Node cur = head;
while(cur != null) {
if(set.contains(cur))
return cur;
set.add(cur);
cur = cur.next;
}
return null;
}
1.2 快慢指针方法
若快指针(一次两步)等于慢指针(一次一步),则有环,此时让快指针从头开始,快慢指针再次重新移动直至相遇,返回相遇节点。
public static Node getLoopNode1(Node head){
if(head == null || head.next == null || head.next.next == null)
return null;
Node fast = head.next.next;
Node slow = head.next;
while(fast != slow){
if( fast.next == null || fast.next.next == null)
return null;
fast = fast.next.next;
slow = slow.next;
}
fast = head;
while(fast != slow){
fast = fast.next.next;
slow = slow.next;
}
return fast;
}
2. 根据有无环判断单链表相交情况
2.1 无环
方法一:暴力法
两次循环寻找第一个公共节点:T(m, n) = O(
m
n
mn
mn)
public Node getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null)
return null;
HashSet<ListNode> set = new HashSet<>();
ListNode cur = headA;
while(cur != null){
set.add(cur);
cur = cur.next;
}
cur = headB;
while(cur != null){
if(set.contains(cur))
return cur;
cur = cur.next;
}
return null;
}
方法二:哈希表发
public Node getIntersectionNode(Node headA, Node headB) {
if(headA == null || headB == null)
return null;
HashSet<ListNode> set = new HashSet<>();
Node cur = headA;
while(cur != null){
set.add(cur);
cur = cur.next;
}
cur = headB;
while(cur != null){
if(set.contains(cur))
return cur;
cur = cur.next;
}
return null;
}
方法三: 双指针法
思路:
指针 pA 指向 A 链表,指针 pB 指向 B 链表,依次往后遍历
如果 pA 到了末尾,则 pA = headB 继续遍历
如果 pB 到了末尾,则 pB = headA 继续遍历
比较长的链表指针指向较短链表head时,长度差就消除了
如此,只需要将最短链表遍历两次即可找到位置
简而言之就是让两个指针走的路程长度一样多,第二次必定会相遇。
public Node getIntersectionNode(Node headA, Node headB) {
if (headA == null || headB == null) return null;
Node pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
public static 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;
}
方法四:两个辅助栈
将两个链表元素分别入两个栈,出栈前先保存节点,直到两个节点不相等时则返回上一个节点
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
Node cur1 = head1;
Node cur2 = head2;
while(cur1 != null){
stack1.push(cur1);
cur1 = cur1.next;
}
while(cur2 != null){
stack2.push(cur2);
cur2 = cur2.next;
}
//栈顶不相等,说明没有共同元素,返回null
if(stack1.peek() != stack2.peek())
return null;
while(!stack1.empty() && !stack2.empty()){
Node temp = stack1.peek();
stack1.pop();
stack2.pop();
if(stack1.peek() != stack2.peek()) //下一个不相等返回上一个
return temp;
}
return null;
}
2.2 一个有环一个无环
一个有环一个无环必定没有公共节点,返回空即可
2.3 都有环
有环分为三种情况:
逻辑:用是否有入环相同节点来划分
将情况划为两种情况:
- loop1 == loop2, 此时就是图中情况二,将环忽略(就是将loop1或者loop2作为结束节点),将这种情况化成了上面无环的情况(区别:这种情况必定有相同节点)
- loop1 != loop2, 此时就是让loop2作为结束节点,loop1作为开始节点,loop1开始遍历
1. 若有loop1 == loop2的情况,那loop1或者loop2都是第一个开始节点,返回一个即可;
2. 若到了结束节点loop2没有相等的情况,则返回null。
public static 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;
n = Math.abs(n);
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;
}
}
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) { //装换成无环的结构链表,但是必有相交节点
cur1 = head1;
cur2 = head2;
while(cur1 != cur2){
cur1 = cur1 == loop1 ? head2 : cur1.next;
cur2 = cur2 == loop2 ? head1 : cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
整体代码如下:
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
//主逻辑
public static 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, head2, loop2);
}
//一个有环一个无环
return null;
}
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
public static 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;
}
public static 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;
n = Math.abs(n);
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;
}
}
public static void main(String[] args) {
// 1->2->3->4->5->6->7->null
Node head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
// 0->9->8->6->7->null
Node head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
// 1->2->3->4->5->6->7->4...
head1 = new Node(1);
head1.next = new Node(2);
head1.next.next = new Node(3);
head1.next.next.next = new Node(4);
head1.next.next.next.next = new Node(5);
head1.next.next.next.next.next = new Node(6);
head1.next.next.next.next.next.next = new Node(7);
head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
// 0->9->8->2...
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next; // 8->2
System.out.println(getIntersectNode(head1, head2).value);
// 0->9->8->6->4->5->6..
head2 = new Node(0);
head2.next = new Node(9);
head2.next.next = new Node(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(getIntersectNode(head1, head2).value);
}
}