链表题目

版权所有,转载请注明出处,谢谢!
http://blog.csdn.net/walkinginthewind/article/details/7393134

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

链表结点声明如下:

struct ListNode
{
    int m_nKey;
    ListNode * m_pNext;

};

题目列表:

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]  viewplain copy
  1. // 求单链表中结点的个数  
  2. unsigned int GetListLength(ListNode pHead)  
  3.  
  4.     if(pHead == NULL)  
  5.         return 0;  
  6.   
  7.     unsigned int nLength 0;  
  8.     ListNode pCurrent pHead;  
  9.     while(pCurrent != NULL)  
  10.      
  11.         nLength++;  
  12.         pCurrent pCurrent->m_pNext;  
  13.      
  14.     return nLength;  
  15.  


2.将单链表反转

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

 

[cpp]  viewplain copy
  1. // 反转单链表  
  2. ListNode ReverseList(ListNode pHead)  
  3.  
  4.         // 如果链表为空或只有一个结点,无需反转,直接返回原链表头指针  
  5.     if(pHead == NULL || pHead->m_pNext == 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->m_pNext;  
  14.         pTemp->m_pNext 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]  viewplain copy
  1. // 查找单链表中倒数第K个结点  
  2. ListNode RGetKthNode(ListNode pHead, unsigned int k) // 函数名前面的R代表反向  
  3.  
  4.     if(k == || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL  
  5.         return NULL;  
  6.   
  7.     ListNode pAhead pHead;  
  8.     ListNode pBehind pHead;  
  9.     while(k && pAhead != NULL) // 前面的指针先走到正向第k个结点  
  10.      
  11.         pAhead pAhead->m_pNext;  
  12.         k--;  
  13.      
  14.     if(k || pAhead == NULL)     // 结点个数小于k,返回NULL  
  15.         return NULL;  
  16.     while(pAhead->m_pNext != NULL)  // 前后两个指针一起向前走,直到前面的指针指向最后一个结点  
  17.      
  18.         pBehind pBehind->m_pNext;  
  19.         pAhead pAhead->m_pNext;  
  20.      
  21.     return pBehind;  // 后面的指针所指结点就是倒数第k个结点  
  22.  

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

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

 

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


5. 从尾到头打印单链表

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

自己使用栈:

 

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

 

使用递归函数:

 

[cpp]  viewplain copy
  1. // 从尾到头打印链表,使用递归  
  2. void RPrintList(ListNode pHead)  
  3.  
  4.     if(pHead == NULL)  
  5.      
  6.         return 
  7.      
  8.     else  
  9.      
  10.         RPrintList(pHead->m_pNext);  
  11.         printf("%d\t"pHead->m_nKey);  
  12.      
  13.  

 

没完,再续

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

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

 

[cpp]  viewplain 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->m_nKey pHead2->m_nKey)  
  10.      
  11.         pHeadMerged pHead1;  
  12.         pHeadMerged->m_pNext NULL;  
  13.         pHead1 pHead1->m_pNext;  
  14.      
  15.     else  
  16.      
  17.         pHeadMerged pHead2;  
  18.         pHeadMerged->m_pNext NULL;  
  19.         pHead2 pHead2->m_pNext;  
  20.      
  21.     ListNode pTemp pHeadMerged;  
  22.     while(pHead1 != NULL && pHead2 != NULL)  
  23.      
  24.         if(pHead1->m_nKey pHead2->m_nKey)  
  25.          
  26.             pTemp->m_pNext pHead1;  
  27.             pHead1 pHead1->m_pNext;  
  28.             pTemp pTemp->m_pNext;  
  29.             pTemp->m_pNext NULL;  
  30.          
  31.         else  
  32.          
  33.             pTemp->m_pNext pHead2;  
  34.             pHead2 pHead2->m_pNext;  
  35.             pTemp pTemp->m_pNext;  
  36.             pTemp->m_pNext NULL;  
  37.          
  38.      
  39.     if(pHead1 != NULL)  
  40.         pTemp->m_pNext pHead1;  
  41.     else if(pHead2 != NULL)  
  42.         pTemp->m_pNext pHead2;  
  43.     return pHeadMerged;  
  44.  

 

