您的位置:首页 > 职场人生

面试100题:7.判断两个链表是否相交

2014-04-22 17:17 337 查看
题目:

给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。

为了简化问题,我们假设俩个链表均不带环。

分析:

之前一直没有搞清楚的问题,今天看了如下链接才终于有了清晰的认识,一个字就是笨,俩字很笨,三字非常笨。

参考:http://blog.163.com/bbluesnow@126/blog/static/27784545201251051156817/。特此感谢!!!

先要搞清楚如下几个问题,就能看透此题的真面目,才能体会到此题居然可以挖掘的这么深。

1)判断一个单链表是否有环

在一个单链表上判断有没有环的首选方法就是采用快慢步长法。分别令两个指针p和q分别指向头节点,p指针每次前

进一步,q指针每次前进两步。如果最终p和q指针能够指向同一个节点,则说明有环。

证明:

假设p和q的速度分别为v1和v2,如果存在环,我们假设指针p和q第一次进入环时,他们相对于环中第一个节点

的偏移地址分别为a和b(偏移地址即为节点数)。

这样判断链表是否有环就转化为p和q指针的值是否存在相等的时候,也就是他们相对于环的首节点的偏移量是

一样的。设环的节点个数为n,程序可能循环了m次。

这样就会有如下等式:(a+m*v1)mod(n)=(b+m*v2)mod(n)

假设p指针mod(n)的最大整数位k1,q指针mod(n)的最大整数位k2,则(a+m*v1)-k1*n= (b+m*v2)-k2*n,整理后得

m=|((k2-k1)*n+a-b)/(v2-v1)| , v2>v1。

时间复杂度分析:

假设甩尾(在环外)长度为len1(结点个数),环内长度为 len2,链表总长度为n,则n=len1+len2 。当p步

长为1,q步长为2时,p指针到达环入口需要len1时间,p到达入口后,q处于哪里不确定,但是肯定在环内,此

时p和q开始追赶,q最长需要len2时间就能追上p(p和q都指向环入口),最短需要1步就能追上p(p指向环入口,

q指向环入口的前一个节点)。事实上,每经过一步,q和p的距离就拉近一步,因此,经过q和p的距离步就可以

追上p。因此总时间复杂度为O(n),n为链表的总长度。

// 1) 判断一个单链表是否有环

template<typename T>

bool checkCircle(T* head)

{

if (NULL == head)

return false;

T* low = head;

T* fast = head;

while (low->next != NULL && (fast->next != NULL) && (fast->next)->next != NULL)

{

low = low->next;

fast = (fast->next)->next;

if (low == fast)

{

return true;

}

}

return false;

}

2)判断一个环的入口点位置

证明:

链表形状类似数字6。假设环外长度(节点数)为a,环内长度为b,则总长度(链表总节点数)为a+b。起始节

点编号,从0开始。假设第i步访问的节点为S(i),i=0,1,2...n。当i<a时,S(i)=i;当i>=a时,S(i)=a+(i-a)%b。

分析追赶过程:

p和q指针分别从头节点以v1和v2速度前进,经过x步后碰撞,则有:S(x)=S(2x)。由于环的周期性存在

如下等式:2x=tb+x,得x=tb。碰撞时,必须发生在环内,所以有x>=a。

入口点为从起点走a步,即S(a)。S(a)=S(tb+a)=S(x+a)

结论:从碰撞点x前进a步即为环的入口点。

假设分别从起点和x点同时前进,第一个碰撞点就是环的入口点。

时间复杂度分析:

假设甩尾(在环外)长度为len1(结点个数),环内长度为 len2 。则时间复杂度为“环是否存在的时间复杂度”+O(len1)。

// 2) 查找环的入口

template<typename T>

T* findLoopPort(T* head)

{

// 判断是否有环,五环返回NULL

if (!checkCircle(head))

return NULL;

T* low = head;

T* fast = head;

// low按照步长1增加,fast按照步长2增加,找到碰撞点

while (1)

{

low = low->next;

fast = fast->next->next;

if (low == fast)

break;

}

// 分别从头结点和碰撞节点开始按照步长1增加,遍历链表,第一个节点相同的点是环入口点

low = head;

while (low != fast)

{

low = low->next;

fast = fast->next;

}

return low;

}

3) 环的长度

