两个单链表相交的一系列问题
问题重述:
给定两个单链表,单链表可能有环,也可能没有环,给定两个链表的头节点,判断是否相交,如果相交就返回相交的第一个结点,如果不相交就返回null
问题分析:
这道问题要求判断两个单链表是否相交,首先,环状链表和非环状链表不可能相交(因为相交之后,两个链表的后续结点应该是一样的)。因此,我们对于相交问题就转换成了单链表的相交和环形链表的相交,那么这道题就可以划分成三个小问题:
- 单链表是否含有环
- 无环单链表的相交
- 环形单链表的相交
解法:
哈希表、双指针
解题:
代码:
问题一:判断链表是否有环,如果有就返回环的入口节点
// 方法一,使用哈希表(使用set集合),遍历链表,将当前遍历的结点和集合中的结点进行判断,如果和集合中的结点一样,那么当前节点就是环开始的结点,如果没有就开始对下一个结点进行判断,如果没有环那么循环是会结束的
// 方法二,使用双指针
public static Node getLoopNode(Node head) {
if(head == null || head.next == null || head.next.next == null) {
return null;
}
// 定义快慢指针,因为前面已经对链表前几个结点进行了判断,所以我们指针初始位置可以直接移动一次
Node slow = head.next;
Node fast = head.next.next;
while(slow != fast) {
if(fast.next == null || fast.next.next == null) {
return null;
}
// 遍历链表,当快慢指针没有移动到同一个位置的时候,慢指针移动一位,快指针移动两位
slow = slow.next;
fast = fast.next.next;
}
// 当快慢指针移动到同一个位置的时候,结束循环,将快指针移动到头结点处
fast = head;
// 再次对链表进行遍历,此时快慢指针每次都前进一位,最后两个指针第一个共同指向的位置就是环开始的结点
while(slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
代码解析:使用哈希表的话思路非常清晰,时间复杂度为O(N),空间复杂度为O(N),如果使用双指针,运用到的是数学思想,时间复杂度为O(N),空间复杂度为O(1)
问题二:判断无环单链表的相交,如果相交就返回相交结点
// 方法一:使用哈希表,先将一个链表的结点加入到集合中取,然后对第二个链表进行遍历,每经过一个结点,就判断当前结点是否存在于哈希集合中,如果存在,那么该节点后面的结点都在哈希集合中(因为next是一样的)
// 方法二:使用双指针
public static Node noLoop(Node headA,Node headB) {
if(headA == null || headB == null) {
return null;
}
// 定义一个常量用于保存两个链表长度的差值
int n = 0;
// 定义两个结点指针,分别对应两个链表的头部
Node cur1 = headA;
Node cur2 = headB;
// 遍历两个链表,得到两个链表的差值,以及两个链表的最后一个结点
while(cur1.next != null) {
n++;
cur1 = cur1.next;
}
while(cur2.next != null) {
n--;
cur2 = cur2.next;
}
// 如果两个链表相交的话,最后一个结点一定会一样
if(cur1 != cur2) {
return null;
}
// 将两个指针结点重新定义为两个链表的头部
cur1 = (n > 0 ? headB : headA);
cur2 = (cur1 == headB ? headA : headB);
n = Math.abs(n);
// 在长度较长的链表中,将链表指针移动到和短链表相同的长度的起始位置
while(n != 0) {
n--;
cur2 = cur2.next;
}
// 对两个链表分别进行遍历,相同的结点就是两个链表的相交结点
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
代码解析:方法二的思路:首先要明确,两个链表如果相交,那么他们在相交结点之后的结点都是相同的,那么我们可以通过判断两个链表的最后一个结点来判断两个链表是否相交,如果相交,那就找到哪一个相交结点。因为两个链表相交,他们后面的链表是相同的,那么从相交结点向前推一段距离(到达短链表的首部),他们从这一段到最后的距离是相同的,走相同的步数可以到达相交结点,那么我们只需要计算两个链表长度的差值(前面需要遍历链表,可以从此处得出),让长的链表先走这一段距离,然后两个链表一起往后走,当当前结点相同的时候,这个节点就是我们要的相交系欸但
问题三:判断有环单链表的相交,相交结点
public static Node bothLoop(Node headA,Node loopA,Node headB,Node loopB) {
Node cur1 = null;
Node cur2 = null;
// 当两个环状链表的环开始结点相同时,我们只需要考虑两个链表在进入环之前的哪一个结点相交(也就是变成了无环单链表的)
if(loopA == loopB) {
int n = 0;
cur1 = headA;
cur2 = headB;
while(cur1.next != loopA) {
n++;
cur1 = cur1.next;
}
while(cur2.next != loopB) {
n--;
cur2 = cur2.next;
}
if(cur1 != cur2) {
return null;
}
cur1 = (n > 0 ? headB : headA);
cur2 = (cur1 == headB ? headA : headB);
n = Math.abs(n);
while(n != 0) {
n--;
cur2 = cur2.next;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else {
// 两个环的起始结点不同,那么只需要在环中遍历,找到那另一个链表的环起始结点即可,如果找到就是相交,没找到就是不相交
cur1 = loopA.next;
while(cur1 != loopB) {
if(cur1 == loopB) {
// 返回loopA或者loopB都可以
return loopA;
}
cur1 = cur1.next;
}
return null;
}
}
最后总问题:
public static Node getInsersectNode(Node headA,Node headB) {
if(headA == null || headB == null) {
return null;
}
Node loopA = getLoopNode(headA);
Node loopB = getLoopNode(headB);
if(loopA == null && loopB == null) {
return noLoop(headA,headB);
}
if(loopA != null && loopB!= null) {
return bothLoop(headA,loopA,headB,loopB);
}
return null;
}
总结:
在对于链表的操作中,双指针是一个非常好用的方法,哈希表在很多时候也能够简便的解决我们的问题