数据结构-链表-奇思妙想 基本函数 1,构造节点: //定义节点类型 struct Node { int value; Node*next; }; 2,分配节点 //之所以要分配节点原因是需要在分配函数中进行初始化,并且也利于判断是否分配成功。 Node* applyNode(); 3,在头部增加节点 //增加节点在头部(无头结点),返回值的原因是由于传入并非指针的引用。 Node* addNodeH(Node* Head,Node* InsertNode); 4,在尾部增加节点 //增加节点在尾部(无头结点),返回值的原因是由于传入并非指针的引用。 Node* addNodeT(Node* Head,Node* InsertNode); 5,以升序方式增加节点 Node* addNodeSort(Node* Head,Node* InsertNode); 6,构造链表 //没有额外的表头结点。 //选择参数choose分别对应以何种方式构造链表,1为头部增加节点;2为尾部增加节点;3为升序增加节点。 Node* createList(int n,int choose); 7,打印链表 void printList(Node*Head); 8,释放链表 void freeList(Node*& Head); 9,链表节点数 int numOfNodes(Node* Head); 10,定位函数 //传入参数i表示第几个节点(从1开始),返回该节点指针 Node* locateNodeI(Node*Head,int i); 11,查找函数 //查找值为value的链表 int SearchList(Node*Head,int value); 12,删除节点 //删除位置i的节点 bool deleteNodeI(Node*&Head,int i); 13,排序函数 //冒泡排序链表,具体的做法是“狸猫换太子”,即只交换节点中的值,对链表结构不做改动。 void sortList(Node*& Head); 以上函数参见代码1。 高级函数 1.单链表反转 思路1:O(n^2). 我的做法是“狸猫换太子”,不进行改动链表结构,只首尾交换len/2次。但是在本函数中用到了定位函数,定位函数实际上是遍历了一遍整个链表,所以综合效率很低,达到O(n^2). void reverseList(Node*Head) 思路2:O(n). 就最一般的情况而言(没有之前写的那么多辅助函数,即条件单纯为只有Head指向一个单链表)。那么可以实现O(n)效率。做法是用三个相邻的指针进行遍历,在遍历的途中,更改指针方向。当然要注意链表数目分情况,和拆链的处理。 Node* reverseList2(Node*Head) 2.找出单链表的倒数第4个元素 思路1:O(2n) 先遍历一遍链表记录节点个数。然后定位该位置count-3,定位函数实际上也是遍历一遍,所以总效率O(n)+O(n) bool findLast4th1(Node*Head,int &ans) 思路2:O(n) 如果题目限制要求,仅允许遍历一遍,则可以按如下方法进行。先定义两个指针,第一个指针先走4步,然后从这时开始,第一个指针和第二个指针同时继续走,即第一个指针和第二个指针相差4步。则第二个指针到头时,第一个指针指向倒数第四个。注意要考虑链表长度。 bool findLast4th2(Node*Head,int &ans) 思路3:O(n) 做一个数组arr[4],让我们遍历单链表,把第1个、第5个、第9个……第4N+1个扔到arr[0],把第2个、第6个、第10个……第4N+2个扔到arr[1],把第3个、第7个、第11个……第4N+3个扔到arr[2],把第4个、第8个、第12个……第4N个扔到arr[3],这样随着单链表的遍历结束,arr中存储的就是单链表的最后4个元素,找到最后一个元素对应的arr[i],让k=(i+1)%4,则arr[k]就是倒数第4个元素。如果不易理解,画个图就好了。注意增加的空间只需要4个,所以是常数级的。比如加到第5个节点时就会把arr[0]中的值冲掉。 bool findLast4th3(Node*Head,int &ans) 3.找出单链表的中间元素 思路1:O(2n) 在函数的支持下,直接求整个链表的长度,然后定位中间元素。 bool getMiddleOne1(Node*Head,int&ans) 思路2:O(n) 如果仍要求只遍历一遍。类似于上题,还是使用两个指针first和second,只是first每次走一步,second每次走两步: bool getMiddleOne2(Node*Head,int&ans) 4.删除无头单链表的一个节点 思路: 注意这里的要求是无头链表,即未知Head指针。但是知道的是current指针指向该链表的某一位置。现在希望删除该指针,而不影响整个链表。即虽然不知道Head指针,但是该链还是完整的。 首先需要明确一点,即current指针之前的链表段落是不可能知道的。这是由链表的单向性决定的。没有任何技术可以做到这一点。 其次,删除链表某节点,该节点不能是首节点,因为首节点一旦删除,Head无法找到该链表了。 再次,删除链表某节点,该节点不能是尾节点,因为尾节点一旦删除,则尾节点的前一节点的指针域无法置0(因为单链无法回溯)。 所以题意解释为:删除无头单链表的一个节点(既非首节点也非尾节点)。 解法是利用“狸猫换太子”。首先复制current指针的下一个节点的value到current节点中。然后删除current的下一节点。 void deleteNoHeadList(Node*Head,Node*Current) 4+.增加无头单链表的一个节点,一个指针current指向单链表中的一个节点,在该节点之前增加一个节点insertNode。 思路: 思路还是“狸猫换太子”,即在current之后增加一个节点insertNode,然后交换insertNode和current的值。 由于在current之后增加节点这个操作在current指向首尾都可以实现。 所以这道问题转化为:增加无头单链表的一个节点,current指针指向该链表某节点(可以为首尾),在其之前增加节点p。 void addNoHeadList(Node*Head,Node*Current,Node*insertNode) 5.两个不交叉的有序链表的合并 思路:O(len1+len2) 合并的办法如下,首先用Node*& 方式传入两个链表的头指针Head1,Head2。用指针引用是因为最后要修改Head1和Head2均为NULL。否则可能被其他人误引用了。然后定义一个合并后的链表的头指针和尾指针Head和Tail。然后不断比较Head1和Head2的首元素,加入到新的合并的链表中。注意一点这里的加入并不是先增加申请一个节点分配,然后删除释放原来的节点。而是直接将指针指向。也就是说在合并的过程中只是指针指向改变了,完全没有申请新的内存和释放节点空间。最后如果有一个Head1或Head2的已经空了,则直接将剩余链表连接到Head即可。当然要注意很多细节。 Node* mergeTwoList(Node*& Head1,Node*& Head2) 6.有个二级单链表,其中每个元素都含有一个指向一个单链表的指针。写程序把这个二级链表称一级单链表。 思路: 注意要重新定义二级单链表的结构,具体的算法是:把所有的下级单链表顺次连接。即可。程序代码略。 7.单链表交换任意两个元素(不包括表头) 思路: 利用“狸猫换太子”,不破坏链表结构,只交换二者Node* cur1和Node* cur2的指向的值。程序代码略。 其中的任意两个元素由外界给定该两个节点的指针。 8.判断单链表是否有环(6形状)?如何找到环的“起始”点?如何知道环的长度? 思路: 注意分析题意,题意并非是说单链表完全成O形状的环,而是说单链表成6形状的环。 首先判断是否有环:为此我们建立两个指针,从Head一起向前跑,一个步长为1,一个步长为2,用 while(直到步长2的跑到结尾) { 检查两个指针是否相等,直到找到为止。 } 来进行判断。 原因是,在这场跑步中,结束循环有两种可能,其一是原来无环,所以2先跑到结尾,因为2比1快,二者不可能相等。其二是原来是有环的,因为这场赛跑永远没有z终点,但是在环中,由于2比1快,因而必定套圈,也即2追上了1,这在无环中是不可能出现的情况。 而进行计算环的长度,只要找到交汇点,然后在圈中跑一次就可以了。 int getCircleLength(Node* cross) bool judgeCircleExists(Node* Head) 9.判断两个单链表是否相交 注意这里是判断是否相交。对于判断问题来讲,相对还是比较简单的。注意单链表并非不能有重复元素。 思路1:O(len1*len2) 把第一个链表的指针值逐项存在hashtable中,遍历第2个链表的每一项的指针值,如果能在第一个链表中找到,则必然相交。但是C++的STL模板中的hash不太会用。所以我使用了set集合,不过貌似set集合是使用遍历的方式来查找元素是否在集合中的,所以效率是比较低的,至少在O(len1*len2)级别。 bool judgeIntersectList1(Node* Head1,Node* Head2) 思路2:O(len1+len2) 把一个链表A接在另一个链表B的末尾,如果有环,则必然相交。如何判断有环呢?从A开始遍历,如果能回到A的表头,则肯定有环。 注意,在返回结果之前,要把刚才连接上的两个链表断开,恢复原状。 bool judgeIntersectList2(Node* Head1,Node* Head2) 思路3:O(len1+len2) 如果两个链表的末尾元素相同(指针相同,即为同一个元素,而非值相等),则必相交。 bool judgeIntersectList3(Node* Head1,Node* Head2) 10.两个单链表相交,计算相交点 思路1: 分别遍历两个单链表,计算出它们的长度M和N,假设M比N大,则长度M的链表先前进M-N,然后两个链表同时以步长1前进,前进的同时比较当前的元素,如果相同,则必是交点。 Node* getIntersectPoint(Node* Head1,Node* Head2) 思路2: 将指针p1,p2定位到两个链表的尾部,然后同时将两个指针前移(不可以,因为是单向链表) 11.用链表模拟大整数加法运算 思路: 对于高精度大数计算,没有数组那么高效,具体数组的做法参见OJ高精度,链表的好处是可以定义节点,其中包含指数次数和值两部分,比如20001可以表示为(2,4)->(1,0)->NULL其中2表示值,4表示10的4次方。这样的话如果数属于稀疏型的则以较少的空间保存了值。具体程序略。 12.单链表排序 思路: 参见基本函数13://冒泡排序链表,具体的做法是“狸猫换太子”,即只交换节点中的值,对链表结构不做改动。 void sortList(Node*& Head); 13.删除单链表中重复的元素 思路: 用Hashtable辅助,遍历一遍单链表就能搞定。同高级函数9的原因,我不太会使用C++STL中的hash。而如果使用set集合来存储链表中的所有的值,实际上效率和每次重新遍历单链表是一样的。“用了c++标准库中的set来保存访问过的元素,所以很方便的就可以判断当前节点是否在set集合中,直接使用set提供的find函数就可以了。而且使用set的查找在时间复杂度上比较低。”我不太清楚STL中set集合的实现方式,如果是基于类似hash结构的话,那自然效率O(1),而如果是数组的话,实际在遍历一遍,所以效率O(n)。不过貌似后者的可能性大一些。 void DeleteDuplexElements(Node*Head); 基本函数代码: ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 2,分配节点 //分配节点 //将分配内存和初始化该节点放在一个函数中 Node* applyNode() { Node* newNode; if((newNode=(Node*)malloc(sizeof(Node)))==NULL) { cout<<"分配内存失败!"<<endl; ::exit(0); } //建立该节点信息: cout<<"请输入本节点值:"<<endl; cin>>newNode->value; newNode->next=NULL; return newNode; } 3,在头部增加节点 //在表头增加节点 //在头指针所指向的链表中增加一个节点,插入头部 //这里必须要返回Node*来进行更新,因为传入的Head是Node*类型,而非Node*& Node* addNodeH(Node* Head,Node* InsertNode) { if(Head==NULL) { Head=InsertNode; } else { InsertNode->next=Head; Head=InsertNode; } return Head; } 4,在尾部增加节点 //在表尾增加节点 //在头指针所指向的链表中增加一个节点,插入尾部 //这里必须要返回Node*来进行更新,因为传入的Head是Node*类型,而非Node*& Node* addNodeT(Node* Head,Node* InsertNode) { if(Head==NULL) { Head=InsertNode; } else { Node* p=Head; while(p->next!=NULL) { p=p->next; } p->next=InsertNode; } return Head; } 5,以升序方式增加节点 //以升序增加节点 //这里必须要返回Node*来进行更新,因为传入的Head是Node*类型,而非Node*& Node* addNodeSort(Node* Head,Node* InsertNode) { if(Head==NULL) { Head=InsertNode; } else { Node* p=Head; //注意,这里把(p->value)<(InsertNode->value)放在p->next!=NULL前面是有原因的,这是避免为了考虑在Head->[4]加入[1]的情况 while((p->value)<(InsertNode->value)&&p->next!=NULL) { p=p->next; } if((p->value)>=(InsertNode->value))//因为((p->value)>=(InsertNode->value))而退出!表示在p前增加节点(狸猫换太子) { //先在p后增加节点 InsertNode->next=p->next; p->next=InsertNode; //再交换p和InsertNode的值 swap(p->value,InsertNode->value); } else//因为(p->next==NULL)而退出!表示在尾增加节点 { p->next=InsertNode; } } return Head; } 6,构造链表 //建立n个节点的链表 choose=1,在表头加入,choose=2在表尾加入,choose=3按value值升序加入 Node* createList(int n,int choose) { Node *Head=NULL,*p=NULL; for(int i=0;i<n;i++) { p=applyNode(); if(choose==1) { Head=addNodeH(Head,p); } else if(choose==2) { Head=addNodeT(Head,p); } else if(choose==3) { Head=addNodeSort(Head,p); } } return Head; } 7,打印链表 //遍历链表并输出 void printList(Node*Head) { Node*p=Head; while(p!=NULL) { cout<<p->value<<"->"; p=p->next; } cout<<"NULL"<<endl; } 8,释放链表 //释放链表 void freeList(Node*& Head) { Node* tmp=Head; while(tmp!=NULL) { Head=Head->next; free(tmp); tmp=Head; } Head=NULL; } 9,链表节点数 //数节点个数 int numOfNodes(Node* Head) { int count=0; while(Head!=NULL) { count++; Head=Head->next; } return count; } 10,定位函数 //定位第i个节点,i从1开始 Node* locateNodeI(Node*Head,int i) { //cout<<"定位"<<i<<"位置"<<endl; Node* pos=NULL; int count=numOfNodes(Head); if(i<=0||i>count) { cout<<"定位越界!"<<endl; } else { pos=Head; for(int j=1;j<i;j++) { pos=pos->next; } } return pos; } 11,查找函数 //查找值value并返回第一个出现该值的位置,如果需要引用其指针,可以再locate该位置 int SearchList(Node*Head,int value) { Node* p=Head; int pos=0; bool find=false; while(p!=NULL) { pos++; if(p->value==value) { find=true; break; } p=p->next; } if(find) return pos; else return -1; } 12,删除节点 //删除某位置i的节点 bool deleteNodeI(Node*&Head,int i) { Node* p=locateNodeI(Head,i); if(p==NULL) { return false; } else { if(p==Head)//说明p是头节点。 { Head=p->next; free(p); } else { Node* prep=locateNodeI(Head,i-1);//定位前一个,必定存在 prep->next=p->next; free(p); } return true; } } 13,排序函数 //链表排序 //排序的方法是不破坏结构,有“狸猫换太子”的意思,只进行value的交换,不破坏链表结构 void sortList(Node*& Head) { int count=numOfNodes(Head); if(count==0||count==1) { return ; } //冒泡排序 bool exchange; for(int i=2;i<=count;i++) { exchange=false; for(int j=count;j>=i;j--) { Node* p1=locateNodeI(Head,j); Node* p2=locateNodeI(Head,j-1); if(p1->value<p2->value) { exchange=true; swap(p1->value,p2->value); } } if(!exchange) break; } } ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 高级函数代码: ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 1.单链表反转1 //单链表反转(O(n^2)) void reverseList(Node*Head) { int count=numOfNodes(Head); //“狸猫换太子”,首尾交换 for(int i=1;i<=count/2;i++) { Node* p1=locateNodeI(Head,i); Node* p2=locateNodeI(Head,count+1-i); swap(p1->value,p2->value); } } 1.单链表反转2 //单链表反转(O(n)) Node* reverseList2(Node*Head) { if(Head==NULL||Head->next==NULL)//空链和单节点 { return Head; } Node* p1=Head; Node* p2=Head->next; Node* p3=Head->next->next; if(p3==NULL)//只有两个节点 { p1->next=NULL; p2->next=p1; Head=p2; return Head; } else//至少三个节点 { p1->next=NULL; while(p3!=NULL) { p2->next=p1; p1=p2; p2=p3; p3=p3->next; } p2->next=p1; Head=p2; return Head; } } 2.找出单链表的倒数第4个元素1 //查找倒数第四个元素,传入ans中 O(2N) bool findLast4th1(Node*Head,int &ans) { //先确定节点个数: int count=numOfNodes(Head); //定位count-4 Node* p=locateNodeI(Head,count-3); if(p!=NULL) { ans=p->value; return true; } else { return false; } } 2.找出单链表的倒数第4个元素2 //查找倒数第四个元素,传入ans中 O(N),只遍历一遍 bool findLast4th2(Node*Head,int &ans) { Node* p1=Head; Node* p2=Head; //p1先走4步。 for(int i=0;i<4;i++) { if(p1!=NULL) { p1=p1->next; } else { return false;//肯定链表长度不够 } } //同步移动 while(p1!=NULL) { p1=p1->next; p2=p2->next; } ans=p2->value; return true; } 2.找出单链表的倒数第4个元素3 //查找倒数第四个元素,传入ans中 O(N) bool findLast4th3(Node*Head,int &ans) { int arr[4]; Node* p=Head; int i=0; int count=0; while(p!=NULL) { arr[i]=p->value; p=p->next; i=(i+1)%4; count++; } if(count<4) { return false; } else { ans=arr[i]; return true; } } 3.找出单链表的中间元素1 //获取中间元素O(2n) bool getMiddleOne1(Node*Head,int&ans) { int count=numOfNodes(Head); if(count==0) { return false; } else { Node* p=locateNodeI(Head,(count+1)/2); ans=p->value; return true; } } 3.找出单链表的中间元素2 //获取中间元素O(n) //类似于上题,还是使用两个指针first和second,只是first每次走一步,second每次走两步: bool getMiddleOne2(Node*Head,int&ans) { if(Head==NULL)//空链表 { return false; } else { Node*first=Head; Node*second=Head->next; while(second!=NULL&&second->next!=NULL) { first=first->next; second=second->next; second=second->next; } ans=first->value; return true; } } 4.删除无头单链表的一个节点 //删除无头单链表的非首尾节点"狸猫换太子"; void deleteNoHeadList(Node*Head,Node*Current) { Node* p=Current->next; //一定是非首尾节点,否则会出错 Current->value=Current->next->value; Current->next=Current->next->next; free(p); } 4+.增加无头单链表的一个节点,一个指针current指向单链表中的一个节点,在该节点之前增加一个节点insertNode。 //增加无头单链表的一个节点,current指针指向该链表某节点(可以为首尾),在其之前增加节点insertNode。 void addNoHeadList(Node*Head,Node*Current,Node*insertNode) { insertNode->next=Current->next; Current->next=insertNode; swap(Current->value,insertNode->value); } 5.两个不交叉的有序链表的合并 //合并两个有序的链表 Node* mergeTwoList(Node*& Head1,Node*& Head2) { Node* Head=NULL;//合并后的链表 Node* Tail=NULL;//合并后链表的尾指针 //p1,p2遍历两个链表 Node* p1=Head1; Node* p2=Head2; while(!(p1==NULL||p2==NULL)) { if(p1->value<=p2->value) { if(Head==NULL)//第一个节点 { Head=p1; Tail=Head; } else { Tail->next=p1; Tail=Tail->next; } p1=p1->next; } else { if(Head==NULL)//第一个节点 { Head=p2; Tail=Head; } else { Tail->next=p2; Tail=Tail->next; } p2=p2->next; } } if(p1!=NULL) { if(Head!=NULL) { Tail->next=p1; } else { Head=p1; } } else if(p2!=NULL) { if(Head!=NULL) { Tail->next=p2; } else { Head=p2; } } Head1=NULL; Head2=NULL; return Head; } 8.判断单链表是否有环(6形状)?如何找到环的“起始”点?如何知道环的长度?1 //计算单链表成环,环的长度,输入的参数为成环的交汇点。 int getCircleLength(Node* cross) { int len=1; Node* p=cross; while(p->next!=cross)//千万不能写作p->next!=p { len++; p=p->next; } return len; } 8.判断单链表是否有环(6形状)?如何找到环的“起始”点?如何知道环的长度?2 //判断单链表是否有环,并且返回环的长度 bool judgeCircleExists(Node* Head,int &len) { if(Head==NULL)//空链 { return false; } else if(Head->next==Head)//1个节点且成环 { return true; } else if(Head->next==NULL)//1个节点不成环 { return false; } //至少两个节点情形 //初始化跑步机 Node* p1=Head;//跑步者1号,跑到第1个节点 Node* p2=Head->next;//跑步者2号,跑到第2个节点 while(p2!=NULL&&p2->next!=NULL)//利用了&&短路 { p1=p1->next; p2=p2->next->next; if(p1==p2) { //此时p1(p2)即为交汇点 len=getCircleLength(p1); return true; } } return false; } 9.判断两个单链表是否相交1 //判断两个单链表是否相交(Y型) bool judgeIntersectList1(Node* Head1,Node* Head2) { set<Node*>s; Node* p1=Head1; Node* p2=Head2; while(p1!=NULL) { s.insert(p1); p1=p1->next; } while(p2!=NULL) { if(s.find(p2)!=s.end()) { s.clear(); return true; } p2=p2->next; } s.clear(); return false; } 9.判断两个单链表是否相交2 //判断两个单链表是否相交(Y型) bool judgeIntersectList2(Node* Head1,Node* Head2) { if(Head1==NULL||Head2==NULL) { return false; } Node* p1=Head1; Node* p2=Head2; //先找到链表2的末尾,由p2指向 while(p2->next!=NULL) { p2=p2->next; } //将链表1的表头与链表2的表尾连接 p2->next=p1; //遍历链表1,如果回到了链表1表头,则相交 while(p1!=NULL) { if(p1->next==Head1) { //记得恢复原状: p2->next=NULL; return true; } p1=p1->next; } //记得恢复原状: p2->next=NULL; return false; } 9.判断两个单链表是否相交3 //判断两个单链表是否相交(Y型) bool judgeIntersectList3(Node* Head1,Node* Head2) { if(Head1==NULL||Head2==NULL) { return false; } Node* p1=Head1; Node* p2=Head2; //p1与p2记录两链表的尾指针 while(p1->next!=NULL) { p1=p1->next; } while(p2->next!=NULL) { p2=p2->next; } if(p1==p2) { return true; } return false; } 10.两个单链表相交,计算相交点 //两链表相交,计算相交点: Node* getIntersectPoint(Node* Head1,Node* Head2) { int len1=numOfNodes(Head1); int len2=numOfNodes(Head2); int initMove=abs(len1-len2); Node* p1=Head1; Node* p2=Head2; if(len1>len2) { for(int i=0;i<initMove;i++) { p1=p1->next; } } else { for(int i=0;i<initMove;i++) { p2=p2->next; } } while(p1!=NULL&&p2!=NULL) { if(p1==p2) { return p1; } p1=p1->next; p2=p2->next; } return NULL; } 12.单链表排序 //链表排序 //排序的方法是不破坏结构,有“狸猫换太子”的意思,只进行value的交换,不破坏链表结构 void sortList(Node*& Head) { int count=numOfNodes(Head); if(count==0||count==1) { return ; } //冒泡排序 bool exchange; for(int i=2;i<=count;i++) { exchange=false; for(int j=count;j>=i;j--) { Node* p1=locateNodeI(Head,j); Node* p2=locateNodeI(Head,j-1); if(p1->value<p2->value) { exchange=true; swap(p1->value,p2->value); } } if(!exchange) break; } } 13.删除单链表中重复的元素 //删除单链表中的重复元素:使用set集合来实现: void DeleteDuplexElements(Node*Head) { if(Head==NULL||Head->next==NULL)//链表为空或者只有一个元素 { return ; } //以下至少两个元素 set<int>s; Node* p1=Head; Node* p2=Head->next; s.clear(); s.insert(p1->value); while(p2!=NULL)//要删除的不可能是链表头,因为如果是链表头,则集合还为空。 { if(s.find(p2->value)==s.end())//没有 { s.insert(p2->value); p2=p2->next; p1=p1->next; } else//已经有,则要删除该节点 { //不可能是链表头 //如果是链表尾 if(p2->next==NULL) { p1->next=NULL; free(p2); p2=NULL; } else { p1->next=p2->next; free(p2); p2=p1->next; } } } } 题目: 给出两个单向链表的头指针,比如h1、h2,判断这两个链表是否相交。这里为了简化问题,我们假设两个链表均不带环。 分析与解法 这样的一个问题,也许我们平时很少考虑。但是在一个大的系统中,如果出现两个链表相交的情况,而且释放了其中一个链表的所有节点,那样就会造成信息的丢失,并且另一个与之相交的链表也会受到影响,这是我们不希望看到的。在特殊的情况下,的确需要出现相交的两个链表,我们希望在释放一个链表之前知道是否有其他链表跟当前这个链表相交。 解法一 直观的想法[暴力穷举] 看到这个问题,我们的第一个想法估计都是,“不管三七二十一”,先判断第一个链表的每个节点是否在第二个链表中。这种方法的时间复杂度为O(Length(h1)*Length(h2))。可见,这种方法很耗时间。 解法二 利用计数的方法[hash表] 很容易想到,如果两个链表相交,那么这两个链表就会有共同的节点。而节点地址又是节点的唯一标识。所以,如果我们能够判断两个链表中是否存在地址一致的节点,就可以知道这两个链表是否相交。一个简单的做法是对第一个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果它在hash表中出现,那么说明第二个链表和第一个链表有共同的节点。这个方法的时间复杂度为O(max(Length(h1)+Length(h2)))。但是它同时需要附加O(Length(h1))的存储空间,以存储哈希表。虽然这样做减少了时间复杂度,但是是以增加存储空间为代价的。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能较少存储空间? 解法三[链1头接在尾上,成环,判断成环] 由于两个链表都没有环,我们可以把第二个链表接在第一个链表后面,如果得到的链表有环,则说明这两个链表相交。否则,这两个链表不想交。这样我们就把问题转化为判断一个链表是否有环。 判断一个链表是否有环,也不是一个简单的问题,但是需要注意的是,在这里如果有环,则第二个链表的表头一定在环上,我们只需要从第二个链表开始遍历,看是否会回到起始点就可以判断出来。最后,当然可别忘了恢复原来的状态,去掉从第一个链表到第二个链表表头的指向。 这个方法总的时间复杂度也是线性的,但只需要常数的空间。 解法四[判断链1和链2最后一个节点是否是同一个节点] 仔细观察题目中的图示,如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表所共有的。那么我们能否利用这个特点简化我们的解法呢?困难在于我们并不知道哪个节点必定是两个链表共有的节点(如果它们相交的话)。进一步考虑“如果没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。 先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不想交。这样我们就得到了一个时间复杂度,它为O(Length(h1)+Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法比解法三更胜一筹。