从碰撞点开始,两个指针p和q,q以一步步长前进,q以两步步长前进,到下次碰撞所经过的操作次数即是环的长度。这很好理解,比如两个运动员A和B从起点开始跑步,A的速度是B的两倍,当A跑玩一圈的时候,B刚好跑完两圈,A和B又同时在起点上。此时A跑的长度即相当于环的长度。

时间复杂度分析:

假设甩尾(在环外)长度为len1(结点个数),环内长度为 len2 ,则时间复杂度为“环是否存在的时间复杂度”+O(len2)。

// 3) 计算环的长度

template<typename T>

int getLoopLength(T* head)

{

if (!checkCircle(head))

return 0;

T* low = head;

T* fast = head;

int length = 0;

// low按照步长1增加,fast按照步长2增加,找到碰撞点,然后接着循环直到下一次碰撞,

// 计算两次碰撞之间的循环次数即为长度

while (1)

{

low = low->next;

fast = fast->next->next;

if (low == fast)

break;

}

while(1)

{

low = low->next;

fast = fast->next->next;

length++;

if (low == fast)

break;

}

return length;

}

4)除环之外的链表长度

// 4) 计算有环链表的尾长度

template<typename T>

int getTailLength(T* head)

{

T* port = findLoopPort(head);

int length = 0;

T* temp = head;

while (temp != port)

{

length++;

temp = temp->next;

}

return length;

}

5)判断两个(无环)单链表是否相交

法一:

将链表A的尾节点的next指针指向链表B的头结点,从而构造了一个新链表。问题转化为求这个新链表是否有环的问题。时间复杂度:为环是否存在的时间复杂度,即O(length(A)+length(B)),使用了两个额外指针。

法二:

两个链表相交,则从相交的节点起,其后的所有的节点都是都是两个链表共有的。因此,如果它们相交,则最后一个节点一定是共有的。因此,判断两链表相交的方法是:遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交。

时间复杂度:O(length(A)+length(B)),但是只用了一个额外指针存储最后一个节点。

// 若都无环,b1==b2==0,尾节点必然相同,是Y字形

if (!b1)

{

while (head1->next != NULL) // 原来的代码head1 != NULL,此处改为head1->next != NULL。因为head1和head2遍历到最后都为NULL的时候必然相等,这样就会造成判断错误

{

head1 = head1->next;

}

while (head2->next != NULL) // 原来的代码head2 != NULL,此处改为head2->next != NULL。因为head1和head2遍历到最后都为NULL的时候必然相等,这样就会造成判断错误

{

head2 = head2->next;

}

if (head1 == head2)

{

return true;

}

}

6) 两个(无环)单链表相交,求相交的第一个节点

将链表A的尾节点的next指针指向链表B的头结点,从而构造了一个环。问题转化为求这个环的入口问题。

时间复杂度:求环入口的时间复杂度。

// 若都无环,b1==b2==0,尾节点必然相同,是Y字形

if (!b1)

{

T* node1 = head1;

T* node2 = head2;

while (node1->next != NULL)// 原来的代码node1 != NULL,此处改为node1->next != NULL。因为node1和node2遍历到最后都为NULL的时候必然相等,这样就会造成判断错误

{

node1 = node1->next;

}

while (node2->next != NULL)// 原来的代码node2 != NULL,此处改为node2->next != NULL。因为node1和node2遍历到最后都为NULL的时候必然相等,这样就会造成判断错误

{

node2 = node2->next;

}

if (node1 == node2)

{

// 相交,把第一个链表的尾节点指向第二个链表

node1->next = head2;

junctionNode = findLoopPort(head1);

return true;

}

}

7) 判断两个(有环)单链表是否相交

分别判断两个链表A、B是否有环。

如果仅有一个有环,则A、B不可能相交。如果相交,则该环必定是A、B共享。

如果两个都有环,则求出A的环入口,判断其是否在B链表上,如果在,则说明A、B相交。

时间复杂度:"环入口问题的时间复杂度"+O(length(B))。

见8)

8) 两个(有环)单链表相交,求相交的第一个节点

分别计算出两个链表A、B的长度LA和LB(环的长度和环到入口点长度之和就是链表长度),参照上面问题3)。

如果LA>LB,则链表A指针先走LA-LB,链表B指针再开始走,则两个指针相遇的位置就是相交的第一个节点。

如果LB>LA,则链表B指针先走LB-LA,链表A指针再开始走,则两个指针相遇的位置就是相交的第一个节点。

时间复杂度:O(max(LA,LB))

// 若有环,则找出链表1的环入口,看是否在链表2上

if (b1)

{

int length1 = getLoopLength(head1) + getTailLength(head1);

int length2 = getLoopLength(head2) + getTailLength(head2);

int len = length2;

T* port = findLoopPort(head1);

if (port != NULL)

{

T* temp = head2;

while (port != temp && (len--) >= 0)

temp = temp->next;

if (port == temp)

{

// 若长度相等,同步寻找相同的节点

if (length1 == length2)

{

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

// 若长度不等,长的先剪掉长度差,然后再同步递增

else if (length1 > length2)

{

int step = length1 - length2;

while (step--)

{

head1 = head1->next;

}

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

else

{

int step = length2 - length1;

while (step--)

{

head2 = head2->next;

}

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

return true;

}

else

{

return false;

}

}

}

完整代码:

/*Title: 7.判断俩个链表是否相交

Date: 2012-11-12*/

#include <iostream>

#include <string>

#include <list>

using namespace std;

// 1) 判断一个单链表是否有环

template<typename T>

bool checkCircle(T* head)

{

if (NULL == head)

return false;

T* low = head;

T* fast = head;

while (low->next != NULL && (fast->next != NULL) && (fast->next)->next != NULL)

{

low = low->next;

fast = (fast->next)->next;

if (low == fast)

{

return true;

}

}

return false;

}

// 2) 查找环的入口

template<typename T>

T* findLoopPort(T* head)

{

// 判断是否有环,五环返回NULL

if (!checkCircle(head))

return NULL;

T* low = head;

T* fast = head;

// low按照步长1增加,fast按照步长2增加,找到碰撞点

while (1)

{

low = low->next;

fast = fast->next->next;

if (low == fast)

break;

}

// 分别从头结点和碰撞节点开始按照步长1增加,遍历链表,第一个节点相同的点是环入口点

low = head;

while (low != fast)

{

low = low->next;

fast = fast->next;

}

return low;

}

// 3) 计算环的长度

template<typename T>

int getLoopLength(T* head)

{

if (!checkCircle(head))

return 0;

T* low = head;

T* fast = head;

int length = 0;

// low按照步长1增加,fast按照步长2增加,找到碰撞点,然后接着循环直到下一次碰撞,

// 计算两次碰撞之间的循环次数即为长度

while (1)

{

low = low->next;

fast = fast->next->next;

if (low == fast)

break;

}

while(1)

{

low = low->next;

fast = fast->next->next;

length++;

if (low == fast)

break;

}

return length;

}

// 4) 计算有环链表的尾长度

template<typename T>

int getTailLength(T* head)

{

T* port = findLoopPort(head);

int length = 0;

T* temp = head;

while (temp != port)

{

length++;

temp = temp->next;

}

return length;

}

// 5) 判断两个链表是否相交

template<typename T>

bool isListJunction(T* head1, T* head2)

{

if (NULL == head1 || NULL == head2)

{

return false;

}

// 如果头结点相同,代表相同的链表,肯定是相交

if (head1 == head2)

{

return true;

}

// 检测是否有环

bool b1 = checkCircle(head1);

bool b2 = checkCircle(head2);

// 若相交,则两个链表要么都无环,要么都有环

if (b1 != b2)

{

return false;

}

// 若都无环,b1==b2==0,尾节点必然相同,是Y字形

if (!b1)

{

while (head1->next != NULL)

{

head1 = head1->next;

}

while (head2->next != NULL)

{

head2 = head2->next;

}

if (head1 == head2)

{

return true;

}

}

// 若有环,则找出链表1的环入口,看是否在链表2上

if (b1)

{

T* port = findLoopPort(head1);

if (port != NULL)

{

T* temp = head2;

int length2 = getLoopLength(head2) + getTailLength(head2);

while (port != temp && (length2--) >= 0)

temp = temp->next;

if (port == temp)

return true;

else

return false;

}

}

return false;

}

// 6) 判断两个链表是否相交,并输出相交节点

template<typename T>

bool isListJunction(T* head1, T* head2, T *&junctionNode)

{

if (NULL == head1 || NULL == head2)

{

return false;

}

// 如果头结点相同,代表相同的链表,肯定是相交

if (head1 == head2)

{

junctionNode = head1;

return true;

}

// 检测是否有环

bool b1 = checkCircle(head1);

bool b2 = checkCircle(head2);

// 若相交,则两个链表要么都无环,要么都有环

if (b1 != b2)

{

return false;

}

// 若都无环,b1==b2==0,尾节点必然相同,是Y字形

if (!b1)

{

T* node1 = head1;

T* node2 = head2;

while (node1->next != NULL)

{

node1 = node1->next;

}

while (node2->next != NULL)

{

node2 = node2->next;

}

if (node1 == node2)

{

// 相交,把第一个链表的尾节点指向第二个链表

node1->next = head2;

junctionNode = findLoopPort(head1);

return true;

}

}

// 若有环,则找出链表1的环入口,看是否在链表2上

if (b1)

{

int length1 = getLoopLength(head1) + getTailLength(head1);

int length2 = getLoopLength(head2) + getTailLength(head2);

int len = length2;

T* port = findLoopPort(head1);

if (port != NULL)

{

T* temp = head2;

while (port != temp && (len--) >= 0)

temp = temp->next;

if (port == temp)

{

// 若长度相等,同步寻找相同的节点

if (length1 == length2)

{

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

// 若长度不等,长的先剪掉长度差,然后再同步递增

else if (length1 > length2)

{

int step = length1 - length2;

while (step--)

{

head1 = head1->next;

}

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

else

{

int step = length2 - length1;

while (step--)

{

head2 = head2->next;

}

while (head1 != head2)

{

head1 = head1->next;

head2 = head2->next;

}

junctionNode = head1;

}

return true;

}

else

{

return false;

}

}

}

return false;

}

template <typename T>

struct listNode

{

T val;

listNode *pre;

listNode *next;

listNode()

{

pre = NULL;

next = NULL;

}

listNode(T value)

{

val = value;

pre = NULL;

next = NULL;

}

};

int main(int argc,char** argv)

{

string first = "my is";

string second = "your";

string three = "is";

string four = "king";

string five = "name";

string first2 = "speak";

string second2 = "what";

list<string> firstlist;

firstlist.push_back(first);

firstlist.push_back(second);

firstlist.push_back(three);

firstlist.push_back(four);

firstlist.push_back(five);

list<string> secondlist;

secondlist.push_back(first2);

secondlist.push_back(second2);

secondlist.push_back(four);

secondlist.push_back(five);

// 链表1,无环

listNode<string> *head1 = new listNode<string>(first);

listNode<string> *pnode2 = new listNode<string>(second);

head1->next = pnode2;

listNode<string> *pnode3 = new listNode<string>(three);

pnode2->next = pnode3;

listNode<string> *pnode4 = new listNode<string>(four);

pnode3->next = pnode4;

listNode<string> *pnode5 = new listNode<string>(five);

pnode4->next = pnode5;

// 链表2,无环

listNode<string> *head2 = new listNode<string>(first2);

listNode<string> *pnode22 = new listNode<string>(second2);

head2->next = pnode22;

pnode22->next = pnode4;

pnode4->next = pnode5;

// 链表1、2相交

bool bJunction = isListJunction(head1,head2);

std::cout<< bJunction<<std::endl;

std::cout<< checkCircle(head1)<<endl;

std::cout<< checkCircle(head2)<<endl;

string first3 = "1";

string second3 = "2";

string three3 = "3";

string four3 = "4";

string five3 = "5";

string six3 = "6";

string seven3 = "7";

// 链表3,有环

listNode<string> *head3 = new listNode<string>(first3);

listNode<string> *pnode32 = new listNode<string>(second3);

head3->next = pnode32;

listNode<string> *pnode33 = new listNode<string>(three3);

pnode32->next = pnode33;

listNode<string> *pnode34 = new listNode<string>(four3);

pnode33->next = pnode34;

listNode<string> *pnode35 = new listNode<string>(five3);

pnode34->next = pnode35;

listNode<string> *pnode36 = new listNode<string>(six3);

pnode35->next = pnode36;

pnode36->next = pnode33;

cout<<findLoopPort(head3)->val<<endl;

cout<<getLoopLength(head3)<<endl;

// 链表4,有环

listNode<string> *head4 = new listNode<string>(seven3);

head4->next = pnode32;

cout<<isListJunction(head3,head4)<<endl;

cout<< "the length of list3 : "<<getLoopLength(head3) + getTailLength(head3)<<endl;

cout<< "the length of list4 : "<<getLoopLength(head4) + getTailLength(head3)<<endl;

listNode<string> *junctionNode = new listNode<string>;

cout<<isListJunction(head3,head4,junctionNode)<<endl;

cout<<"list3 与 list4的交点: "<<junctionNode->val<<endl;

system("pause");

return 0;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: