链表

来源:互联网 发布:网页三剑客软件下载 编辑:程序博客网 时间:2024/05/01 23:18

 另附:    http://www.tuicool.com/articles/mimaEf7

链表是最基本的数据结构,面试官也常常用链表来考察面试者的基本能力,而且链表相关的操作相对而言比较简单,也适合考察写代码的能力。链表的操作也离不开指针,指针又很容易导致出错。综合多方面的原因,链表题目在面试中占据着很重要的地位。本文对链表相关的面试题做了较为全面的整理,希望能对找工作的同学有所帮助。

链表结点声明如下:

struct ListNode
{
    int data;
    ListNode * next;

};

题目列表:

1. 求单链表中结点的个数
2. 将单链表反转
3. 查找单链表中的倒数第K个结点(k > 0)
4. 查找单链表的中间结点
5. 从尾到头打印单链表
6. 已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
7. 判断一个单链表中是否有环
8. 判断两个单链表是否相交
9. 求两个单链表相交的第一个节点
10. 已知一个单链表中存在环,求进入环中的第一个节点
11. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted


详细解答

1. 求单链表中结点的个数

这是最最基本的了,应该能够迅速写出正确的代码,注意检查链表是否为空。时间复杂度为O(n)。参考代码如下:

[cpp] view plain copy
  1. // 求单链表中结点的个数  
  2.  int GetListLength(ListNode * pHead)  
  3. {  
  4.     if(pHead == NULL)  
  5.         return 0;  
  6.   
  7.      int Length = 0;  
  8.     ListNode * pCurrent = pHead;  
  9.     while(pCurrent != NULL)  
  10.     {  
  11.         Length++;  
  12.         pCurrent = pCurrent->next;    //next = m_pNext
  13.     }  
  14.     return Length;  
  15. }  

2. 将单链表反转

从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)。参考代码如下:

[cpp] view plain copy
  1. // 反转单链表  
  2. ListNode * ReverseList(ListNode * pHead)  
  3. {  
  4.         // 如果链表为空或只有一个结点,无需反转,直接返回原链表头指针  
  5.     if(pHead == NULL || pHead->next == NULL)    
  6.         return pHead;  
  7.   
  8.     ListNode * pReversedHead = NULL; // 反转后的新链表头指针,初始为NULL  
  9.     ListNode * pCurrent = pHead;  
  10.     while(pCurrent != NULL)  
  11.     {  
  12.         ListNode * pTemp = pCurrent;  
  13.         pCurrent = pCurrent->next;  
  14.         pTemp->next = pReversedHead; // 将当前结点摘下,插入新链表的最前端  
  15.         pReversedHead = pTemp;  
  16.     }  
  17.     return pReversedHead;  
  18. }  

3. 查找单链表中的倒数第K个结点(k > 0)

最普遍的方法是,先统计单链表中结点的个数,然后再找到第(n-k)个结点。注意链表为空,k为0,k为1,k大于链表中节点个数时的情况。时间复杂度为O(n)。代码略。

这里主要讲一下另一个思路,这种思路在其他题目中也会有应用。

主要思路就是使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。

参考代码如下:

