题目:输入一个链表,输出该链表中倒数第K个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。
看到这道题目,最直观的想法,就是先算出链表的长度n,然后倒数第k个结点就是顺序的第(n-k+1)个数,不过这样需要2次遍历链表,如果要求只能遍历链表一次,那么上述算法就不符合要求了。
那我们就使用第二种算法,设定两个指针p1和p2,两个指针刚开始都指向链表的第一个结点,然后让p1指针先走(k-1)步,然后再让两个指针一起往后走,当p1指针指向链表最后一个结点的时候,p2指针刚好指向链表中的倒数第k个结点。
在写代码的时候需要考虑鲁棒性,最好采用防御性编程,就是考虑在哪些地方会出错,然后提前加上错误判断,这样避免因此错误输入而导致程序崩溃。
下面首先给出代码实例,然后再讲解程序鲁棒性:
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //链表结构 struct ListNode { int m_nValue; ListNode* m_pNext; }; //创建一个链表结点 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //遍历链表中的所有结点 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //输出链表中的某一结点的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //往链表末尾添加结点 /* 注意这里pHead是一个指向指针的指针,在主函数中一般传递的是引用。 因为如果要为链表添加结点,那么就会修改链表结构,所以必须传递引用才能够保存修改后的结构。 */ void AddToTail(ListNode** pHead,int value) { ListNode* pNew=new ListNode();//新插入的结点 pNew->m_nValue=value; pNew->m_pNext=NULL; if(*pHead==NULL)//空链表 { *pHead=pNew; } else { ListNode* pNode=*pHead; while(pNode->m_pNext!=NULL) pNode=pNode->m_pNext; pNode->m_pNext=pNew; } } //非防御性编程,非法输入会导致错误。 ListNode* KthNodeFromEnd(ListNode* pHead,int k) { ListNode* pNode=pHead;//当前结点 ListNode* pKthNode=pHead;// while(k-1>0) { pNode=pNode->m_pNext;//让pNode先走k-1步 --k; } while(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext; pKthNode=pKthNode->m_pNext; } return pKthNode; } //防御性编程,鲁棒性更好 ListNode* KthNodeFromEnd2(ListNode* pHead,int k) { if(pHead==NULL||k==0) return NULL; ListNode* pNode=pHead;//当前结点 ListNode* pKthNode=pHead;// while(k-1>0) { if(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext;//让pNode先走k-1步 --k; } else return NULL; } while(pNode->m_pNext!=NULL) { pNode=pNode->m_pNext; pKthNode=pKthNode->m_pNext; } return pKthNode; } void main() { //创建结点 ListNode* pNode1=CreateListNode(1);//创建一个结点 PrintList(pNode1);//打印 //往链表中添加新结点 AddToTail(&pNode1,2);//为链表添加一个结点 AddToTail(&pNode1,3);//为链表添加一个结点 AddToTail(&pNode1,4);//为链表添加一个结点 AddToTail(&pNode1,5);//为链表添加一个结点 AddToTail(&pNode1,6);//为链表添加一个结点 AddToTail(&pNode1,7);//为链表添加一个结点 //打印链表 PrintList(pNode1);//打印 //反转链表 ListNode* KthNode=KthNodeFromEnd2(pNode1,3); PrintListNode(KthNode); system("pause"); }
上述程序中,求倒数第k个结点的第一个方法:ListNode* KthNodeFromEnd(ListNode* pHead,int k)。
这个方法看似满足了我们的题目要求,但是当我们仔细分析,发现有3中方法让程序崩溃
- 输入的pListHead为空指针,由于代码会尝试访问空指针指向的内存,程序崩溃
- 输入的以pListHead为头结点的链表的结点少于k。这样在while(k-1>0)的循环中,又会出现尝试访问空指针指向的内存。
- 输入的参数k小于等于0。根据题意k的最小值应为1。
考虑上述因素,我们给出了第二个方法:ListNode* KthNodeFromEnd2(ListNode* pHead,int k)。
相关题目
1.求链表的中间结点。如果链表中结点总数为奇数,返回中间结点;如果结点总数是偶数,返回中间两个结点的任意一个。为了解决这个问题,我们也可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。
代码实例如下:
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //链表结构 struct ListNode { int m_nValue; ListNode* m_pNext; }; //创建一个链表结点 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //遍历链表中的所有结点 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //输出链表中的某一结点的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //往链表末尾添加结点 /* 注意这里pHead是一个指向指针的指针,在主函数中一般传递的是引用。 因为如果要为链表添加结点,那么就会修改链表结构,所以必须传递引用才能够保存修改后的结构。 */ void AddToTail(ListNode** pHead,int value) { ListNode* pNew=new ListNode();//新插入的结点 pNew->m_nValue=value; pNew->m_pNext=NULL; if(*pHead==NULL)//空链表 { *pHead=pNew; } else { ListNode* pNode=*pHead; while(pNode->m_pNext!=NULL) pNode=pNode->m_pNext; pNode->m_pNext=pNew; } } //求链表中间结点 ListNode* MidNodeInList(ListNode* pHead) { if(pHead==NULL) return NULL; ListNode* pNode=pHead;//当前结点 ListNode* pMidNode=pHead;//中间结点 //这里的判断条件是下一个结点不为空且下下个结点也不为空 while(pNode->m_pNext!=NULL&&pNode->m_pNext->m_pNext!=NULL) { pNode=pNode->m_pNext->m_pNext; pMidNode=pMidNode->m_pNext; } return pMidNode; } void main() { //创建结点 ListNode* pNode1=CreateListNode(1);//创建一个结点 PrintList(pNode1);//打印 //往链表中添加新结点 AddToTail(&pNode1,2);//为链表添加一个结点 AddToTail(&pNode1,3);//为链表添加一个结点 AddToTail(&pNode1,4);//为链表添加一个结点 AddToTail(&pNode1,5);//为链表添加一个结点 AddToTail(&pNode1,6);//为链表添加一个结点 AddToTail(&pNode1,7);//为链表添加一个结点 //打印链表 PrintList(pNode1);//打印 //求链表的中间结点 ListNode* MidNode=MidNodeInList(pNode1); PrintListNode(MidNode); system("pause"); }
2.判断一个单向链表是否形成了环状结构。和前面的问题一样,定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另外一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环状结构;如果走得快的指针走到了链表的末尾(m_pNext指向NULL)都没有追上走得慢的指针,那么链表就不是环状结构。
代码实例如下:
#include<iostream> #include<stdlib.h> #include<stack> using namespace std; //链表结构 struct ListNode { int m_nValue; ListNode* m_pNext; }; //创建一个链表结点 ListNode* CreateListNode(int value) { ListNode *pNode=new ListNode(); pNode->m_nValue=value; pNode->m_pNext=NULL; return pNode; } //连接两个结点 void ConnectListNode(ListNode* pCurrent,ListNode* pNext) { if(pCurrent==NULL) { cout<<"前一个结点不能为空"<<endl; exit(1); } else { pCurrent->m_pNext=pNext; } } //遍历链表中的所有结点 void PrintList(ListNode* pHead) { ListNode *pNode=pHead; while(pNode!=NULL) { cout<<pNode->m_nValue<<" "; pNode=pNode->m_pNext; } cout<<endl; } //输出链表中的某一结点的值 void PrintListNode(ListNode* pNode) { if(pNode == NULL) { printf("The node is NULL\n"); } else { printf("The key in node is %d.\n", pNode->m_nValue); } } //判断链表中是否有环 int IsLootList(ListNode* pHead) { if(pHead==NULL) return NULL; ListNode* pFirst=pHead;//第一个指针,步长为2 ListNode* pSecond=pHead;//第二个指针,步长为1 while(pFirst->m_pNext!=NULL&&pFirst->m_pNext->m_pNext!=NULL) { pFirst=pFirst->m_pNext->m_pNext; pSecond=pSecond->m_pNext; if(pFirst==pSecond) return 1; } return 0; } void main() { //创建结点 ListNode* pNode1=CreateListNode(1);//创建一个结点 ListNode* pNode2=CreateListNode(2);//创建一个结点 ListNode* pNode3=CreateListNode(3);//创建一个结点 ListNode* pNode4=CreateListNode(4);//创建一个结点 ListNode* pNode5=CreateListNode(5);//创建一个结点 ListNode* pNode6=CreateListNode(6);//创建一个结点 ListNode* pNode7=CreateListNode(7);//创建一个结点 //连接结点 ConnectListNode(pNode1,pNode2);//连接两个结点 ConnectListNode(pNode2,pNode3);//连接两个结点 ConnectListNode(pNode3,pNode4);//连接两个结点 ConnectListNode(pNode4,pNode5);//连接两个结点 ConnectListNode(pNode5,pNode6);//连接两个结点 ConnectListNode(pNode6,pNode7);//连接两个结点 ConnectListNode(pNode7,pNode1);//连接两个结点 //打印链表 //PrintList(pNode1);//打印 if(IsLootList(pNode1)) { cout<<"存在环"<<endl; } else { cout<<"不存在环"<<endl; } system("pause"); }