- 注: 这也是程序员面试金典中的02.07题、剑指offer中的第52题
1. 问题描述及分析
如果只学过java,而不了解指针的话其实难以理解题目,为什么示例1中的值为1的节点不是相交的节点呢?
其实可以这样理解,链表A和B中虽然都有值为1的节点,但这两个节点存储的位置不同,比如A中值为1的节点存储的位置为0x33ff,B中值为1的节点存储的位置为0x0011,但是从8开始,所有的节点存储的值和位置都相同,所以值为8的节点是相交的起始节点。
Java中判断两个节点是否相同可以直接用==
判断。
2. 解题思路及代码
思路参考:
作者:sdwwld
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/solution/ji-he-shuang-zhi-zhen-deng-3chong-jie-jue-fang-s-3/
来源:力扣(LeetCode)
主要有3种解题思路:
2.1 方法一:用Set集合来判断
- 做这题最容易想到的解决方式是,先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可。
- 代码如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
Set<ListNode> set = new HashSet<>();
// 将A链表遍历,并将所有节点存放Set集合种
while (curA != null){
set.add(curA);
curA = curA.next;
}
while (curB != null){
if (set.add(curB)){
curB = curB.next;
}else {
// 如果向set中添加节点失败,证明当前节点就是相同的节点
return curB;
}
}
// 如果至少有一个链表为空,或者两个链表都添加成功,证明没有相同节点,返回null
return null;
}
- 提交结果:
2.2 方法二: 先将两个链表对齐
如果两个链表长度相同,那就可以同时遍历两个链表,然后逐个判断,难点在于两个链表长度不同的情况。
其实也很简单,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可。如下图所示:
- 代码如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = len(headA);
int lenB = len(headB);
while (lenA != lenB){
if (lenA > lenB){
// A链表比较长
curA = curA.next;
lenA --;
}else {
// B链表比较长
curB = curB.next;
lenB--;
}
}
// 同时遍历
while (curA != curB){
curA = curA.next;
curB =curB.next;
}
return curA;
}
// 求链表长度
private int len(ListNode head){
int res = 0;
while (head != null){
res++;
head = head.next;
}
return res;
}
- 执行结果:
2.2 方法三:双指针解决
思路是,假设链表A和B的长度不同,但是A+B和B+A的长度是相等的,或者说,先遍历A然后再遍历B,与先遍历B再遍历A走的长度是相等的,并且如果有相同点,两种遍历方法会在相同点处相遇。
下面,具体展开讲一讲。
两个指针,最开始的时候一个指向链表A,一个指向链表B,然后他们每次都要往后移动一位,顺便查看节点是否相等。如果链表A和链表B不相交,基本上没啥可说的,我们这里假设链表A和链表B相交。那么就会有两种情况:
- 一种是链表A的长度和链表B的长度相等,他们每次都走一步,最终在相交点肯定会相遇。
- 一种是链表A的长度和链表B的长度不相等,如下图所示
虽然A和B有交点,但因长度不同,所以他们完美的错开了,即使把链表都走完了也找不到相交点。
我们仔细看上面的图,如果A指针把链表A走完了,然后再从链表B开始走到相遇点就相当于把这两个链表的所有节点都走了一遍,同理如果B指针把链表B走完了,然后再从链表A开始一直走到相遇点也相当于把这两个链表的所有节点都走完了。
所以如果A指针走到链表末尾,下一步就让他从链表B开始。同理如果B指针走到链表末尾,下一步就让他从*链表A开始。只要这两个链表相交最终肯定会在相交点相遇,如果不相交,最终他们都会同时走到两个链表的末尾,不妨画个图看一下:
如上图所示,A指针和B指针如果一直走下去,那么他们最终会在相交点相遇。下面简单证明一下,参考。
- 设长链表A长度为LA,短链表长度LB;
- 由于遍历速度相同,则在长链表A走完LA长度时,短链表B已经反过头在A上走了LA-LB的长度,剩余要走的长度为LA-(LA-LB) = LB;
- 之后长链表A要反过头在B上走,剩余要走的长度也是LB;
- 也就是说目前两个链表“对齐”了。因此,接下来遇到的第一个相同节点便是两个链表的交点。
- 代码如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode nodeA = headA;
ListNode nodeB = headB;
while (nodeA != nodeB){
nodeA = nodeA == null ? headB : nodeA.next;
nodeB = nodeB == null ? headA : nodeB.next;
}
return nodeA;
}
- 提交结果: