题目描述
编写一个程序,找到两个单链表相交的起始节点。
如示例1:
输入: listA = [1, 1, 2, 3, 4, 5]; listB = [1, 2, 3, 4, 5]
输出: ListNode(3)
图1
注意:
- 如果两个链表没有交点,返回
null
; - 在返回结果后,两个链表仍须保持原有的结构;
- 可假定整个链表结构中没有循环;
- 程序尽量满足O(N)时间复杂度,且仅用O(1)内存。
思路分析
第一次看到这道题目时,很容易想到Leetcode142,不同的是它们是一个链表且内部可能带有循环,求解循环的初始节点,我们可以通过Leetcode142来类似思考。
为方便讲解,我们直接按照图1所给的图示进行推理,说明中已经给出希望使用常量级的空间,则不能借助辅助容器,所以我们直接思考指针解法。这里假设,两个指针,第一个命名为pA
,起始节点为listA
头节点; 第二个名命为pB
, 起始节点为listB
头节点。根据Leetcode142我们知道,当两个指针相遇时,其必走过了相同个数的节点,这时我们就知道了结果判定条件,即pA == pB
,这样我们再进一步的往前推进。
这里给出的两条链表,当我们的指针运行到各自链表尾部的时候,该如何处理?题目中没有给出两条链表的长度大小信息,再根据之前我们的经验判定:走过了相同个数的节点,则不妨当pA
指向listA
尾节点时,下一步将其指向listB
的头节点;类似地,pB
指向listB
尾节点时,下一步将其指向listA
的头节点。这样我们易知,当存在两个链表地交点时,一定会有pA == pB && pA != null
的满足节点存在,此时,pA
即我们的返回节点。
边界条件考虑
这里,我们在思路分析中给出了一般规则,但是更重要的是边界问题的统一化,这里我们需要进一步测试以下边界问题:
- 当两个链表不存在相交节点时,此时的处理规则是什么?
- 能否将边界条件的解决统一到一般规则内?
对于问题1,我们来细致的讨论。当两条链表不存在交点时,即图2所示的情况:
图2
我们先按照一般规则判断会不会发生异常:
代码1:
ListNode pA = listA;
ListNode pB = listB;
while(pA != pB){
pA = pA.next;
pB = pB.next;
}
return pA;
当while
循环体运行第5
次的时候,此时pA -> ListNode(5), pB -> null
; 此时依然满足pA != pB
,故进入第6
次循环,则有pA -> null, pB -> null
,此时循环终值,返回值pA = null
,返回正确,即一般规则覆盖了此边界情况。
对于问题2,当两条链表存在交点时,尤其是两条链表不等时,我们每个指针至少均对两条链表遍历近一次后,会发现节点;此时我们需要在循环体内,给出连接到另一个链表头的代码,将代码1改善后,则有:
ListNode pA = listA;
ListNode pB = listB;
while(pA != pB){
pA = pA == null ? listB : pA.next;;
pB = pB == null ? listA : pB.next;
}
return pA;
到此,我们已经讨论了一般规则与边界情况,此时可直接给出代码如下:
解题代码
public static ListNode solution(ListNode listA, ListNode listB) {
if (listA == null || listB== null ) {
return null;
}
ListNode pA = listA;
ListNode pB = listB;
while(pA != pB ){
pA = pA == null ? listB : pA.next;
pB = pB == null ? listA : pB.next;
}
return pA;
}
特别说明
这里的解题代码书写方式是参照网络大神解法,但因难以溯源,且分析过程为原创,故此篇文章标为“原创”,望请谅解。
复杂度分析
时间复杂度:我们对数据遍历次数小于等于两次,时间复杂度为O(N);
空间复杂度:我们没有借助额外的容器,所以空间复杂度为常量级O(1)。
GitHub源码
完整可运行文件请访问GitHub。