链表面试题总结 C/C++

本文详细总结了链表在面试中常见的题目,包括链表逆序、查找公共结点、删除节点等,并提供了C/C++的实现。讨论了链表的空间效率和时间效率,强调了链表操作的鲁棒性和递归、栈的运用。
摘要由CSDN通过智能技术生成

数据结构和算法,是我们程序设计最重要的两大元素,可以说,我们的编程,都是在选择和设计合适的数据结构来存放数据,然后再用合适的算法来处理这些数据。

      在面试中,最经常被提及的就是链表,因为它简单,但又因为需要对指针进行操作,凡是涉及到指针的,都需要我们具有良好的编程基础才能确保代码没有任何错误。

      链表是一种动态的数据结构,因为在创建链表时,我们不需要知道链表的长度,当插入一个结点时,只需要为该结点分配内存,然后调整指针的指向来确保新结点被连接到链表中。所以,它不像数组,内存是一次性分配完毕的,而是每添加一个结点分配一次内存。正是因为这点,所以它没有闲置的内存,比起数组,空间效率更高。

      像是单向链表的结点定义如下:

struct ListNode
{
     int m_nValue;
     ListNode* m_pNext;
};

     那么我们往该链表的末尾添加一个结点的代码如:

复制代码
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;
      }
}
复制代码

      我们传递一个链表时,通常是传递它的头指针的指针。当我们往一个空链表插入一个结点时,新插入的结点就是链表的头指针,那么此时就会修改头指针,因此必须把pHead参数设置为指向指针的指针,否则出了这个函数,pHead指向的依然是空,因为我们传递的会是参数的一个副本。但这里又有一个问题,为什么我们必须将一个指向ListNode的指针赋值给一个指针呢?我们完全可以直接在函数中直接声明一个ListNode而不是它的指针?注意,ListNode的结构中已经非常清楚了,它的组成中包括一个指向下一个结点的指针,如果我们直接声明一个ListNode,那么我们是无法将它作为头指针的下一个结点的,而且这样也能防止栈溢出,因为我们无法知道ListNode中存储了多大的数据,像是这样的数据结构,最好的方式就是传递指针,这样函数栈就不会溢出。
    对于java程序员来说,指针已经是遥远的记忆了,因为java完全放弃了指针,但并不意味着我们不需要学习指针的一些基础知识,毕竟这个世界上的代码并不全部是由java所编写,像是C/C++的程序依然运行在世界上大部分的机器上,像是一些系统的源码,就是用它们编写的,加上如果我们想要和底层打交道的话,学习C/C++是必要的,而指针就是其中一个必修的内容。

     就因为链表的内存不是一次性分配的,所以它并不像数组一样,内存是连续的,所以如果我们想要在链表中查找某个元素,我们就只能从头结点开始,而不能像数组那样根据索引来,所以时间效率为O(N)。

    像是这样:

复制代码
void RemoveNode(ListNode** pHead, int value)
{
      if(pHead == NULL || *pHead == NULL)
      {
           return;
      }

      ListNode* pToBeDeleted = NULL;
      if((*pHead)->m_nValue == value)
      {
           pToBeDeleted = *pHead;
           *pHead = (*pHead)->m_pNext;
      }
      else 
      {
            ListNode* pNode = *pHead;
            while(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue != value)
            {
                  pNode = pNode->m_pNext;
            }
            if(pNode->m_pNext != NULL && pNode->m_pNext->m_nValue == value)
            {
                  pToBeDeleted = pNode->m_pNext;
                  pNode->m_pNext = pNode->m_pNext->m_pNext;
            }
      }

      if(pToBeDeleted != NULL)
      {
             delete pToBeDeleted;
             pToBeDeleted = NULL;
       }
}
复制代码

      上面的代码用来在链表中找到第一个含有某值的结点并删除该结点.
      常见的链表面试题目并不仅仅要求这么简单的功能,像是下面这道题目:

题目一:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。

       首先我们必须明确的一点,就是我们无法像是数组那样直接的逆序遍历,因为链表并不是一次性分配内存,我们无法使用索引来获取链表中的值,所以我们只能是从头到尾的遍历链表,然后我们的输出是从尾到头,也就是说,对于链表中的元素,是"先进后出",如果明白到这点,我们自然就能想到栈。

      事实上,链表确实是实现栈的基础,所以这道题目的要求其实就是要求我们使用一个栈。

      代码如下:

复制代码
void PrintListReversingly(ListNode* pHead)
{
      std :: stack<ListNode*> nodes;

      ListNode* pNode = pHead;
      while(pNode != NULL)
      {
          nodes.push(pNode);
          pNode = pNode->m_pNext;
      }

      while(!nodes.empty())
      {
           pNode = nodes.top();
           printf("%d\t", pNode->m_nValue);
           nodes.pop();
      }
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值