zoukankan      html  css  js  c++  java
  • 链表刷题总结

    链表刷题总结


    做了一段时间的链表题,多多少少也看了一些优秀题解,链表解题技巧无非就以下几种:

    1. 朴素解法,head = head.next遍历链表来解决问题;
    2. 双指针,甚至是三指针,在很多的链表题中发挥很大的作用;
    3. 快慢指针,快指针和慢指针以不同的速度同时遍历链表;
    4. 递归,大多数链表题都能用递归来解决;
    5. 哑节点dummy,或者是哨兵节点,可以简化链表的极端操作;

    朴素解法就不总结了,下面来说说我这几天对其他节点的感受和做过的题。

    双指针


    对于链表的题目,双指针的应用太多了。不论是删除链表的倒数第N个节点,还是回文链表,都会用到双指针。因为链表的随机访问效率差,当我们想要处于某个位置的节点的时,都需要去从头遍历,所以使用额外的指针来记录节点位置是非常有必要的。

    • 删除链表的倒数第N个节点,对于这个问题,使用双指针beforeafter,距离N个节点,即before先走N步,然后afterbefore一起走。当before到达链尾时,after指向倒数第N个节点。

      while (n-- > 0){
          before = before.next;
      }
      while (before.next != null){ // 结束循环后slow指向需要删除节点的前一个节点
          before = before.next;
          after = after.next;
      }
      
    • 回文链表:双指针分别指向头尾,像中间靠近。

      int front = 0;      // 双指针分别指向头尾,向中间靠经
      int back = vals.size() - 1;
      while (front < back){
          if (!vals.get(front).equals(vals.get(back))){  // 使用equals
          	return false;
           }
           front++;
           back--;
      }
      
    • 旋转链表:本质上是找到倒数第K个节点,但是K有可能大于链表的长度,所以可以先考虑将链表闭合,再找到相应位置断开这个环。

      // 记录链表节点数
      int n;                 
      for (n = 1; old_tail.next != null; n++){
          old_tail = old_tail.next;
      }
      
      // 形成闭环
      old_tail.next = head;  
      
      ListNode new_tail = head;
      for (int i = 0; i < n - k%n - 1; i++){
          new_tail = new_tail.next;
      }
      ListNode new_head = new_tail.next;
      
      // 断开
      new_tail.next = null;
      return new_head;
      
    • 两两交换链表中的节点:使用三个指针,需要交换的节点firstNodesecondNode,还有firstNode的前一个节点prevNode

      while ((head != null) && (head.next != null)) {
          ListNode firstNode = head;
          ListNode secondNode = head.next;
      	// 进行交换
      	prevNode.next = secondNode;
      	firstNode.next = secondNode.next;
      	secondNode.next = firstNode;
         	// 对prevNode和head重新赋值,准备下一次的交换 
          prevNode = firstNode;
          head = firstNode.next; // jump
      }
      

    链表中用到指针的地方非常多,同时在草稿纸上进行画图对解题也非常有帮助。

    快慢指针


    我觉得快慢指针其实也就是双指针的一种,只不过快慢指针是同时以不同的速度对链表进行遍历,来解决相关的问题。

    • 链表的中间节点:
      • 解决这个问题,我们当然可以对链表进行两次遍历。第一次遍历记录链表节点的总数,然后再遍历到链表的中点位置即可。
      • 如果使用快慢指针呢?我们将快指针fast的速度设为2,即每次走两步,将慢指针slow的速度设为1,每次走一步。这样,当fast走到末尾时,slow正好走到中间,完美解决题目。
      • 思考:这个和上面找到倒数第N个节点的不同在哪?
        • 使用双指针,两个指针不同时出发,这个时候我们对于先出发的指针需要先走多少步是已知的。
        • 而对于这个题来说,链表的总结点我们不知道,所以我们并不知道要先走多少步,所以快慢指针同时出发,因为fast的速度是slow的两倍,所以当fast走到末尾时,slow正好在中间。
    • 环形链表
      • 使用快慢指针解决环形链表问题,如果是环形链表,则在某一时刻快指针会追上慢指针,如果不是,快指针会先一步到达链表尾部,退出循环。
      • 当我们需要找到环形链表的环头时,将快指针的速度设为2,慢指针速度设为1,当快慢指针相遇时,它们不一定在环的起始节点,所以我们在找到了相遇节点后,该如何找到环开始的位置呢?
        • 数学思想:快指针的速度是慢指针的2倍,所以快指针走的路程是慢指针走的路程的两倍;
        • 在分段画图分析后,我们可以得到这样的结论:head出发与meet相遇,两指针的速度一样,相遇时即为环的起点。

    相关题目

    递归


    使用递归函数,避免复杂的更改指针变量指向操作,使解题更加简单。

    相关题目

    • (力扣)21.合并两个有序链表

      • 将两链表的头的值进行比较,较小的节点的next为下一层递归的结果,本级递归返回两链表头较小的节点

        if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
        
    • (力扣)23.合并K个升序链表

      • 归并排序,我们将链表数组分成两个,分别对小链表数组进行合并。
      • 通过递归将链表一直分割,直到链表只有1个时,此时链表是有序的,然后将两个有序的链表进行合并;
        • 先对子列表进行递归,返回对列表内排序好的头节点;
        • 然后再对当前的两个链表进行合并,合并调用上面21题的函数。
    • (力扣)24.两两交换链表中的节点

      • 终止条件

        • 当递归到链表为空或者只有1个元素时,无法进行交换,递归终止;
      • 返回值

        • 已经完成交换的子列表的头结点
      • 单次递归过程

        • 将待交换的两个节点设置为firstNodesecondNodefirstNode交换后在后面,所以firstNode.next指向下一层递归返回的子链表,而secondNodefirstNode位置交换,所以secondNode.next指向firstNode

          ListNode firstNode = head;
          ListNode secondNode = head.next;
          
          // 交换
          firstNode.next  = swapPairs(secondNode.next);
          secondNode.next = firstNode;
          
          // 返回
          return secondNode;
          
    • (力扣)203.移除链表元素

      • 递归边界,head == null

      • 先调用递归函数删除后面的目标元素,即head.next = removeElements(head.next, val);

      • 再判断当前节点是否等于目标元素

        • 相等,返回head.next;
        • 不相等,返回head
        public ListNode removeElements(ListNode head, int val) {
            if (head == null){
                return null;
            }
            head.next = removeElements(head.next, val);
            return head.val == val ? head.next : head;
        }
        
    • (力扣)206.反转链表

      • 递归边界

        • 当递归到链表为空或者只有1个元素时,不需要反转,递归终止;
      • 返回值

        • 返回已经反转好的子链表头节点
      • 本级任务

        • 将当前节点的下一节点的next指向当前节点
        public ListNode reverseList(ListNode head) {
            if (head == null || head.next == null) return head;
            ListNode p = reverseList(head.next);
            head.next.next = head;
            head.next = null;
            return p;
        }
        
    • (力扣)234.回文链表

      • cur先到尾节点,由于递归的特性再从后往前进行比较;

      • front是递归函数外的指针;

      • 如果cur.val != front.val,返回false

      • 否则,front向前移动并返回true

        本质上是同时在正向和逆向迭代,利用递归的特性,可以实现链表逆向迭代,翻转链表的递归方法就是利用这个思想。

    • (力扣)328.奇偶链表

      • 终止条件

        • 当递归到链表只有两个节点时,不需要再进行奇偶分离再合并,递归终止。
      • 返回值

        • 返回奇链表的尾节点。
      • 单次递归过程

        • 进行奇偶分离odd.next = even.next; even.next = odd.next.next;后,进入下一层递归。

    哑节点


    设置dummy节点,避免对链表第1个节点进行单独讨论。

    相关题目

    总结


    第一次这样写刷题总结,还在摸索当中,凑合看吧,多写几次肯定会有进步,加油!

    TO BE CONTINUED...
  • 相关阅读:
    [Noip模拟题]教主的魔法
    [Usaco2005 Jan]Muddy Fields泥泞的牧场
    机器学习-数据可视化神器matplotlib学习之路(三)
    机器学习-数据可视化神器matplotlib学习之路(二)
    机器学习-数据可视化神器matplotlib学习之路(一)
    机器学习-ID3决策树算法(附matlab/octave代码)
    Hive安装-windows(转载)
    windows下hadoop安装配置(转载)
    C#发送邮件异常:根据验证过程,远程证书无效
    C#多线程--线程池(ThreadPool)
  • 原文地址:https://www.cnblogs.com/ly-leah/p/13700622.html
Copyright © 2011-2022 走看看