链接:https://mp.weixin.qq.com/s/A4jjclVpd7Q03yJfARR3DA
公众号:程序员架构进阶
一 题目
求两个单向链表的最早公共交点;如果没有返回null。
二 解析
链表是单向链表,即只有指向下一个节点的指针,而没有反向;公共节点,指地址相同的节点。即假设链表L1中有一个节点node1,L2中有一个节点node2,node1 = node2,注:这里的“=”,指的是node1 和 node2是同一个节点,也就是说,L1 和 L2都持有了对一个节点的引用,那么就说两个节点实际上是一个公共节点。我们可以用下面的图来表示:
上图中的node2和node3就是公共节点,node2是最早公共节点。
链表L1的长度m,链表L2的长度为n。
三 算法设计
3.1 多次遍历
两个链表都是有限长度,最直接的方法,就是直接遍历。从链表L1的第一个节点开始,遍历L2的所有节点,判断L1的这个节点是否与L2中的某个节点是公共节点,如果是,则直接返回这个节点即可;如果遍历结束后发现没有找到,那么返回nul.l。
时间复杂度:比较次数为mxn,所以时间复杂度为O(mxn);
空间复杂度:只使用一个临时变量,空间复杂度为O(1)。
3.2 倒序查找
上面的算法虽然能够找到公共节点,但显然效率太低。我们再看一下公共节点的定义,如果节点node是两个链表的公共节点,那么一定有L1从node开始之后,与L2的node及之后的节点完全相同。
基于这样的分析,我们从尾部向前查找就可以了,如果最后一个节点是公共节点,那么就继续向前查找,直到找到第一个公共节点。
但这里还有一个问题,因为给定的是两个单向链表,所以不能直接做倒序查找(前一个节点),所以我们需要处理一下。链表不可以,数组是可以的,所以思路为:
1、链表转数组,得到两个节点数组;
2、从两个数组的最后一个节点开始逐个向前比对,直到找到第一个公共节点位置。
示意如下:
时间复杂度:需要分别遍历一次链表,复杂度为m+n,之后从尾部遍历查找一次,所以时间复杂度为O(m+n+max(m, n));
空间复杂度:需要使用两个数组存储节点,还有一个指针临时变量,空间复杂度为O(m+n)。
3.3 首位对齐,指针逐个后移
再次分析,如果我们把两个链表按照尾部对齐,第一个公共节点一定出现在从后向前找的第k个节点,那么这个节点一定是链表L1的第m-k个节点,L2的第n-k个节点。
也就是说,假设m>n,那么我们直接从L1的第(m-n)个节点开始,与链表L2的第1个节点开始对比,如果相同,说明这个节点就是最早公共节点;如果不是,那么两个链表同时向后一位进行对比,判断是否是公共节点,如此下去。公共节点一定在我们比对的这些对节点之中。
这种方式下,如果两个链表的长度m,n是已知的,那么直接遍历就可以了,时间复杂度为O(min(m, n));
如果长度未知,那么我们需要遍历一次两个链表,得到两个链表的长度,然后再设置指针的起始位置并进行遍历,复杂度为O(m+n+min(m, n))。
空间复杂度:因为只需要m, n, 指针三个临时变量,所以空间复杂度为O(1)。
四 总结
这是链表题中并不复杂的一道,如果在leetcode中,应该最多只属于中等难度。但从这道题中,我们仔细思考之后可以看出一些题目之外的东西。
做题的人看到的是完全相同的信息,但能给出的解答是不同的。也就是说,每个人对信息的理解、提取、利用的能力存在差异,导致会有部分人得不到最优的解答。
其中一个比较容易犯的错误,或者说容易陷入的思维屏障,就是把题目中的某些条件,只作为了“目标”,而没有发现这本来就是可以用来利用的“条件”。以本题为例,公共节点本身有一定的要求,找到最早公共节点是我们的目标,但同时,公共节点本身的特性也是我们可以用来减少对比次数的条件。
算法题大多如此,充分利用题目中隐含的所有条件,才可以节约大量的时间或空间,这种思路,在工程中也一样可能适用。