• 链表面试总结


    链表是最基本的数据结构,凡是学计算机的必须的掌握的,在面试的时候经常被问到,关于链表的实现,百度一下就知道了。在此可以讨论一下与链表相关的练习题。

    1、在单链表上插入一个元素,要求时间复杂度为O(1)

    解答:一般情况在链表中插入一元素是在末尾插入的,这样需要从头遍历一次链表,找到末尾,时间为O(n)。要在O(1)时间插入一个新节点,可以考虑每次在头节点后面插入,即每次插入的节点成为链表的第一个节点。

    2、给定一个链表,判断是否有环。

    解答:这个是一个经典的问题了,思路也很简单,我们首先设置两个指针p1,p2同时指向链表的头部,然后p1每次向后走1步,p2每次向后走2步。如果有环,那么有一步会出现p1=p2,如果p2已经到达了尾结点,则无环。复杂度:时间:O(n),空间:O(1)

    扩展:给定一个链表,找出环的入口位置。思路也是一样,用p1,p2指针。只是需要多做一步,那就是当p1=p2的时候,将p1重新指向链表的头结点,然后p1和p2都每次向后走一步,下一次p1=p2的结点就是环的入口。复杂度:时间:O(n),空间:O(1)

    3、遍历单链表一次,找出链表中间节点

    解答:定义两个指针p和q,初始都指向链表头节点。然后开始向后遍历,p每次移动2步,q移动一步,当p到达末尾的时候,p正好到达了中间位置。

    4、单链表逆置,不允许额外分配存储空间,不允许递归,可以使用临时变量,执行时间为O(n)

    解答:这个题目在面试笔试中经常碰到,基本思想上将指针逆置。如下图所示:

    实现:

    [cpp] view plain copy
     
    1. Node* reverse_list(Node *head){  
    2.   Node *cur=head;  
    3.   Node *pre = NULL;  
    4.   Node *post = cur->next;  
    5. //    Node *reverse_head = cur;  
    6.   while(post){  
    7.       cur->next = pre;  
    8.       pre = cur;  
    9.       cur = post;  
    10.         post = post->next;  
    11.   }  
    12.   cur->next = pre;  
    13. //    reverse_head = cur;  
    14.   return cur;  
    15. }  

    扩展:链表翻转。给出一个链表和一个数k,比如,链表为1→2→3→4→5→6,k=2,则翻转后2→1→6→5→4→3,若k=3,翻转后3→2→1→6→5→4,若k=4,翻转后4→3→2→1→6→5,用程序实现。

    实质是也是逆置,只不过是两个链表逆置后再串联起来。实现如下:

    [cpp] view plain copy
     
    1. bool rotate_list(Node *head,int k,Node* &newhead){  
    2.     if(k < 0)  
    3.         return false;  
    4.     else if(0 == k)  
    5.         return true;  
    6.     int len = 0;  
    7.     Node *node=head;  
    8.     while(node){  
    9.         ++len;  
    10.         node = node->next;  
    11.     }  
    12.     if(k > len)  
    13.         return false;  
    14.     Node *one_end,*two_start;  
    15.     node = head;  
    16.     Node *post = node->next;  
    17.     int n=k;  
    18.     if(1 == n){  
    19.     }else{  
    20.         while(n > 1){// rotate sublist one  
    21.             node->next = post->next;  
    22.             post->next = head;  
    23.             head = post;  
    24.             post = node->next;  
    25.             --n;  
    26.         }  
    27.     }  
    28.   
    29.     if(len-k <= 1){ // rotate sublist two  
    30.     }else{  
    31.         one_end = node;  
    32.         node = post;  
    33.         post = post->next;  
    34.         two_start = node;  
    35.         n = len-k;  
    36.         while(n>1){  
    37.             one_end->next = post;  
    38.             node->next = post->next;  
    39.             post->next = two_start;  
    40.             two_start = post;  
    41.             post = node->next;  
    42.             --n;  
    43.         }  
    44.     }  
    45.     newhead = head;  
    46.     return true;  
    47. }  

    5、用一个单链表L实现一个栈,要求push和pop的操作时间为O(1)

    解答:根据栈中元素先进后出的特点,可以在链表的头部进行插入和删除操作

    6、用一个单链表L实现一个队列,要求enqueue和dequeue的操作时间为O(1)

    解答:队列中的元素是先进先出,在单链表结构中增加一个尾指针,数据从尾部入队,从头

    部入队。

    7、给定两个链表(无环),判断是否有相交。

    解答:首先明确一点,如果两个链表相交,那么从第一个交点开始到尾结点结束,所有的结点都是公共结点。所以,两个有公共结点而部分重合的链表,拓扑形状看起来像一个Y,而不可能像X。

    这也就是说,如果两个链表相交,那么这两个链表的尾结点肯定是公共结点,如果尾结点不是公共结点,那么这两个链表肯定不相交。

    所以我们可以如下操作:依次遍历两个链表,最后判断尾结点是否相同,如果相同,则相交,如果不相同,则不相交。复杂度:时间:O(m+n),空间:O(1)

    或者一个链表的头结点指向另一个链表的尾节点,判断是否有环。

    8、给定两个链表(无环),找到第一个公共节点。

    解答:我们最容易想到的是从尾结点开始挨个向前比较,最后一个相同的就是第一个公共结点。(从后往前遍历)

    但是单链表只能从前往后进行遍历,如果想要从后往前的话则需要先从前向后遍历一次,同时用来记录每一个结点,最后出栈,然后挨个对比,这样的确可行,但是却要额外付出O(m+n)的空间,时间复杂度O(mn)。(单链表+栈)

    仔细想想,我们可以先分别遍历两个单链表,记录长度m和n(无妨假设m>n),然后先让长度为m的链表向后走(m-n)步,接着两个链表同时向后遍历,第一个相同的结点就是要求的第一个公共结点。复杂度:O(m+n)m,n分别为两个链表的长度;空间:O(1)

    PS:另外还有一种巧妙的方法是把在一个链表尾部插入另一个链表,然后判断合成的新链表是否有环。环入口即为第一个公共点。

    可参考:http://blog.csdn.net/wcyoot/article/details/6426436

    扩展:两个链表,找出他们的第一个交点,要求每个链表只能遍历一次,可以对链表进行任何操作,空间O(1).

    题目告诉说可以对链表进行任何操作,这是一个没有用到的条件(大家一定要注意到题目中没有用到的条件,往往是解题的关键所在)。

    1.遍历第一个链表List1,将每一个节点的next都置为NULL。

    2.遍历第二个链表List2,List2的尾节点就是第一个交点。

     

    9、 给定2个链表,求这2个链表的并集(链表)和交集(链表)。不要求并集(链表)和交集(链表)中的元素有序。输入:List1:10->15->4->20,List2:8->4->2->10输出:交集(链表):4->10;并集(链表):2->8->20->4->15->10

    法一:简单直观的方法:

    InterSection(list1,list2):初始化结果链表为空,遍历链表1,在链表2中查找它的每一元素,如果链表2中也有这个元素,则将该元素插入到结果链表中

         Union(list1,list2): 初始化结果链表为空,将链表1中的所有元素都插入到结果链表中。遍历链表2,如果结果链表中没有该元素,则插入,否则跳过该元素。

    法二:可适应归并排序,not clear。

    法三:Hash法

    Union(list1,list2),首先用链表1初始化结果链表,创建一个空的hash表。遍历链表1,将链表中的元素插入到hash表。然后遍历list2,对于list2中的元素,如果hash表中不存在该元素,则同时将该元素插入到结果链表中,如果hash表中已经存在,则忽略该元素,继续遍历下一个元素。

    InterSection(list1,list2),首先初始化结果链表为NULL,创建一个空的hash表。遍历list1,将list1中的每一个元素都插入到hash表中。然后遍历list2,对于list2中的元素,如果已经存在于hash表中,则将该元素插入到结果链表,如果不存在与hash表中,则忽略该元素,继续遍历下一个元素。

    参考:http://blog.csdn.net/lalor/article/details/7430631

    10、从单链表返回倒数第n个元素

    普通,基本思路就是用栈,一一压栈,再弹栈,第n个元素就可出来。

    进阶,看到栈,就应该想到递归,递归是天然的栈。用全局变量,实现如下:

    [cpp] view plain copy
     
    1. Node* pn_elem = NULL;  
    2. int nn;  
    3. void recursive(Node* node){  
    4.   if(!node)   return ;  
    5.   recursive(node->next);  
    6.   if(1==nn)   pn_elem =  node;  
    7.   --nn;  
    8. }  

    高级,维护两个指针,两个指针相差n个元素,当前面的指针到达链表末尾,后面指针所指的元素即是所求的元素。实现如下

    [cpp] view plain copy
     
    1. Node* last_n_elem(Node*node,int n){  
    2.   if(node! || n<1)    returnNULL;  
    3.   Node *p=node,*q=node;  
    4.   while(n>0 && q){  
    5.       q=q->next;  
    6.       --n;  
    7.   }  
    8.   while(q){  
    9.       q=q->next;  
    10.       p=p->next;  
    11.   }  
    12.   return p;  
    13. }  



    11、链表元素去重,从未排序的链表中移除重复的项。

    思路:可使用额外的空间的话,可以用数组存数字,实现最好的方式就是哈希表啦。遍历一下即可。

    实现:

    [cpp] view plain copy
     
    1. std::map<Node*, bool>hash;  
    2. void duplicate_remove(Node *node){  
    3.   if(!node)   return ;  
    4.   Node *post=node->next;  
    5.   hash[node->data] = true;  
    6.   while(post){  
    7.       if(hash[post->data]){  
    8.           Node *temp = post;  
    9.           post = post->next;  
    10.           node->next = post;  
    11.           delete temp;  
    12.       }else{  
    13.           hash[post->data] = true;  
    14.           node = post;  
    15.           post = post->next;  
    16.       }  
    17.   }  
    18. }  

    如果不允许使用临时缓存,怎么解决?

    思路:用两个指针。当某个指针指向某个元素时,另一个指针将后面的相同元素全部删除。复杂度O(n^2)。具体实现就不写了。

    12、链表求和问题。

    该问题基本上有两个类型:

    a、1->2->5->4 , 2->5->3->4,得3->7->8->8.

    思路:先加高位,再加低位。两个0~9的数相加,要么不进位,要么进位为1.用两个指针,p指向当前进位点,q指向当前操作点。当然第一个元素得特殊考虑,可能进位嘛。

    自己实现:

    [cpp] view plain copy
     
    1. Node* merge_list_add(Node *list1,Node *list2){  
    2.     Node*q1=list1,*q2=list2,*ans=NULL,*pre=NULL,*p=NULL,*q=NULL;  
    3.     int cvalue = q1->data+ q2->data;  
    4.     bool flag = false;  
    5.     ans = new Node();  
    6.     // node 1  
    7.     if(cvalue >9){ //进位  
    8.         pre = new Node();  
    9.         pre->data =cvalue%10;  
    10.         ans->next = pre;  
    11.         ans->data = 1;  
    12.         p=pre;  
    13.     }else if(9 ==  cvalue){//最高位为9  
    14.         flag = true;  
    15.         ans->data =cvalue;  
    16.         pre= ans;  
    17.         p=pre;  
    18.     }else{  
    19.         ans->data =cvalue;  
    20.         p = pre= ans;  
    21.     }  
    22.     q1=q1->next;q2=q2->next;  
    23.     while(q1 && q2){// the following node  
    24.         q = new Node();  
    25.         pre->next = q;  
    26.         cvalue = q1->data+ q2->data;  
    27.         q->data =cvalue%10;  
    28.         if(cvalue > 9 ){  
    29.             if(flag){  
    30.                 if(p != ans){  
    31.                     p->data += 1;  
    32.                 }else{//999...[],前面全是9  
    33.                     Node*temp = new Node();  
    34.                     temp->data= 1;  
    35.                     temp->next= ans;  
    36.                     flag =false;  
    37.                     ans =temp;  
    38.                     p = ans;  
    39.                 }  
    40.             }else{  
    41.                 p->data +=1;  
    42.             }  
    43.             for(p=p->next;p!=q;p=p->next){  
    44.                 p->data =0;  
    45.             }  
    46.         }else if(cvalue <9){  
    47.             p = q;  
    48.         }  
    49.         pre = q;  
    50.         q1=q1->next;q2=q2->next;  
    51.     }  
    52.     return ans;  
    53. }  

    参考:http://hawstein.com/posts/add-singly-linked-list.html,第二种实现不错

    b、 元素个数不一定相同,高位在后,个位在链表头结点。1->2->3 , 4->5->3->4,得5->7->6->4.

    思路:需要注意的是,链表为空,有进位,链表长度不一样。

    [cpp] view plain copy
     
    1. #include <assert.h>  
    2. #include <iostream>  
    3. using namespace std;  
    4. struct Node{  
    5.     int data;  
    6.     Node *next;  
    7. };  
    8. Node* create_list(int arr[],int len){  
    9.     assert(arr &&len>0);  
    10.     Node *head =  new Node();  
    11.     head->data = arr[0];  
    12.     Node *cur=NULL;  
    13.     Node *pre=head;  
    14.     for(int i=1;i<len;++i){  
    15.         cur = new Node();  
    16.         cur->data =arr[i];  
    17.         pre->next = cur;  
    18.         pre = cur;  
    19.     }  
    20.     cur->next = NULL;  
    21.     return head;  
    22. }  
    23. Node* merge_list_add(Node *list1,Node *list2){  
    24.     if(NULL == list1) returnlist2;  
    25.     if(NULL == list2) returnlist1;  
    26.     Node *ans=NULL,*pre=NULL;  
    27.     int c=0;//进位  
    28.     int value = 0;  
    29.     while(list1 &&list2){  
    30.         value =list1->data +list2->data + c;  
    31.         Node* temp = newNode();  
    32.         temp->data =value%10;  
    33.         c = value/10;  
    34.         if(pre){  
    35.             pre->next =temp;  
    36.             pre = temp;  
    37.         }else  
    38.             ans=pre=temp;  
    39.         list2 = list2->next;  
    40.         list1 =list1->next;  
    41.     }    
    42.     if(!list1 &&!list2 && c>0){//两个链表长度一样,但有进位  
    43.         Node* temp = newNode();  
    44.         temp->data = 1;  
    45.         temp->next =NULL;//结束  
    46.         pre->next = temp;  
    47.     }  
    48.     //有一个链表更长  
    49.     while(list1){  
    50.         value =list1->data + c;  
    51.         Node* temp = newNode();  
    52.         temp->data =value%10;  
    53.         c = value/10;  
    54.         pre->next = temp;  
    55.         pre = temp;  
    56.         list1 =list1->next;     
    57.     }  
    58.     while(list2){  
    59.         value =list2->data + c;  
    60.         Node* temp = newNode();  
    61.         temp->data =value%10;  
    62.         c = value/10;  
    63.         pre->next = temp;  
    64.         pre = temp;  
    65.         list2 =list2->next;     
    66.     }  
    67.     pre->next = NULL;  
    68.     return ans;  
    69. }  
    70. int main(){  
    71.     int a[]={1,2,7};  
    72.     int b[]={4,5,3,9,3};  
    73.     //此处应该加个判断,保证数组元素均在[0,9]  
    74.     Node* lista =create_list(a,3);  
    75.     Node* listb =create_list(b,5);  
    76.     Node* cur = lista;  
    77.     cout<<"list a:";  
    78.     while(cur != NULL){  
    79.         cout<<cur->data<<"";  
    80.         cur = cur->next;  
    81.     }  
    82.     cur = listb;  
    83.     cout<<endl<<"listb: ";  
    84.     while(cur != NULL){  
    85.         cout<<cur->data<<"";  
    86.         cur = cur->next;  
    87.     }  
    88.     cout<<endl;  
    89.     Node *ans =merge_list_add(lista, listb);  
    90.     for(; ans; ans=ans->next)  
    91.        cout<<ans->data<<" ";  
    92.     cout<<endl;  
    93. }  

    13、用算法实现删除链表的一个中间节点,所知的只有该节点的指针。如a-b-c-d-e中只知道c的指针,实现a-b-d-e。

    思路:若直接删除的话,链表就断了,可是无法得到节点c的前驱b。故可转换思路利用c的后继d。将d的值赋给c, 然后将后继节点d删除,也就实现删除操作。

    由于c的位置不定,得分情况讨论。一、c为普通的中间节点,用上述方式解决。二,c为头节点,用上述方式解决。三、c为尾节点,一般认为删除即可,但是会出现问题。删除之后,尾节点的前驱不为空,下次遍历就会出错,特别注意。四、c为空节点,直接返回。

    实现:

    [cpp] view plain copy
     
    1. bool remove_elem(Node* node){  
    2.   if(!node || !node->next)    returnfalse;  
    3.   Node *post = node->next;  
    4.   node->data = post->data;  
    5.   node->next = post->next;  
    6.   delete post;  
    7.   return true;  
    8. }  

    扩展:

    a、Google题目,给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点

    思路跟前面的一致,同样要注意尾节点。

    b、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。

    思路:首先分配一个结点q,将q插入在p后,接下来将p中的数据copy入q中,
    然后再将要插入的数据记录在p中。

    14、环链表开始节点,1->2->5->4->2

    思路:

    1、        用快慢指针,满指针1,快指针2。

    我们注意到第一次相遇时,指针走过的路程S1 = 非环部分长度 + 弧A长

    快指针走过的路程S2 = 非环部分长度 + n * 环长 + 弧A长

    S1 * 2 = S2,可得 非环部分长度 = n * 环长 - 弧A长

    让指针1到起始点后,走过一个非环部分长度,指针2过了相等的长度。

    就是n * 环长 - 弧A长,正好回到环的开头。

    或者参考:http://blog.csdn.net/lalor/article/details/7628332

     2、     更简单直观的方法就是利用哈希表。无环的话,每个地址就是不一样;有环的话,两个地址一样的就是环开始节点。下面用c++的map实现

    std::map<Node*, bool>hash;

    Node* loop_start(Node* node){

      while(node){

          if(hash(node)) 

              return node;

          else{

              hash(node) = true;

              node = node->next;

          }

      }

      return NULL; //return head ;  same

    }

    15、如何知道环的长度

    一、在环上相遇后,记录第一次相遇点为pos,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。

      设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2*len步,相遇时多走了一圈:环长=2*len-len。

    二、利用哈希表,即两个碰撞元素间的个数

    16、输入一个链表的头结点,从尾到头反过来打印出每个结点的值。

    思路:用栈实现。

    进阶:用递归。实现如下:

    [cpp] view plain copy
     
    1. void PrintListReversingly(ListNode*pHead){  
    2.       if(pHead != NULL){  
    3.            if(pHead->m_pNext != NULL){  
    4.                  PrintListReversingly(pHead->m_pNext);  
    5.            }  
    6.        printf("%d ",pHead->m_nValue);  
    7.       }  
    8. }  

    注意:但使用递归就意味着可能发生栈溢出的风险,尤其是链表非常长的时候。所以,基于循环实现的栈的鲁棒性要好一些。

     

    17、输入两个递增链表,合并为一个递增链表。

    思路:遍历两个链表,依次比较,形成新的队列

    进阶:递归,每次递归返回合并后新链表的头结点

    [cpp] view plain copy
     
    1. list_node*List::recursive_merge(list_node * a,list_node * b){  
    2.         if(a == NULL)return b;  
    3.         if(b == NULL)return a;  
    4.         if(a->value <= b->value){  
    5.                 a->next=recursive_merge(a->next,b);  
    6.                 return a;  
    7.         }  
    8.         if(a->value > b->value){  
    9.                b->next=recursive_merge(a,b->next);  
    10.                 return b;  
    11.         }  
    12. }  

    18、用链表实现约瑟夫环

    这里就不实现了。

    19、判断一条单向链表是不是“回文”,1-2-4-2-1

    思路:对于单链表结构,可以用两个指针从两端或者中间遍历并判断对应字符是否相等。但这里的关键就是如何朝两个方向遍历。

    由于单链表是单向的,所以要向两 个方向遍历的话,可以采取经典的快慢指针的方法,即定位到链表的中间位置,再将链表的后半逆置,最后用两个指针同时从链表头部和中间开始同时遍历并比较即可。

    实现:(注意链表元素的奇偶性,稍微不同)

    [cpp] view plain copy
     
    1. bool is_list_plalindrome(Node*head){  
    2.   if(!head)  
    3.       return false;  
    4.   Node *one=head,*two=head,*pre=NULL;  
    5.   while(two!=NULL && two->next!=NULL){  
    6.       pre=one;  
    7.       one = one->next;  
    8.       two = two->next->next;  
    9.   }  
    10.   //if length of list is odd, mid  
    11.   Node *subhead=NULL,*node=NULL,*post=NULL;  
    12.   if(!two){ //even,two==NULL  
    13.       subhead = one;  
    14.   }else{ //odd  
    15.       subhead=one->next;  
    16.   }  
    17.     node=subhead;  
    18.     post=node->next;  
    19.     while(node->next){// rotate sublist (旋转的这种写法,很容易理解)  
    20.         node->next = post->next;  
    21.         post->next = subhead;  
    22.         subhead = post;  
    23.         post = node->next;  
    24.     }  
    25.     for(Node*p=head,*q=subhead;p!=pre->next;p=p->next,q=q->next){  
    26.         if(p->data!=q->data)  
    27.             return false;  
    28.     }  
    29.     return true;  
    30. }  



    20、从尾到头输出链表。

    题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值。

    思路:跟输出倒数第n个元素的方法类似。

    方法一、先把链表反向,然后再从头到尾遍历一遍。但该方法需要额外的操作

    方法二、设一个栈,从头到尾遍历一次,把结点值压力栈中,再出栈打印。

    方法三、递归。

    实现:

    [cpp] view plain copy
     
    1. void list_out_reverse(Node*head){  
    2.   if(!head)  
    3.       return;  
    4.   else  
    5.       list_out_reverse(head->next);  
    6.   cout<<head->data<<" ";  
    7. }  



    扩展:该题还有两个常见的变体:

    1. 从尾到头输出一个字符串;

    2. 定义一个函数求字符串的长度,要求该函数体内不能声明任何变量。

    两个的分别实现:

    [cpp] view plain copy
     
    1. void reverseString(conststring& s,unsigned int begin){  
    2.   if(!s.size())  
    3.       return;  
    4.   if(begin>=s.size())  
    5.       return;  
    6.   reverseString(s,begin+1);  
    7.   cout<<s[begin]<<" ";  
    8. }  
    [cpp] view plain copy
     
    1. int getLength(const char *s){  
    2.   if(*s=='/0')  
    3.       return 0;  
    4.   return getLength(s+1) + 1;  
    5. }  
    6.    

    21、链表和数组的区别?

    分析:主要在基本概念上的理解。但是最好能考虑的全面一点,现在公司招人的竞争可能就在细节上产生,谁比较仔细,谁获胜的机会就大。

    数组无需初始化,因为数组的元素在内存的栈区,系统自动申请空间。而链表的结点元素在内存的堆区,每个元素须手动申请空间,如malloc。也就是说数组是静态分配内存,而链表是动态分配内存。链表如此麻烦为何还要用链表呢?数组不能完全代替链表吗?回到这个问题只需想想我们当初是怎么完成学生信息管理系统的。为何那时候要用链表?因为学生管理系统中的插入,删除等操作都很灵活,而数组则大小固定,也无法灵活高效的插入,删除。

    数组是线性结构,静态分配内存,在内存中连续,数组元素在栈区。可以直接索引,时间复杂度O(1)。数组插入或删除元素比较困难,时间复杂度O(n)

    链表也是线性结构,动态分配内存,在内存中不连续,链表元素在堆区。元素的定位均需遍历,时间复杂度O(n)。链表插入或删除元素操作灵活性强,时间复杂度O(1)

    22、编写实现链表排序的一种算法。说明为什么你会选择用这样的方法?

    思路:如果只是数据内容之间的相互交换,那么这种排序方法也比较适合链表的排序,插入、冒泡、希尔和选择排序。快速排序、合并排序、堆排序都涉及到了中间值的选取问题,所以不大适合链表排序。

    选择排序的实现:

    [cpp] view plain copy
     
    1. Node* insert_sort(Node *head){  
    2.     if(!head ||!head->next)  
    3.         return head;  
    4.     Node *p,*q,*pre,*temp;  
    5.     p=head->next;  
    6.     head->next=NULL;  
    7.     //   p is the head of unsorted list  
    8.     //   head is the head of sorted list  
    9.     while(p){  
    10.         q=head;  
    11.         while(q &&(q->data < p->data)){  
    12.             pre=q;  
    13.             q=q->next;  
    14.         }  
    15.         temp = p->next;  
    16.         if(q==head){  
    17.             p->next = q;  
    18.             head = p;  
    19.         }else{  
    20.             pre->next=p;  
    21.             p->next = q;  
    22.         }  
    23.         p=temp;  
    24.     }  
    25.     return head;  
    26. }  

    其他排序参考:http://blog.csdn.net/hackbuteer1/article/details/6666475

    http://wenku.baidu.com/link?url=CxhR7E5PGrEwRU5eKu_3xX5EvZ6MP-7GhRLhAfoQFThh5HxlQ5SgIxdfRPVXRO-oeCkwFYFqLJJezeswxdOmRE4W_QJBY3iS4Xpot23XCPi

    23、复杂链表的复制(默认无环)

    Q:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。请完成函数ComplexNode* Clone(ComplexNode* pHead),以复制一个复杂链表。

    一开始想这道题毫无思路,如果蛮来,首先创建好正常的链表,然后考虑sibling这个分量,则需要O(n^2)的时间复杂度。

    思路一: 一般复制一个简单链表就这么遍历一遍就好了,这个复杂链表,比简单链表多的地方就在于多了一个sibling的指针,也就是说在建立完简单链表之后,如何在新的链表中找到sibling对应的地址。我们已知的是旧的节点的地址,所以只需要用一个map,保存每一个节点旧的节点对应的新的节点的地址即可。(即将原链表中的结点N和相应复制结点N'建立哈希映射<N,N'>

    第一次遍历,建立简单节点,第二次遍历,对于旧链表中的每一个节点的sibling指针地址,从map中找到新链表中对应节点的地址,连接上就好了。

    实现:(不错的实现,学习

    [cpp] view plain copy
     
    1. ComplexNode* Clone(ComplexNode*pHead){  
    2.     if(pHead == NULL)  return NULL;  
    3.      map<ComplexNode*, ComplexNode*>pointMap;  
    4.      ComplexNode* newHead,*tail; // newHead指向复制的新链表的开头,tail始终指向结尾  
    5.      // 开辟一个头结点  
    6.      newHead = new ComplexNode;  
    7.      newHead->value = pHead->value;  
    8.      newHead->pNext = NULL;  
    9.      newHead->pSibling = NULL;  
    10.      pointMap[pHead] = newHead; // 将头结点放入map中  
    11.    
    12.      tail = newHead;  
    13.      ComplexNode *p = pHead->pNext;  
    14.      while(p != NULL){ // 第一遍先将简单链表复制一下  
    15.         ComplexNode* newNode = new ComplexNode;  
    16.         newNode->value = p->value;  
    17.         newNode->pNext = NULL;  
    18.         newNode->pSibling = NULL;  
    19.    
    20.         tail->pNext = newNode;  
    21.         tail = newNode;  
    22.         pointMap[p] = newNode;  
    23.          p = p->pNext;  
    24.      }  
    25.      // 根据map中保存的数据,找到对应的节点  
    26.      p = pHead;  
    27.      tail = newHead;  
    28.      while(p!=NULL){  
    29.          if(p->pSibling!=NULL){  
    30.              tail->pSibling =pointMap.find(p->pSibling)->second;//Key,找N对应的N’  
    31.          }  
    32.          p = p->pNext;  
    33.          tail = tail->pNext;  
    34.      }  
    35.     return newHead;  
    36.  }  

    思路二:(精妙)一个技巧便可以巧妙的解答此题。看图便知。

    首先是原始的链表

    然后我们还是首先复制每一个结点N为N*,不同的是我们将N*让在对应的N后面,即为

    然后我们要确定每一个N*的sibling分量,非常明显,N的sibling分量的next就是N*的sibling分量。

    最后,将整个链表拆分成原始链表和拷贝出的链表。

    这样,我们就解决了一个看似非常混乱和复杂的问题。

    实现:

    [cpp] view plain copy
     
      1. struct Node{  
      2.     int val;  
      3.     Node* next;  
      4.     Node*sibling;  
      5. };  
      6. void Clone(Node* head){  
      7.     Node*current=head;  
      8.     while(current){  
      9.         Node*temp=new Node;  
      10.         temp->val=current->val;  
      11.         temp->next=current->next;  
      12.         temp->sibling=NULL;  
      13.         current->next=temp;  
      14.         current=temp->next;  
      15.     }  
      16. }  
      17. void ConstructSibling(Node*head){  
      18.     Node*origin=head;  
      19.     Node*clone;  
      20.     while(origin){  
      21.         clone=origin->next;  
      22.         if(origin->sibling)  
      23.             clone->sibling=origin->sibling->next;  
      24.         origin=clone->next;  
      25.     }  
      26. }  
      27.    
      28. Node* Split(Node* head){  
      29.     Node*CloneHead,*clone,*origin;  
      30.     origin=head;  
      31.     if(origin){  
      32.         CloneHead=origin->next;  
      33.         origin->next=CloneHead->next;  
      34.         origin=CloneHead->next;  
      35.         clone=CloneHead;  
      36.     }  
      37.     while(origin){  
      38.         Node*temp=origin->next;  
      39.         origin->next=temp->next;  
      40.         origin=origin->next;  
      41.         clone->next=temp;  
      42.         clone=temp;  
      43.     }  
      44.     return CloneHead;  
      45. }  
      46. //the whole thing  
      47. Clone(head);  
      48. ConstructSibling(head);  
      49. Split(head); 
  • 相关阅读:
    UVA1292-----Strategic game-----树形DP解决树上的最小点覆盖问题
    【OpenGL游戏开发之三】OpenGl核心函数库汇总
    【Lucene】挖掘相关搜索词
    OpenMP入门教程(三)
    OpenMP入门教程(二)
    OpenMP入门教程(一)
    结构体的排序
    结构体
    循环群、对称群、陪集和拉格朗日定理、正规子群和商群
    概率论与数理统计(一)—— 随机事件与概率
  • 原文地址:https://www.cnblogs.com/xiaodi914/p/5800798.html
Copyright © 2020-2023  润新知