[cpp] view plain copy
  1. // 查找单链表中倒数第K个结点  
  2. ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函数名前面的R代表反向  
  3. {  
  4.     if(k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL  
  5.         return NULL;  
  6.   
  7.     ListNode * pA = pHead;       //A在前
  8.     ListNode * pB = pHead;  
  9.     while(k > 1 && pA != NULL) // 前面的指针先走到正向第k个结点  
  10.     {  
  11.         pA = pA->next;  
  12.         k--;  
  13.     }  
  14.     if(k > 1 || pA == NULL)     // 结点个数小于k,返回NULL  
  15.         return NULL;  
  16.     while(pA->next != NULL)  // 前后两个指针一起向前走,直到前面的指针指向最后一个结点  
  17.     {  
  18.         pB = pB->next;  
  19.         pA = pA->next;  
  20.     }  
  21.     return pB;  // 后面的指针所指结点就是倒数第k个结点  
  22. }  

4. 查找单链表的中间结点

此题可应用于上一题类似的思想。也是设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度O(n)。参考代码如下:

[cpp] view plain copy
  1. // 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点  
  2. ListNode * GetMiddleNode(ListNode * pHead)  
  3. {  
  4.     if(pHead == NULL || pHead->next == NULL) // 链表为空或只有一个结点,返回头指针  
  5.         return pHead;  
  6.   
  7.     ListNode * pA = pHead;  
  8.     ListNode * pB = pHead;  
  9.     while(pA->next != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步  
  10.     {  
  11.         pA = pA->next;  
  12.         pB = pB->next;  
  13.         if(pA->next != NULL)  
  14.             pA = pA->next;  
  15.     }  
  16.     return pB; // 后面的指针所指结点即为中间结点  
  17. }  


5. 从尾到头打印单链表

对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,这一题要么自己使用栈,要么让系统使用栈,也就是递归。注意链表为空的情况。时间复杂度为O(n)。参考代码如下:

自己使用栈:

[cpp] view plain copy
  1. // 从尾到头打印链表,使用栈  
  2. void RPrintList(ListNode * pHead)  
  3. {  
  4.     std::stack<ListNode *> s;  
  5.     ListNode * pNode = pHead;  
  6.     while(pNode != NULL)  
  7.     {  
  8.         s.push(pNode);  
  9.         pNode = pNode->next;  
  10.     }  
  11.     while(!s.empty())  
  12.     {  
  13.         pNode = s.top();  
  14.         printf("%d\t", pNode->data);  
  15.         s.pop();  
  16.     }  
  17. }  

使用递归函数:

[cpp] view plain copy
  1. // 从尾到头打印链表,使用递归  
  2. void RPrintList(ListNode * pHead)  
  3. {  
  4.     if(pHead == NULL)  
  5.     {  
  6.         return;  
  7.     }  
  8.     else  
  9.     {  
  10.         RPrintList(pHead->next);  
  11.         printf("%d\t", pHead->data);  
  12.     }  
  13. }  
  14. // 反之,从头到尾依次打印链表
  15. void Rprint(ListNode *phead)
  16. {
  17.    p = phead->next;
  18.    while(p)
  19.    {
  20.        printf("%d", p->data);
  21.        p = p->next;
  22.    }
  23.    printf("\n");
  24. }


6. 已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序

这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要O(1)的空间。时间复杂度为O(max(len1, len2))。参考代码如下:

[cpp] view plain copy
  1. // 合并两个有序链表  
  2. ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)  
  3. {  
  4.     if(pHead1 == NULL)  
  5.         return pHead2;  
  6.     if(pHead2 == NULL)  
  7.         return pHead1;  
  8.     ListNode * pHeadMerged = NULL;  
  9.     if(pHead1->data < pHead2->data)  
  10.     {  
  11.         pHeadMerged = pHead1;  
  12.         pHeadMerged->next = NULL;  
  13.         pHead1 = pHead1->next;  
  14.     }  
  15.     else  
  16.     {  
  17.         pHeadMerged = pHead2;  
  18.         pHeadMerged->next = NULL;  
  19.         pHead2 = pHead2->next;  
  20.     }  
  21.     ListNode * pTemp = pHeadMerged;  
  22.     while(pHead1 != NULL && pHead2 != NULL)  
  23.     {  
  24.         if(pHead1->data < pHead2->data)  
  25.         {  
  26.             pTemp->next = pHead1;  
  27.             pHead1 = pHead1->next;  
  28.             pTemp = pTemp->next;  
  29.             pTemp->next = NULL;  
  30.         }  
  31.         else  
  32.         {  
  33.             pTemp->next = pHead2;  
  34.             pHead2 = pHead2->next;  
  35.             pTemp = pTemp->next;  
  36.             pTemp->next = NULL;  
  37.         }  
  38.     }  
  39.     if(pHead1 != NULL)  
  40.         pTemp->next = pHead1;  
  41.     else if(pHead2 != NULL)  
  42.         pTemp->next = pHead2;  
  43.     return pHeadMerged;  
  44. }  

也有如下递归解法:

[cpp] view plain copy
  1. ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2)  
  2. {  
  3.     if(pHead1 == NULL)  
  4.         return pHead2;  
  5.     if(pHead2 == NULL)  
  6.         return pHead1;  
  7.     ListNode * pHeadMerged = NULL;  
  8.     if(pHead1->data < pHead2->data)  
  9.     {  
  10.         pHeadMerged = pHead1;  
  11.         pHeadMerged->next = MergeSortedList(pHead1->next, pHead2);  
  12.     }  
  13.     else  
  14.     {  
  15.         pHeadMerged = pHead2;  
  16.         pHeadMerged->next = MergeSortedList(pHead1, pHead2->next);  
  17.     }  
  18.     return pHeadMerged;  
  19. }  


7. 判断一个单链表中是否有环

这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。参考代码如下:

