超难链表题

现有两个单链表,一个长度为M,一条长度为N,可能有环也可能无环,若他们相交,则给出他们相交的第一个节点,否则返回null,要求空间复杂度O(1)、空间复杂度O(M+N)。

这题信息量很大,需要拆分:

1.如何判断单链表有无环:

有环链表的判定方式:只要他能在遍历的过程中遍历到一个节点两次,那就是有环的。

所以我们可以准备一个hash,遍历链表:

A.遇到了hash中未加入的节点,加入hash

B.遇到了hash中加入过的节点,有环

C.链表遍历完了,指针指向了null,此时必定无环

 

O(1)复杂度下如何得到单链表有环无环呢?需要利用神器-快慢指针。

我们让快慢指针在这个链表上跑,如果有环,他们一定会相遇:

证明:设不属于环的部分长度为m,属于环部分的长度为n,如果他们相遇,一定是在环上【因为非环的地方由于快指针跑得快,他会一直在慢指针前面】,设他们跑了x次。

于是我们有式子:(1*x-m)%n==(2*x-m)%n,由于快指针跑得快,因此我们可以将上式变形为(x-m)-k1*n==(2*x-m)-k2*n(k2>k1),移项后我们得到x=(k2-k1)*n,这是一定有解的。

我们可以证明得k2-k1=1:

首先,由于快指针是一次跳两步的,所以当快指针走完了整条链【以所有节点被访问一次为基准】,此时不管这个单链表是完全是一个环还是一条直线加一个环,慢指针所在位置肯定里入环点距离不超过环长一半,而这时快指针在入环节点或入环节点的前一个节点,此时只要证在这次的遍历环过程中两个指针会相遇即可。

1)由于现在慢指针离环终点还有大于一半环长的距离,因此,在慢指针跳到终点前快指针必定会跨过慢指针一次

2)分情况讨论:

A.直线部分L和环部分长度C均为奇数或偶数,那么快指针经过(L+C)/2跳转到入环节点,此时慢指针在(L+C)/2位置,此时快慢指针相距(L+C)/2-L=(C-L)/2,而因为L和C的奇偶性相同,所以C-L必定为偶数,于是经过(C-L)/2步两者相遇,而快指针跳一次环需要的时间为C/2,C/2>(C-L)/2,所以可知快指针在第二次遍历环的时候就会撞上慢指针,最终慢指针没有走完一环而快指针没有走完第二环,于是k2-k1=0,并且可知k2=1,k1=0,x=1*n。

B.直线部分L、环部分长度C为一奇一偶,那么快指针经过(L+C+1)/2跳转到入环节点的前一个节点,此时慢指针在(L+C+1)/2位置,此时快慢指针相距(L+C+1)/2-L-1=(C-L-1)/2,由于C、L奇偶性不同,所以(C-L-1)为偶数,可整除2。于是经过(C-L-1)/2步两个指针相遇,然而因为(C-1)/2>(C-L-1)/2,所以可知快指针在第二次遍历环的时候就会撞上慢指针,最终慢指针没有走完一环而快指针没有走完第二环,于是k2-k1=0,并且可知k2=1,k1=0,x=1*n。

另外,当他们相遇后,如果将快指针指向头节点并变为慢指针,两个慢指针继续遍历,相等的时候对应的节点就是入环的节点。

证明:因为k1=0,所以第一次相遇时,慢指针的位置为(x-m)-0*n==x-m(x=1*n)->n-m,慢指针距离走完环还有的距离是n-(n-m)=m,得证。

快慢指针判环:

1)快慢指针同时在环上跑,如果快指针遍历到了NULL,则必定无环。

2)若快慢指针相遇,将快指针指向头节点,并将速度改为慢指针,新慢指针和旧慢指针同时跑,第一个相遇点即使入环点loop。

 

单链表的情况分类:

                                                                      1.无环

                                                                 2.有环

                    3.只有环

也就是,单链表有环的情况下,必定是先无环【无环的长度可为0】后有环,否则,会出下图情况:

这样乍一看是先有环后无环的,但是会发现,ERROR节点居然有两个next指针,这不符合单链表的基本特征。

 

现在我们解决了判环的问题,现在来求第一个相交的节点:

A.两条无环单链表相交:

不可能的情况如下图:还是之前说的,ERROR有两个next域,不符合单链表的条件。

弱化约束:如果不要求空间复杂度,可以先遍历一遍链表1,将所有节点打入hash,再遍历链表2,每次遍历节点,去hash中查询,如果已经有记录,那么这个节点必定是第一个相交节点,直接返回true。如果链表2都遍历完了,说明没有相交节点,返回false。

强化约束:由上面的图分析,我们可以知道,如果两条单链表相交,从他们相交的节点开始,一直到尾节点,都是一样的。因此,我们可以借用树上找公共祖先的思想,分别遍历两条链表得到他们的长度和尾节点,len1,tail1,len2,tail2。

a.tail1!=tail2,两条链表不可能相交

b.tail1==tail2,两条链表必定相交,但是第一个相交节点未必是尾节点,设dif=abs(len1-len2):

1)如果len1>len2,先将len1走dif,此时两条链表的开始指针到尾节点长度相等,同时开始遍历,将第一次相等处的节点返回即可

2)如果len2>len1,先将len2走dif,。。。。。。同上

 

B.一条链表有环,一条链表无环,不可能相交:因为当无环单链表和有环单链表相交时,由于有环单链表前面部分无环后面部分有环,因此从相交处开始到尾节点一定有一部分属于环,而只要有一部分属于环,就不可能满足无环条件。或者这么看,一旦相交,尾节点相同,而尾节点是属于环的,此时要求无环单链表有环,无法满足。示意图如下,A不可能无环:

C.两条有环链表

分三种情况:

1.两个各自不相交:

从A链表的入环节点开始,遍历下去,知道转回自己,如果期间都没有碰到B链表的入环节点,就是两个有环但各自不相交。

 

2.在相同的入环节点前相交

因为有相同的入环节点,所以第一个相交节点肯定是在入环节点或者更前,将环切掉,问题退化成两条单链表相交问题。

 

3.在环上相交,注意next域方向,并没有出现一个节点两个next的情况,因此是满足的

从A链表的入环节点开始,遍历下去,知道转回自己,如果期间碰到B链表的入环节点,就是两个有环链表相交,此时任意返回两个入环节点的一个即可,因为都在一个环上,环可以当成是一个无限长的直线,所以两个入环节点都可以作为第一个相交节点。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章