`

程序员面试题精选(09)-查找链表中倒数第k个结点

阅读更多

题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。链表结点定义如下: struct ListNode
{
      int        m_nKey;
       ListNode* m_pNext;
};
分析:为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。可是输入的是单向链表,只有从前往后的指针而没有从后往前的指针。因此我们需要打开我们的思路。
既然不能从尾结点开始遍历这个链表,我们还是把思路回到头结点上来。假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数)。如果我们能够得到链表中结点的个数n,那我们只要从头结点开始往后走n-k-1步就可以了。如何得到结点数n?这个不难,只需要从头开始遍历链表,每经过一个结点,计数器加一就行了。
这种思路的时间复杂度是O(n),但需要遍历链表两次。第一次得到链表中结点个数n,第二次得到从头结点开始的第n­-k-1个结点即倒数第k个结点。
如果链表的结点数不多,这是一种很好的方法。但如果输入的链表的结点个数很多,有可能不能一次性把整个链表都从硬盘读入物理内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。我们知道把数据从硬盘读入到内存是非常耗时间的操作。我们能不能把链表遍历的次数减少到1?如果可以,将能有效地提高代码执行的时间效率。
如果我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
这种思路只需要遍历链表一次。对于很长的链表,只需要把每个结点从硬盘导入到内存一次。因此这一方法的时间效率前面的方法要高。
思路一的参考代码:

C++代码 复制代码
  1. ///////////////////////////////////////////////////////////////////////   
  2. // Find the kth node from the tail of a list   
  3. // Input: pListHead - the head of list   
  4. //         k          - the distance to the tail   
  5. // Output: the kth node from the tail of a list   
  6. ///////////////////////////////////////////////////////////////////////   
  7. ListNode* FindKthToTail_Solution1(ListNode* pListHead, unsigned int k)   
  8. {   
  9.       if(pListHead == NULL)   
  10.             return NULL;   
  11.   
  12.       // count the nodes number in the list   
  13.        ListNode *pCur = pListHead;   
  14.       unsigned int nNum = 0;   
  15.       while(pCur->m_pNext != NULL)   
  16.        {   
  17.              pCur = pCur->m_pNext;   
  18.              nNum ++;   
  19.        }   
  20.   
  21.       // if the number of nodes in the list is less than k   
  22.       // do nothing   
  23.       if(nNum < k)   
  24.             return NULL;   
  25.   
  26.       // the kth node from the tail of a list    
  27.       // is the (n - k)th node from the head   
  28.        pCur = pListHead;   
  29.       for(unsigned int i = 0; i < nNum - k; ++ i)   
  30.              pCur = pCur->m_pNext;   
  31.       return pCur;   
  32. }  
///////////////////////////////////////////////////////////////////////
// Find the kth node from the tail of a list
// Input: pListHead - the head of list
//         k          - the distance to the tail
// Output: the kth node from the tail of a list
///////////////////////////////////////////////////////////////////////
ListNode* FindKthToTail_Solution1(ListNode* pListHead, unsigned int k)
{
      if(pListHead == NULL)
            return NULL;

      // count the nodes number in the list
       ListNode *pCur = pListHead;
      unsigned int nNum = 0;
      while(pCur->m_pNext != NULL)
       {
             pCur = pCur->m_pNext;
             nNum ++;
       }

      // if the number of nodes in the list is less than k
      // do nothing
      if(nNum < k)
            return NULL;

      // the kth node from the tail of a list 
      // is the (n - k)th node from the head
       pCur = pListHead;
      for(unsigned int i = 0; i < nNum - k; ++ i)
             pCur = pCur->m_pNext;
      return pCur;
}


思路二的参考代码:

C++代码 复制代码
  1. ///////////////////////////////////////////////////////////////////////   
  2. // Find the kth node from the tail of a list   
  3. // Input: pListHead - the head of list   
  4. //         k          - the distance to the tail   
  5. // Output: the kth node from the tail of a list   
  6. ///////////////////////////////////////////////////////////////////////   
  7. ListNode* FindKthToTail_Solution2(ListNode* pListHead, unsigned int k)   
  8. {   
  9.       if(pListHead == NULL)   
  10.             return NULL;   
  11.   
  12.        ListNode *pAhead = pListHead;   
  13.        ListNode *pBehind = NULL;   
  14.       for(unsigned int i = 0; i < k; ++ i)   
  15.        {   
  16.             if(pAhead->m_pNext != NULL)   
  17.                    pAhead = pAhead->m_pNext;   
  18.             else  
  19.              {   
  20.                   // if the number of nodes in the list is less than k,    
  21.                   // do nothing   
  22.                   return NULL;   
  23.              }   
  24.        }   
  25.        pBehind = pListHead;   
  26.   
  27.       // the distance between pAhead and pBehind is k   
  28.       // when pAhead arrives at the tail, p   
  29.       // Behind is at the kth node from the tail   
  30.       while(pAhead->m_pNext != NULL)   
  31.        {   
  32.              pAhead = pAhead->m_pNext;   
  33.              pBehind = pBehind->m_pNext;   
  34.        }   
  35.   
  36.       return pBehind;   
  37. }  
///////////////////////////////////////////////////////////////////////
// Find the kth node from the tail of a list
// Input: pListHead - the head of list
//         k          - the distance to the tail
// Output: the kth node from the tail of a list
///////////////////////////////////////////////////////////////////////
ListNode* FindKthToTail_Solution2(ListNode* pListHead, unsigned int k)
{
      if(pListHead == NULL)
            return NULL;

       ListNode *pAhead = pListHead;
       ListNode *pBehind = NULL;
      for(unsigned int i = 0; i < k; ++ i)
       {
            if(pAhead->m_pNext != NULL)
                   pAhead = pAhead->m_pNext;
            else
             {
                  // if the number of nodes in the list is less than k, 
                  // do nothing
                  return NULL;
             }
       }
       pBehind = pListHead;

      // the distance between pAhead and pBehind is k
      // when pAhead arrives at the tail, p
      // Behind is at the kth node from the tail
      while(pAhead->m_pNext != NULL)
       {
             pAhead = pAhead->m_pNext;
             pBehind = pBehind->m_pNext;
       }

      return pBehind;
}

讨论:这道题的代码有大量的指针操作。在软件开发中,错误的指针操作是大部分问题的根源。因此每个公司都希望程序员在操作指针时有良好的习惯,比如使用指针之前判断是不是空指针。这些都是编程的细节,但如果这些细节把握得不好,很有可能就会和心仪的公司失之交臂。
另外,这两种思路对应的代码都含有循环。含有循环的代码经常出的问题是在循环结束条件的判断。是该用小于还是小于等于?是该用k还是该用k-1?由于题目要求的是从0开始计数,而我们的习惯思维是从1开始计数,因此首先要想好这些边界条件再开始编写代码,再者要在编写完代码之后再用边界值、边界值减1、边界值加1都运行一次(在纸上写代码就只能在心里运行了)。
扩展:和这道题类似的题目还有:输入一个单向链表。如果该链表的结点数为奇数,输出中间的结点;如果链表结点数为偶数,输出中间两个结点前面的一个。如果各位感兴趣,请自己分析并编写代码。

分享到:
评论

相关推荐

    python-leetcode面试题解之第19题删除链表的倒数第N个结点.zip

    本题的目标是删除链表中的倒数第N个节点。下面我们将深入探讨这个问题,分析解决方案,并学习相关的链表知识。 链表是一种线性数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的引用。与数组不同...

    python程序员面试(算法完整)

    - **找出倒数第k个元素**: 使用双指针法,先移动一个指针k步,然后两个指针同时移动直到第一个指针到达链表尾部。 - **检测单链表是否有环**: 使用快慢指针技术,快指针每次移动两步,慢指针每次移动一步,如果有...

    《C 程序员面试算法宝典》读书笔记模板x.pptx

    5. 如何找出单链表中的倒数第 k 个元素 6. 如何检测一个较大的单链表是否有环 7. 如何把链表相邻元素翻转 8. 如何把链表以 k 个结点为一组进行翻转 9. 如何合并两个有序链表 栈与队列篇 1. 如何实现栈 2. 如何实现...

    【Leetcode刷题笔记04】24. 两两交换节点 19. 删除结点 07. 链表相交 142. 环形链表 II.md

    删除链表的倒数第N个节点、面试题02.07.链表相交和142.环形链表II。记录了清晰的文字题解+图示以及Java参考代码。 适合人群:链表算法感兴趣的程序员或学生,想要打好数据结构与算法基础的人。 能学到什么:掌握链表...

    leetcode分类-nowcoder:牛客网学习,包括剑指offer,程序员面试金典,leetcode,公司模拟真题,数据结构等

    15链表中倒数第k个结点 16反转链表 17合并两个排序的链表 18树的子结构 19二叉树的镜像 20顺时针打印矩阵 21包含min函数的栈 22栈的压入弹出序列 23从上往下打印二叉树 24二叉搜索树的后序遍历序列 25二叉树中和为某...

    leetcode-cpp刷题

    - 删除链表倒数第n个节点。 - 实现思路:使用快慢指针,快指针先走n步,当快指针到达尾部时,慢指针指向待删除节点的前一个节点。 - **2.2.8 Swap Nodes in Pairs** - 两两交换链表中的节点。 - 实现思路:使用...

    剑指Offer---Java版代码

    5. 链表:包括链表中倒数第K个结点,链表中环的入口结点,反转链表,合并两个排序的链表等。 6. 查找算法:如数值的整数次方,数组中出现次数超过一半的数字等。 7. 动态规划:例如把数组排成最小的数,把数字翻译成...

    剑指offer(牛客网)

    13. 链表中倒数第k个节点:找到链表中倒数第k个节点。 14. 反转链表:将链表的连接方向逆转。 15. 合并两个排序的链表:将两个已经排序好的链表合并成一个新的排序链表。 16. 树的子结构:判断一个树是否包含另一...

Global site tag (gtag.js) - Google Analytics