面试题15连表中倒数第k个结点

来源:互联网 发布:广州服装开单软件 编辑:程序博客网 时间:2024/06/05 15:36

面试题15:链表中倒数第K个结点

题目:输入一个链表,输出该链表中倒数第K个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、23456。这个链表的倒数第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中方法让程序崩溃

  1. 输入的pListHead为空指针,由于代码会尝试访问空指针指向的内存,程序崩溃
  2. 输入的以pListHead为头结点的链表的结点少于k。这样在while(k-1>0)的循环中,又会出现尝试访问空指针指向的内存。
  3. 输入的参数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");}
复制代码
原创粉丝点击