也有如下递归解法:

 

[cpp]  viewplain 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->m_nKey pHead2->m_nKey)  
  9.      
  10.         pHeadMerged pHead1;  
  11.         pHeadMerged->m_pNext MergeSortedList(pHead1->m_pNext, pHead2);  
  12.      
  13.     else  
  14.      
  15.         pHeadMerged pHead2;  
  16.         pHeadMerged->m_pNext MergeSortedList(pHead1, pHead2->m_pNext);  
  17.      
  18.     return pHeadMerged;  
  19.  


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

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

 

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


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

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

 

[cpp]  viewplain 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->m_pNext != NULL)  
  8.         pTail1 pTail1->m_pNext;  
  9.   
  10.     ListNode pTail2 pHead2;  
  11.     while(pTail2->m_pNext != NULL)  
  12.         pTail2 pTail2->m_pNext;  
  13.     return pTail1 == pTail2;  
  14.  

9.求两个单链表相交的第一个节点
对第一个链表遍历,计算长度len1,同时保存最后一个节点的地址。
对第二个链表遍历,计算长度len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,不相交,结束。
两个链表均从头节点开始,假设len1大于len2,那么将第一个链表先遍历len1-len2个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,然后一起向后遍历,知道两个节点的地址相同。

时间复杂度,O(len1+len2)。参考代码如下:

 

[cpp]  viewplain copy
  1. ListNode* GetFirstCommonNode(ListNode pHead1, ListNode pHead2)  
  2.  
  3.     if(pHead1 == NULL || pHead2 == NULL)  
  4.         return NULL;  
  5.   
  6.     int len1 1;  
  7.     ListNode pTail1 pHead1;  
  8.     while(pTail1->m_pNext != NULL)  
  9.      
  10.         pTail1 pTail1->m_pNext;  
  11.         len1++;  
  12.      
  13.   
  14.     int len2 1;  
  15.     ListNode pTail2 pHead2;  
  16.     while(pTail2->m_pNext != NULL)  
  17.      
  18.         pTail2 pTail2->m_pNext;  
  19.         len2++;  
  20.      
  21.   
  22.     if(pTail1 != pTail2) // 不相交直接返回NULL  
  23.         return NULL;  
  24.   
  25.     ListNode pNode1 pHead1;  
  26.     ListNode pNode2 pHead2;  
  27.         // 先对齐两个链表的当前结点,使之到尾节点的距离相等  
  28.     if(len1 len2)  
  29.      
  30.         int len1 len2;  
  31.         while(k--)  
  32.             pNode1 pNode1->m_pNext;  
  33.      
  34.     else  
  35.      
  36.         int len2 len1;  
  37.         while(k--)  
  38.             pNode2 pNode2->m_pNext;  
  39.      
  40.     while(pNode1 != pNode2)  
  41.      
  42.         pNode1 pNode1->m_pNext;  
  43.         pNode2 pNode2->m_pNext;  
  44.      
  45.         return pNode1;  
  46.  

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

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

 

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


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

 

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

 

[cpp]  viewplain copy
  1. void Delete(ListNode pHead, ListNode pToBeDeleted)  
  2.  
  3.     if(pToBeDeleted == NULL)  
  4.         return 
  5.     if(pToBeDeleted->m_pNext != NULL)  
  6.      
  7.         pToBeDeleted->m_nKey pToBeDeleted->m_pNext->m_nKey; // 将下一个节点的数据复制到本节点,然后删除下一个节点  
  8.         ListNode temp pToBeDeleted->m_pNext;  
  9.         pToBeDeleted->m_pNext pToBeDeleted->m_pNext->m_pNext;  
  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->m_pNext != pToBeDeleted) // 找到倒数第二个节点  
  23.                 pNode pNode->m_pNext;  
  24.             pNode->m_pNext NULL;  
  25.             delete pToBeDeleted;  
  26.             
  27.      


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode 中链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode 中链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值