• 链表相关笔试题


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

        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题)

          

  • 相关阅读:
    .Net中获取打印机的相关信息
    如何在windows server 2008上配置NLB群集
    jvm分析内存泄露
    JVM调优
    线程池工作队列饱和策略
    线程池的处理流程:
    Java的Executor框架和线程池实现原理(转)
    线程池实现原理详解:
    futer.get()(如果任务没执行完将等待)
    sql注入
  • 原文地址:https://www.cnblogs.com/qingjiaowoxiaoxioashou/p/6416649.html
Copyright © 2020-2023  润新知