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


    题目:输入一个链表,输出该链表中倒数第K个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、23456。这个链表的倒数第3个结点是值为4的结点。

    看到这道题目,最直观的想法,就是先算出链表的长度n,然后倒数第k个结点就是顺序的第(n-k+1)个数,不过这样需要2次遍历链表,如果要求只能遍历链表一次,那么上述算法就不符合要求了。

    那我们就使用第二种算法,设定两个指针p1和p2,两个指针刚开始都指向链表的第一个结点,然后让p1指针先走(k-1)步,然后再让两个指针一起往后走,当p1指针指向链表最后一个结点的时候,p2指针刚好指向链表中的倒数第k个结点。

    在写代码的时候需要考虑鲁棒性,最好采用防御性编程,就是考虑在哪些地方会出错,然后提前加上错误判断,这样避免因此错误输入而导致程序崩溃。

    下面首先给出代码实例,然后再讲解程序鲁棒性:

    View Code
    #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.求链表的中间结点。如果链表中结点总数为奇数,返回中间结点;如果结点总数是偶数,返回中间两个结点的任意一个。为了解决这个问题,我们也可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。

    代码实例如下:

    View Code
    #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)都没有追上走得慢的指针,那么链表就不是环状结构。

    代码实例如下:

    View Code
    #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");
    
    }
  • 相关阅读:
    vue中计算属性computed和watch的区别
    sqlserver中pivot(行转列),unpivot(列转行)
    js本地缓存的方式Cookie、localStorage、sessionStorage
    Linux调度系统全景指南(中篇)
    Linux调度系统全景指南(终结篇)
    Linux调度系统全景指南(下篇)
    五层模型
    Linux内核概念
    网络性能
    PyMuPDF使用
  • 原文地址:https://www.cnblogs.com/xwdreamer/p/2473355.html
Copyright © 2020-2023  润新知