题目:输入一个链表,输出该链表中倒数第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");
}