链表相关笔试题

  在笔试面试考数据结构时,由于时间有限,所出的题不会是红黑树、平衡二叉树等比较复杂的数据结构。链表结构简单,题目规模小但需要仔细考虑细节,因此称为笔试面试中的高频考点。因此,下面总结出链表相关题目,以供复习。

    1.比较顺序表和链表的优缺点,说说他们分别在什么场景下使用?

    2.从尾到头打印单链表(剑指offer第五题)

    3.删除一个无头单链表的非尾节点

    4.在无头单链表的一个非头结点前插入一个节点

    5.单链表实现约瑟夫环

    6.逆置/反转单链表

    7.单链表排序(冒泡排序&快速排序)

    8.合并两个有序链表合并后依然有序

    9.查找链表的中间节点,要求只能遍历一次链表

    10.查找单链表倒数第K个节点,要求只能遍历一次链表

    11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。

    12.判断两个链表是否相交,若相交,求交点(假设链表不带环)

    13.判断两个链表是否相交,若相交,求交点(假设链表带环)

    14.复杂链表的复制,一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表

    15.求两个已排序链表中相同的数据。void UnionSet(ListNode* l1,ListNode* l2);

/////////////////////////////////////////////////////////////////////////////////////////////////////////(分割线)///////////////////////////////////////////////////

    1.比较顺序表和链表的优缺点,说说他们分别在什么场景下使用?

      首先我们从顺序表和链表的结构上来进行分析:
        (1)对于顺序表,无论是动态的还是静态的,他们都是连续的存储空间,在读取上时间效率比较高,可通过地址之间的运算来访问,但是在插入和删除时会出现比较麻烦的负载操作。

        (2)对于顺序表,因为是链式存储。因此在我们需要的时候我们才在堆上为他们开辟空间,链表对于插入删除比较简单,但是遍历的话需要多次跳转。

      其次,我们从顺序表和链表的空间申请方式来看:

        (1)对于顺序表,空间开辟是在顺序表已满的时候开辟,开辟次数较多的时候会出现较大的空间浪费

        (2)对于链表,空间是针对单个节点的,不存在多余的空间浪费。并且在碎片内存池的机制下,可以有效的利用空间。

      综上所述:顺序表一般用于查找遍历操作比较频繁的情况下使用,链表则针对于数据删除修改操作比较多的情况下使用。

    2.从尾到头打印单链表

      从尾到头打印单链表有两种解法,一种是利用栈把节点从头到尾push进去,利用栈先进后出的特点,从尾到头打印单链表节点,一种是利用递归,在输出现有节点之前输出下一个节点,循环直至最后一个节点,然后再将节点从尾到头依次打印。

     code1:利用栈

      void PrintTailToHead(ListNode* head)
      {
          stack<int> st;
          ListNode* p = head;
          while (p != NULL)
          {
              st.push(p->_data);
              p = p->_next;
          }
          while (!st.empty())
          {
              printf("%d->", st.top());
              st.pop();
          }
      }

     code2:利用递归

      void PrintTailToHead(ListNode* head)
     {
          if (head != NULL)
          {
              while (head->_next != NULL)
              {
                  PrintTailToHead(head->_next);
              }
          }
          printf("%d->", head->data);
     }

    3.删除一个无头单链表的非尾节点

      由于链表无头,所以用常规方法删除节点是不可能的。所以我们可以换种思路,将要删除的节点后面的节点的值赋给要删的节点,然后再把要删除节点的后面的节点删除,等于通过转换,为被删除节点创造了一个头结点。代码如下:

      void DeleteNotTailNode(ListNode* p)
     {
          ListNode* s = p->_next;
          assert(s);
          p->_data = s->_data;
          p->_next = s->_next;
          free(p);
     }

    4.在无头单链表的一个非头结点前插入一个节点

      这个题目跟上一个题目很像。在这个非头结点后面插入一个节点,把这个非头节点的值赋给新插入的节点,然后再把要插入的值赋给这个非头节点即可。

      void InsertNotHeadNode(ListNode* p, int data)
     {
          ListNode* s = (ListNode)malloc(sizeof(&ListNode));
          assert(s);
          s->_next =p->_next;
          p->_next = s;
          s->_data = p->_data;
          p->_data = data;
     }

    5.单链表实现约瑟夫环(剑指offer第45题)

       

    6.逆置/反转单链表(剑指offer第16题)

      

    7.单链表排序(冒泡排序&快速排序)

    8.合并两个有序链表合并后依然有序(剑指offer第17题)

      这个题比较简单,分别用指针指向两个链表,比较两个链表指针所指向节点的值,然后将节点取下来重新组成一个链表即可,代码如下:

      ListNode Merge(ListNode* head1, ListNode* head2)
     {
          if (head1 == NULL)
              return head2;
          if (head2 == NULL)
              return head1;
          ListNode* newhead = NULL;
          if (head1->_data < head2->_data)
          {
              newhead = head1;
              newhead->_next=Merge(head1->_next, head2);
          }
          if (head1->_data>head2->data)
          {
              newhead = head2;
              newhead->_next = Merge(head1, head2->_next);
          }
     }

    9.查找链表的中间节点,要求只能遍历一次链表

      查找链表的中间节点,但只能遍历一次链表,所以我们会想到用快慢指针来解决这个问题。定义一个快指针,每次走两步,载定义一个慢指针,每次走一步。等到快指针走到链表尾,慢指针所指向的节点就是链表的中间节点。代码如下:

      ListNode* FindMidNode(ListNode* head)
     {
          ListNode* fast;

        ListNode* slow;
          fast = head;
          slow = head;
          while (fast&&fast->_next)
          {
              slow = slow->_next;
              fast = fase->_next->_next;
          }
          retrun slow;
     }

    10.查找单链表倒数第K个节点,要求只能遍历一次链表(剑指offer第15题)

      其实这个题跟上面的题很像,稍微转化一下就能想出思路。我们可以定义两个指针,一个指针先走K步,然后两个指针同时移动,等到先走的指针走到链表尾部,后走的指针所指向的节点就是倒数第K个节点。要注意考虑链表的各种情况。代码如下:

      ListNode* FindKthNode(ListNode* head,int k)
      {
          if (head == NULL || k == 0)
              return NULL;    
          ListNode* fast;

       ListNode* slow;
          fast = head;
          slow = head;
          for (int i = 0; i < k - 1; ++i)   //要注意链表长度比K短的情况
          {
              if (fast->_next != NULL)
                  fast = fast->_next;
              else retrun NULL;
          }
          while (fast->_next != NULL)
          {
              fast = fast->_next;
              slow = slow->_next;
          }
          return slow;
     }

    11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。(剑指offer第56题)

    12.判断两个链表是否相交,若相交,求交点(假设链表不带环)

    13.判断两个链表是否相交,若相交,求交点(假设链表带环)

    14.复杂链表的复制,一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表(剑指offer第26题)

    15.求两个已排序链表中相同的数据。void UnionSet(ListNode* l1,ListNode* l2);

    16.在已排序的链表中删除链表中重复的结点(剑指offer第57题)

      

原文地址:https://www.cnblogs.com/qingjiaowoxiaoxioashou/p/6416649.html