输入两个链表,找出它们的第一个公共节点。链表的定义,哨兵节点等的处理见博文
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
如下面的两个链表:
我们知道链表是通过next引用串联起来的,两个链表第一个公共节点后面的节点都是相同的,所以差异就是第一个 相同节点的前面节点。我们接下来看看如何解决这个问题。
思路一:可以想象成两个人走路,如下图所示,如果两个人走路的速度都是v,如果甲先走距离 M ,则 甲 和 乙再同时走距离 S,一定会同时到达道路的交叉口,然后后续的 L 就是两个共用的道路。
其实两个链表的第一个公共节点也是类似的逻辑,只要节点数多的链表先走 几步,保证余下的节点数量一样多,则后续一定会同步到达交叉点。nodeA先走节点数的差额步数,
然后nodeA 和 nodeB同时走,一定会同步到达第一个交叉节点,或者没交叉节点,则到达终止节点。
经过上面的分析,其实问题的重点是判断哪个节点先走,走多少步(走多少个节点),其实就是节点数多的节点先开始走,走的步数是节点数多的链表的节点个数减去节点数少的链表的节点个数。经过上面分析代码如下,结合注释和图来看,比较好理解。
public Node getIntersectionNode(Node headA, Node headB) {
Node nodeA = headA;
Node nodeB = headB;
//分别定义lenA和lenB表示两条链表节点的个数
int lenA = 0;
int lenB = 0;
//求链表A节点数
while (nodeA != null) {
lenA++;
nodeA = nodeA.next;
}
//求链表B节点数
while (nodeB != null) {
lenB++;
nodeB = nodeB.next;
}
//节点重置
nodeA = headA;
nodeB = headB;
//如果节点A的数量大于节点B的个数,链表A向前移动lenA-lenB 步
if (lenA > lenB) {
int step = lenA - lenB;
while (step > 0) {
nodeA = nodeA.next;
step--;
}
}
//如果节点B的数量大于节点A的个数,则链表B向前移动lenB - lenA 步
if (lenB > lenA) {
int step = lenB - lenA;
while (step > 0) {
nodeB = nodeB.next;
step--;
}
}
//然后两个链表同步走
while (nodeA != null && nodeB != null) {
//第一个相等的节点即为第一个交叉点
if (nodeA == nodeB) {
return nodeA;
}
nodeA = nodeA.next;
nodeB = nodeB.next;
}
return null;
}
时间复杂度为O(lenA+lenB),其实链表的长度(即链表节点个数)可以作为链表的一个属性存储起来,这样就减少遍历求链表的节点个数了。
思路二:遍历其中一个链表,用Set记录下出现的值,再遍历另一个,如果出现在Set中,则为第一个相交的节点。代码下
public Node getIntersectionNode(Node headA, Node headB) {
Node nodeA = headA;
Node nodeB = headB;
Set<Node> set = new HashSet<>();
//遍历A,把A中出现的元素添加到Set中
while (nodeA != null) {
set.add(nodeA);
nodeA = nodeA.next;
}
//遍历B,如果链表B的元素出现在Set中,则可以直接返回
while (nodeB != null) {
if (set.contains(nodeB)) {
return nodeB;
}
nodeB = nodeB.next;
}
return null;
}
时间复杂度为O(lenA + lenB),空间复杂度为O(lenA)。不满足要求。
思路三:比较巧妙,我们照样以下面这一副图进行说明。
如果甲走到终点时 走 的路程是 M + S +L,乙走到终点时走的路程是S + L,那 如果 小人甲 走到终点时换到 小人乙 的起点重新开始走,小人乙走到终点时换到 小人甲 的起点重新开始走,当走到 道路交叉口时。
甲走过的路程 = (M + S + L) + (S) 乙 走过的路程 = (S + L) + (M + S),到交叉点时走的路程是一样的,说明会同时到达交叉点,基于上面分析,得到代码如下:
public ListNode getIntersectionNode(Node headA, Node headB) {
Node nodeA = headA;
Node nodeB = headB;
while (nodeA != nodeB) {
nodeA = nodeA == null ? headB : nodeA.next;
nodeB = nodeB == null ? headA : nodeB.next;
}
return nodeA;
}
可能有疑问,如果 nodeA 和 nodeB没交叉时会不会满足?结论其实也是正确的。没交叉点,相当于 L = 0;其实上面的等式还是成立的。