问题描述:
一个比较经典的问题,判断两个链表是否相交,如果相交找出他们的交点。
思路:
1、碰到这个问题,第一印象是采用hash来判断,将两个链表的节点进行hash,然后判断出节点,这种想法当然是可以的。O(Length(h1)+Length(h2)).需要消耗额外的存储空间O(Length(h1)).
2、当然采用暴力的方法也是可以的,遍历两个链表,在遍历的过程中进行比较,看节点是否相同。复杂度O(Length(h1)*Length(h2)).
3、第三种思路是比较奇特的,在编程之美上看到的。先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?
这样进行转换后就可以从链表头部进行判断了,其实并不用。通过简单的了解我们就很容易知道,如果新链表是有环的,那么原来第二个链表的头部一定在环上。因此我们就可以从第二个链表的头部进行遍历的,从而减少了时间复杂度(减少的时间复杂度是第一个链表的长度)。复杂度是线性的O(N)
下图是一个简单的演示:
这种方法可以判断两个链表是否相交,但不太容易找出他们的交点。
4、仔细研究两个链表,如果他们相交的话,那么他们最后的一个节点一定是相同的,否则是不相交的。因此判断两个链表是否相交就很简单了,分别遍历到两个链表的尾部,然后判断他们是否相同,如果相同,则相交;否则不相交。示意图如下:
判断出两个链表相交后就是判断他们的交点了。假设第一个链表长度为len1,第二个问len2,然后找出长度较长的,让长度较长的链表指针向后移动|len1 - len2| (len1-len2的绝对值),然后在开始遍历两个链表,判断节点是否相同即可。复杂度是O(Length(h1)+Length(h2)).并且不需要消耗格外的存储空间。
扩展问题:
1. 简述
给出两个链表的头指针,比如h1,h2,判断这两个链表是否相交。这里是为了简化问题,我们假设两个链表不带环。
扩展:如果链表可能有环呢?
扩展:如何求出两个相交链表的相交的第一个节点。
2. 分析
主要就是两个问题,问题一,两个链表是否相交,问题二,两个链表如果相交,求得相交第一个节点。
如果两个链表相交,那么要么两个链表都没有环,要么都有环。所以我们首先判断h1和h2是否有环:三种情况:一个有环一个没环(这种情况说明肯定不相交),两个都有环(需要进一步判断),两个都没有环(需要进一步判断)。
一个链表是否有环的判断方法很简单,就是两个指针,一个一次走一步,另一个一次走两步,如果有环,那么两个指针早晚进入环,两个指针的相对速度正好是1,因此距离会一点一点减少,直到两个指针相遇,如果其中一个指针变为空了,说明不存在环。
对于都没有环的两个链表:如果相交比如是从尾部向头部的若干个节点相交(至少1个),可以直接判断两个链表的最后一个节点地址是否相同。如果要寻找第一个相交的节点,也很容易,计算两个节点的长度差,假设为k,让较长的链表先走k步,然后,每走一步前先判断节点地址是否相等即可。
对于都有环的两个链表:如果他们相交那么两个链表的环的部分必然是重合的,因为一个链表如果有环必然是形如数字6的情况。注意这里不能用两个指针分别从两个链表走,因为,如果链表相交,那么一快一慢的指针会相交,但是如果链表不相交,那么两个指针永远都不会相交,而且都在各自的环中不停的走,这样无法结束循环的。这里需要转换思路,首先求得链表进入环的第一个节点,因为这个节点也在环上,所以这个节点必然是相交的,只要判断这个节点地址是否相同即可。对于相交的第一个节点,其实如果找到两个链表进入环的第一个节点了,那么从头节点到这个节点,就转化为两个单链表的形式了,进入环的第一个节点相当于单链表的最后一个节点。
关键在于寻找带环链表的进入环的那个节点。方法如下:两个指针,一个走一步,一个走两步,在环中相遇位置为X,然后从头节点和X位置,分别一步一步的走,每次判断是否相遇,相遇点就是所求节点。
证明如下:假设头节点位置为A,第一个节点为M,相遇节点为X,环长为Len。
因为快节点每次比慢节点快一步,慢节点进入后快节点不用一圈就能赶上慢节点了。
慢节点走的路程 = AM + MX
快节点走的路程 = AM + MX + n * Len
2*(AM + MX ) = AM + MX + n *Len
AM + MX = n*Len
AM = (n-1)*Len + XM 就是这句话了,说明从A到M的距离与X到M的距离,同余环长。因此分别从A和X走,必然相交于X节点。