[cpp] view plain copy
  1. bool HasCircle(ListNode * pHead)  
  2. {  
  3.     ListNode * pFast = pHead; // 快指针每次前进两步  
  4.     ListNode * pSlow = pHead; // 慢指针每次前进一步  
  5.     while(pFast != NULL && pFast->next != NULL)  
  6.     {  
  7.         pFast = pFast->next->next;  
  8.         pSlow = pSlow->next;  
  9.         if(pSlow == pFast) // 相遇,存在环  
  10.             return true;  
  11.     }  
  12.     return false;  
  13. }  


8. 判断两个单链表是否相交

如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则不相交。时间复杂度为O(len1+len2),因为只需要一个额外指针保存最后一个节点地址,空间复杂度为O(1)。参考代码如下:

[cpp] view plain copy
  1. bool IsIntersected(ListNode * pHead1, ListNode * pHead2)  
  2. {  
  3.         if(pHead1 == NULL || pHead2 == NULL)  
  4.                 return false;  
  5.   
  6.     ListNode * pTail1 = pHead1;  
  7.     while(pTail1->next != NULL)  
  8.         pTail1 = pTail1->next;  
  9.   
  10.     ListNode * pTail2 = pHead2;  
  11.     while(pTail2->next != NULL)  
  12.         pTail2 = pTail2->next;  
  13.     return pTail1 == pTail2;  
  14. }  

10. 已知一个单链表中存在环,求进入环中的第一个节点

首先判断是否存在环,若不存在结束。在环中的一个节点处断开(当然函数结束时不能破坏原链表),这样就形成了两个相交的单链表,求进入环中的第一个节点也就转换成了求两个单链表相交的第一个节点。参考代码如下:

[cpp] view plain copy
  1. ListNode* GetFirstNodeInCircle(ListNode * pHead)  
  2. {  
  3.     if(pHead == NULL || pHead->next == NULL)  
  4.         return NULL;  
  5.   
  6.     ListNode * pFast = pHead;  
  7.     ListNode * pSlow = pHead;  
  8.     while(pFast != NULL && pFast->next != NULL)  
  9.     {  
  10.         pSlow = pSlow->next;  
  11.         pFast = pFast->next->next;  
  12.         if(pSlow == pFast)  
  13.             break;  
  14.     }  
  15.     if(pFast == NULL || pFast->next == NULL)  
  16.         return NULL;  
  17.   
  18.     // 将环中的此节点作为假设的尾节点,将它变成两个单链表相交问题  
  19.     ListNode * pAssumedTail = pSlow;   
  20.     ListNode * pHead1 = pHead;  
  21.     ListNode * pHead2 = pAssumedTail->next;  
  22.   
  23.     ListNode * pNode1, * pNode2;  
  24.     int len1 = 1;  
  25.     ListNode * pNode1 = pHead1;  
  26.     while(pNode1 != pAssumedTail)  
  27.     {  
  28.         pNode1 = pNode1->next;  
  29.         len1++;  
  30.     }  
  31.       
  32.     int len2 = 1;  
  33.     ListNode * pNode2 = pHead2;  
  34.     while(pNode2 != pAssumedTail)  
  35.     {  
  36.         pNode2 = pNode2->next;  
  37.         len2++;  
  38.     }  
  39.   
  40.     pNode1 = pHead1;  
  41.     pNode2 = pHead2;  
  42.     // 先对齐两个链表的当前结点,使之到尾节点的距离相等  
  43.     if(len1 > len2)  
  44.     {  
  45.         int k = len1 - len2;  
  46.         while(k--)  
  47.             pNode1 = pNode1->next;  
  48.     }  
  49.     else  
  50.     {  
  51.         int k = len2 - len1;  
  52.         while(k--)  
  53.             pNode2 = pNode2->next;  
  54.     }  
  55.     while(pNode1 != pNode2)  
  56.     {  
  57.         pNode1 = pNode1->next;  
  58.         pNode2 = pNode2->next;  
  59.     }  
  60.     return pNode1;  
  61. }  

11. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted

对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)。参考代码如下:

[cpp] view plain copy
  1. void Delete(ListNode * pHead, ListNode * pToBeDeleted)  
  2. {  
  3.     if(pToBeDeleted == NULL)  
  4.         return;  
  5.     if(pToBeDeleted->next != NULL)  
  6.     {  
  7.         pToBeDeleted->data = pToBeDeleted->next->data// 将下一个节点的数据复制到本节点,然后删除下一个节点  
  8.         ListNode * temp = pToBeDeleted->next;  
  9.         pToBeDeleted->next = pToBeDeleted->next->next;  
  10.         delete temp;  
  11.     }  
  12.     else // 要删除的是最后一个节点  
  13.     {  
  14.         if(pHead == pToBeDeleted) // 链表中只有一个节点的情况  
  15.         {  
  16.             pHead = NULL;  
  17.             delete pToBeDeleted;  
  18.         }  
  19.         else  
  20.         {  
  21.             ListNode * pNode = pHead;  
  22.             while(pNode->next != pToBeDeleted) // 找到倒数第二个节点  
  23.                 pNode = pNode->next;  
  24.             pNode->next = NULL;  
  25.             delete pToBeDeleted;  
  26.         }     
  27.     }  
  28. }  

=========================================================

8、 扩展:链表有环,如何判断相交

题目描述:上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?上面的方法还同样有效么?

分析:如果有环且两个链表相交,则两个链表都有共同一个环,即环上的任意一个节点都存在于两个链表上。因此,就可以判断一链表上俩指针相遇的那个节点,在不在另一条链表上。

代码如下:

//判断两个带环链表是否相交bool isIntersectWithLoop(Node *h1,Node *h2){    Node *circleNode1,*circleNode2;    if(!hasCircle(h1,circleNode1))    //判断链表带不带环,并保存环内节点        return false;                //不带环,异常退出    if(!hasCircle(h2,circleNode2))        return false;    Node *temp = circleNode2->next;    while(temp != circleNode2)    {        if(temp == circleNode1)            return true;        temp = temp->next;    }    return false;}

9、扩展:两链表相交的第一个公共节点

题目描述:如果两个无环单链表相交,怎么求出他们相交的第一个节点呢?

分析:采用对齐的思想。计算两个链表的长度 L1 , L2,分别用两个指针 p1 , p2 指向两个链表的头,然后将较长链表的 p1(假设为 p1)向后移动L2 - L1个节点,然后再同时向后移动p1 , p2,直到 p1 = p2。相遇的点就是相交的第一个节点。

代码如下:

//求两链表相交的第一个公共节点     思想:用两个指针扫描”两个链表“,最终两个指针到达 null 或者到达公共结点。
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2)
     {
        ListNode *p1 = pHead1;
        ListNode *p2 = pHead2;
        while(p1!=p2)
          {
            p1 = (p1==NULL ? pHead2 : p1->next);
            p2 = (p2==NULL ? pHead1 : p2->next);
        }
        return  p1;
    }
};

10.从单链表中删除指定的结点

实现:

将指定结点的后继的值赋值给指定结点,然后删除其后继即可。

   1: void DeleteMidNode(Node *del)
   2: {
   3:     if (!del) {
   4:         return; 
   5:     }
   6:     
   7:     Node *next = del->next;
   8:     if (next) {
   9:         del->data= next->data;
  10:         del->next= next->next;
  11:         free(next);    
  12:     }
  13: }


11.单链表的插入、删除操作

 

  1.  1、S节点插到P节点之后:
  2.     s->next = p->next;
  3.     p->next = s;

  4.  2、S节点插到p节点之前:
  5.     s->next = p->next;
  6.     p->next = s;
  7.     swap(p->data, s->data);

  8. 3、删除*p所指节点:
  9.     p->next = p->next->next;

 

11.双链表的插入、删除操作

 

  1.  1、p节点之后插入s节点:
  2.     s->prior = p ;
  3.     s->next  = p->next;
  4.     p->next->prior = s;
  5.     p->next = s;

  6.  2、p节点之前插入s节点:
  7.     s->next = p ;
  8.     s->prior = p->prior;
  9.     p->prior->next = s;
  10.     p->prior = s;

  11. 3、删除*p所指节点:
  12.     p->prior->next = p->next;
  13.     p->next->prior = p->prior;

 



12.删除单链表中重复的节点

   思想:相同的值域的节点都是相邻的,用p扫描单链表,若p节点的值等于后继节点的值,则删除后者,否则p移向下一          个节点。
  1. void del_same(LinkList &L)  
  2. {     
  3.    LNode *p = L->next, *q;    //p为扫描指针
  4.    while(p->next != NULL) 
  5.     {  
  6.       q = p->next;            //q为后继节点
  7.      if(p->data == q->data)
  8.       {
  9.          p->next = q->next;
  10.          free(q);
  11.       }
  12.      else
  13.           p=p->next;
  14.     }

13.删除单链表中特定的值
  1. void del(LinkList *&L,ElemType x)  
  2. {  Node *t;  
  3.    if (L==NULL) return;  
  4.    if (L->data==x)  
  5.     {  t=L;  
  6.       L=L->next;  
  7.       free(t);  
  8.       return;  
  9.     }  
  10.    else del(L->next,x);  
  11. }  



0 0
原创粉丝点击