链表类(Linked List)


链表如何实现,如何遍历链表。链表可以保证头部尾部插入删除操作都是O(1),查找任意元素位置O(N),快慢指针和链表反转几乎是所有链表类问题的基础,尤其是反转链表,代码很短,建议直接背熟。
链表和二叉树是天然的递归数据结构,因此本专题一定要掌握递归的思想。
递归三部曲:
整个递归的终止条件。
一级递归需要做什么?
应该返回给上一级的返回值是什么?
参考博客:
https://lyl0724.github.io/2020/01/25/1/#vcomment

206. 反转链表

注:本题对递归又有了新的认知,前面学习递归的思想,更多的是不要用人脑去计算递归过程,而是用一种通用的办法直接得出答案,这种通用的过程称之为递推公式,后续部分已经完成了,只要关注第k个和第k+1个的逻辑即可!
那么本题新的认知是,递归是由递和归组成,递的过程反映的思想就是拆解成子问题的思想,归的过程就是在处理递推公式(完成指定功能),所谓的递归出口就是最小子问题的结果!
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 使用迭代法:充分定义好指针,这里需要三个指针,其中一个指针保存下一个节点信息,另外两个指针做好反转
        // 这种链表的迭代法一般画好图,做好记录节点信息,一步一步走就可以!
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null){
            // 若curr指针不空,记录下一个节点next
            ListNode next = cur.next;
            // cur指向前者
            cur.next = prev;
            // prev移动到cur
            prev = cur;
            // cur移动到下一个节点next
            cur = next;
        }
        // 最后返回最后一个节点:prev
        return prev;

    }
}

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 使用递归方式解决
        // 最小子问题非递归出口
        if(  head == null ||head.next == null){
            return head;
        }
        // 递归的递过程,这里的p实际上是最后的一个节点
        ListNode p = reverseList(head.next);
        // 递归的归过程:递归函数,也就是递推公式的处理
        // head下一个指针指向head
        head.next.next = head;
        // head的下一个指针指向null
        head.next = null;
        // 最后返回最后一个节点p
        return p;
    }
}

19. 删除链表的倒数第 N 个结点(快慢指针+虚拟头节点)

注意:在没有使用虚拟头节点的时候,需要考虑头节点的删除问题,容易漏掉因此一般链表一般都搭配虚拟头节点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        /**
        分析:
        寻找倒数第k个节点使用 快慢指针法+虚拟头节点法
         */
        ListNode fast = head,slow = head;
        // 快指针先走 n 步
        for(int i = 0; i < n; i++){
            fast = fast.next;
        }
        // 解决删除头节点问题
        if(fast == null){
            // 表示删除第一个节点
            return head.next;
        }
        // 遍历到链表末尾
        while(fast.next !=null){
            fast = fast.next;
            slow = slow.next;
        }
        // 此时 slow指向倒数第n个指针的前驱节点
        slow.next = slow.next.next;
        // 返回节点
        return head;

    }
}

使用虚拟头结点,这样子就不需要考虑原始头结点的删除问题(因为原始头结点的前驱是dummy虚拟节点)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head == null){
            return null;
        }
        // 创建一个虚拟头节点,其中 -1 代表虚拟头节点的值
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        // 快慢指针
        ListNode fast = dummy, slow = dummy;
        // 快指针先走 n+1,此时要考虑虚拟头的存在
        for(int i = 0; i <= n; i++){
            fast = fast.next;
        }
        // 快指针 不为空的情况下,快慢指针一直走
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        // 快指针到链表末尾 且为空了
        // slow指针指向倒数第k个节点的前驱
        slow.next = slow.next.next;
        // 返回节点
        return dummy.next;



    }
}

24. 两两交换链表中的节点(迭代+递归)

递归还是要好好理解,提取出递归终止条件后,要记得,我们只关系一个零部件的逻辑,剩下的,递归已经处理过了。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        /**
        分析:
        实际上是一个递归的问题,凡是递归的问题也可以使用迭代来解决

        class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null)  
            return head;
        ListNode rest = head.next.next;
        ListNode newHead = head.next;
        newHead.next = head;
        head.next = swapPairs(rest);
        return newHead;
    }
}
         */
        //  if(head == null || head.next == null){
        //      return head;
        //  }
        //  // 定义虚拟头结点
        //  ListNode dummy = new ListNode(-1);
        //  dummy.next = head;

        //  // 定义三个指针,用于交换节点
        //  ListNode pre = dummy;
        //  ListNode first = head;
        //  ListNode second = head.next;

        //  while(second != null){
        //      // 保存下一个节点信息
        //      ListNode next = second.next;
        //      // 开始交换节点
        //      pre.next = second;
        //      second.next = first;
        //      first.next = next;

        //      // 移动指针,进行后续的交换操作
        //      pre = first;
        //      first = next;
        //      // 如果first移动到了 null 则跳出循环
        //      if(first == null){
        //          break;
        //      }
        //      second = first.next;
        //  }
        //  // 返回节点
        //  return dummy.next;

        //递归动画  https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/dong-hua-yan-shi-die-dai-yu-di-gui-liang-ha0u/
        // 递归出口和递归函数是核心,那么我们只关注一个零部件的逻辑,其余的交给递归处理!!!

        // 递归出口
        if(head ==null || head.next == null){
            return head;
        }
        // 递归函数逻辑
        // subHead是后续swap处理过的链表
        ListNode subHead = swapPairs(head.next.next);
        // 交换链表节点:我只关心这个零部件的逻辑,morensubHead已经处理过了
        ListNode headNext = head.next;
        headNext.next = head;
        // 指向的业务逻辑 subHead默认处理过了
        head.next = subHead;
        return headNext;


    }
}

25. K 个一组翻转链表(递归)

本题为困难,但是分析出递归思路后,也还ok的
递归三部曲:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        /**
        分析:
        这是一个递归的问题,递归出口?
         */
         // 递归法,空间复杂度是O(n)
        // 递归出口:当节点为空,只有一个节点,或者剩余节点数目不够k个
        if(head == null || head.next == null){
            return head;
        }
        // 探寻剩余节点数目不够k个的出口
        ListNode end = head;
        for(int i = 0; i < k; i++){
            if(end == null){
                // 小于k个 返回头节点,为什么返回head?
                // 在递归的处理过程中只有:head head后面的逻辑 已完成处理的逻辑(返回值)
                return head;
            }
            end = end.next;
        }
        // end节点是 要翻转链表末尾节点的下一个,因此设计翻转链表要设计设计成左闭右开形式
        ListNode newHead = reverse(head,end);
        // 下一轮循环的起始节点就是end节点
        head.next = reverseKGroup(end,k);
        // 返回 newHead
        return newHead;
    }
    public ListNode reverse(ListNode start,ListNode end){
        // 翻转的原则是一边保存下一个节点,一般改变指针
        ListNode pre = null;
        ListNode next = null;
        while(start != end){
            // 保存下一个节点
            next = start.next;
            // 改变指针
            start.next = pre;
            // 移动指针
            pre = start;
            start = next;
        }
        return pre;

    }
}

迭代法:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        //定义一个假的节点。
        ListNode dummy=new ListNode(0);
        //假节点的next指向head。
        // dummy->1->2->3->4->5
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;


    }
    //链表翻转
    // 例子:   head: 1->2->3->4
    public ListNode reverse(ListNode head) {
         //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while (curNode != null){
            nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
            curNode.next=preNode;//将当前节点next域指向前一个节点   null<-1<-2<-3<-4
            preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
            curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
        }
        return preNode;

    }


}

82. 删除排序链表中的重复元素 II(虚拟头+三指针工作法)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        /**
        分析:
        一次遍历,注意记录前驱节点,工作节点,next节点
         */
         ListNode dummy = new ListNode(-1000);
         dummy.next = head;
         ListNode pre = dummy;
         ListNode cur = head;
         while(cur != null){
             // 记录 next
             ListNode next = cur.next;
             // 判断
             if(next != null && cur.val == next.val){
                 // 移动到非重复元素
                 while(next != null && cur.val == next.val){
                 next = next.next;
                }
                // 删除重复数字
                pre.next = next;
                // 摆正位置
                cur = next;
                // 跳过当前循环
                continue;
             }
             // 正常的移动
             pre = cur;
             cur = next;
         }
         return dummy.next;

    }
}

83. 删除排序链表中的重复元素(链表的操作)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        /**
        这不是上一题的思路~
         */
         ListNode dummy = new ListNode(-1000);
         dummy.next = head;

        //  ListNode pre = dummy;
         ListNode cur = head;
         while(cur != null){
             // 记录下一个节点
             ListNode next = cur.next;

             while( next != null && cur.val == next.val){
                 next = next.next;
             }
            //  pre = cur;
             cur.next = next;
             cur = next;

         }
         return dummy.next;

    }
}

92. 反转链表 II(链表的基操)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        /**
        分析:
        整体思想是分成三段来解决,先切开,然后翻转,最后拼接
         */
        if(left == right){
            return head;
        }
        ListNode dummy = new ListNode(-1000);
        dummy.next = head;

        ListNode pre = dummy;
        ListNode cur = dummy;
        for(int i = 1; i < left; i++){
            cur = cur.next;
        }
        // pre节点是 反转链表的前驱
        pre = cur;
        ListNode first = pre.next;
        for(int i = left; i <= right; i++){
            cur = cur.next;
        }
        // next节点是反转链表的后继
        ListNode next = cur.next;
        // 切断链表
        pre.next = null;
        // 切断链表
        cur.next = null;
        // 翻转链表,并连接链表
        pre.next = reverse(first);
        first.next = next;
        return dummy.next;

    }
    public ListNode reverse(ListNode left){
        ListNode pre = null;
        ListNode cur = left;
        while( cur != null){
            // 记录下一个节点信息
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

141. 环形链表(快慢指针)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        /**
        分析:
        经典快慢指针法
         */
         if(head == null||head.next == null){
             // 只有一个节点或者没有节点
             return false;
         }
         ListNode fast = head, slow = head;
         while(fast != null && fast.next != null){
             fast = fast.next.next;
             slow = slow.next;
             if(fast == slow){
                 return true;
             }
         }
         return false;

        
    }
}

142. 环形链表 II(快慢指针)

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        /**
        分析:
        首先判断有没有环,有环的话,找到环的入口即可。依旧是使用快慢指针法
         */
         if(head == null || head.next == null){
             return null;
         }
         ListNode fast = head, slow = head;
         // 设置标记
         boolean flag = false;
         while(fast != null && fast.next != null){
             fast = fast.next.next;
             slow = slow.next;
             if(fast == slow){
                 // 存在环
                 flag = true;
                 break;
             }
         }
         // 有环继续找 环入口
         if(flag){
             fast = head;
             while(fast != slow){
                 fast = fast.next;
                 slow = slow.next;
             }
             return fast;
         }
         // 没有环就return null
         return null;
        
    }
}

203. 移除链表元素(链表基操)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        /**
        分析:链表的基操
         */
         ListNode dummy = new ListNode(-1);
         dummy.next = head;

         ListNode pre = dummy;
         ListNode cur = head;
         while( cur != null){
             // 时刻保证 cur不空
             while( cur != null && cur.val != val){
             pre = cur;
             cur = cur.next;
         }
            // 时刻保证 cur 不空
            if(cur != null){
                ListNode next = cur.next;
                pre.next = next;
                cur = next;
            }
            
         }
         return dummy.next;
         

    }
}

237. 删除链表中的节点(链表基操)

从本题可以得知,删除链表元素,前驱节点还是必备得!

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public void deleteNode(ListNode node) {
        /**
        分析:
        按道理说应该是得有前驱节点才可以删除的,但是本题规定了每个节点的值是不一样的,那么就可以使用覆盖操作
         */
         // 前驱节点 用来删除最后一个节点
         ListNode pre  = null;
         while(node != null){
             ListNode next = node.next;
             if(next != null){
                 node.val = next.val;
             }else{
                 pre.next = null;
             }
             pre = node;
             node = node.next;
         }
        
    }
}

328. 奇偶链表(链表的基操+分治)

基本思想是分治,尤其要注意边界条件

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        /**
        分析:
        一提到原地算法,立马就想到快慢指针法,但是本题是链表啊,快慢指针原地修改是不现实的。
        切换思路,构建奇数链表和偶数链表,然后将奇数链表末尾串联偶数链表头~
         */
         // 特判
         if(head == null || head.next == null || head.next.next == null){
             // 对应状态为空节点 一个节点 两个节点
             return head;
         }
         // 记录奇数链表 和 偶数链表的工作指针
         ListNode odd = head, even = head.next;
         // 记录偶数链表的头节点,便于后续串联
         ListNode evenHead = head.next;
         // 当偶数节点不空时,构造两条链表
         // 保证odd节点一定是非空的
         while(even != null && even.next != null){
             // 构造奇数链表
             odd.next = even.next;
             odd = even.next;
             // 构造偶数链表
             even.next = odd.next;
             even = odd.next;
         }
         // 连接两条链表
         odd.next = evenHead;
         return head;


    }
}

725. 分隔链表(链表的基操)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode[] splitListToParts(ListNode head, int k) {
        /**
        分析:
        考察链表的基操
         */
         int len = 0;
         ListNode p = head;
         while(p != null){
             len++;
             p = p.next;
         }
        ListNode[] res = new ListNode[k];
        if( len <= k){
            // 小于k个,直接切割
            for(int i = 0; i < len; i++){
                ListNode next = head.next;
                head.next = null;
                res[i] = head;
                head = next;
            }
        }else{
            // 大于k个,就考虑切割分配个数问题
            // 主要围绕着 基数+余数的问题
            // 基数是  len / k ,余数是 len % k,若余数 = 2,表示前2个基数基础基础上+1
            ListNode temp = head;
            for(int i = 0; i < k; i++){
                // 找到循环分配后的尾结点
                ListNode tail = spilt(temp,len,k,i);
                // 记录下一个节点
                ListNode next = tail.next;
                // 切割
                tail.next = null;
                // 保存
                res[i] = temp;
                temp = next;
            }
        }

        return res;
    }
    public ListNode spilt(ListNode temp,int len,int k,int i){
        // count 是基数
        int count = len / k;
        // 判断余数 分配的回数,可以画图
        if(len % k >= (++i) ){
            count++;
        }
        ListNode tail = temp;
        // 找到尾结点
        for(int j = 0; j < count - 1; j++){
            tail = tail.next;
        }
        return tail;
    }
}

上面的代码是基于链表的切割,也就是在原地修改,那么完全可以采用另一种思路(复制链表),不在原地切割修改,而是复制出我们需要的链表出来,这样的代码更加简洁(但是需要空间复杂度)

class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        int len = 0;
        ListNode curr = root;
        while (curr != null) {
            len++;
            curr = curr.next;
        }

        int width = len / k;
        int remainder = len % k; // 余数

        ListNode[] res = new ListNode[k];
        curr = root;
        for (int i = 0; i < k; i++) {
            // 定义个虚拟节点
            ListNode dummyNode = new ListNode(-1);
            // 复制链表的节点
            ListNode node = dummyNode;
            // 定义真实长度
            int realWidth = width + (i < remainder ? 1 : 0);
            // 不断复制想要的链表
            for (int j = 0; j < realWidth; j++) {
                node.next = new ListNode(curr.val);
                node = node.next;
                if (curr != null) curr = curr.next;
            }
            // 将复制的链表储存起来
            res[i] = dummyNode.next;
        }
        return res;
    }
}

876. 链表的中间结点(快慢指针)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        // 想寻找倒数第K+1个节点,链表中间节点,这些都是使用快慢指针
        // 快指针走2步,慢指针走1步,最后慢指针就是中点了
        ListNode fast,slow;
        fast = slow = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;

    }
}

2. 两数相加(加法模板)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        /**
        分析:
        一般思路是将两个链表转换为数字,数字相加,得到答案,然后将答案逆序放回链表中.
        经过思考后,发现实现十分麻烦,不如直接将链表的对应数组相加,然后判断有无进位。
        这是一道加法模板题,只不过场景换成了链表
         */
         // 定义一个结果链表(有点像伪头部法)
         ListNode res = new ListNode();
         // 定义一个工作节点,指向首节点
         ListNode cur = res;
         // 默认进位为 0
         int carry = 0; 
         while(l1 != null || l2 != null){
             // 定义对应数字
             int x = l1 == null ? 0 : l1.val;
             int y = l2 == null ? 0 : l2.val;
             int sum = x + y + carry;
             // 构造节点
             cur.next = new ListNode(sum % 10);
             // 更新进位
             carry = sum / 10;
             // 移动工作节点
             cur = cur.next;
             // 移动l1和l2
             // 一般而言为了避免空指针异常,都要进行异常判断
             if(l1 != null){
                l1 = l1.next;
             }
             if(l2 != null){
                l2 = l2.next;
             }
         }
         // 处理最后一个进位信息
         if(carry != 0){
             // 尾巴增加一个节点
             cur.next = new ListNode(carry);
         }
         // 返回结果
         return res.next;

    }
}

21. 合并两个有序链表(虚拟头节点+递归法的理解)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        /**
        分析:
        通过不断比对,然后将节点添加到新链表中。
         */
         // 虚拟头结点的妙处
         ListNode dummmy = new ListNode(-1);
         ListNode cur = dummmy;
         // 两条链表都不为空 那么循环判断
         while(l1 != null && l2 != null){
             if(l1.val <= l2.val){
                 cur.next = l1;
                 l1 = l1.next;
                 cur = cur.next;
             }else{
                 cur.next = l2;
                 l2 = l2.next;
                 cur = cur.next;
             }
         }
         // 有一条链表为空了,直接连接下一条链表
         if(l1 == null){
            cur.next = l2;
        }else if(l2 == null){
            cur.next = l1;
         }
         return dummmy.next;

        // 

    }
}

使用递归解法:无非就是关注递归出口,本级节点逻辑,本级节点返回值

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 使用递归解法
        // 递归出口:当 l1为空,那么直接返回 l2,当l2为空,那么直接返回l1
        if( l1 == null) return l2;
        if( l2 == null) return l1;
        // 只关注本级节点做了什么,以及本级节点的返回值是什么
        if( l1.val <= l2.val){
            // l1 作为一个头节点,向下递,mergeTwoLists(l1.next,l2)是已经处理好的后续递归链表
            l1.next = mergeTwoLists(l1.next,l2);
            // 本级节点最后返回最终的链表头节点
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

23. 合并K个升序链表(小顶堆+顺序合并法)

顺序合并法

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        /**
        分析:
        是合并两个有序链表的升级版,既然是多个链表合并,那么完全可以将这k个链表两两合并,然后调用两两合并的代码。
        或者使用优先队列,按节点值构造小顶堆,然后依次取堆顶元素,构造一个新的队列
         */

         // 解法一:使用两两合并链表
         // 特判
        //  对于数组,不但要判断它是否为空指针,也需要判断它是否有内容,同时要先判断空指针再判断长度是否为0,顺序不能颠倒,因为空指针没有length属性
        // 常见的就是申请了一个对象空间为0的数组, int[] res = new int[0]; 此时长度为0
         if(lists == null || lists.length == 0) return null;
         // 取第一条链表
         ListNode res = lists[0];
         // 遍历合并
         for(int i = 1; i < lists.length; i++){
             res = merge_two_lists(res,lists[i]);
         }
         return res;
    }
    public ListNode merge_two_lists(ListNode l1,ListNode l2){
        if(l1 == null) return l2;
        if(l2 == null) return l1;

        if( l1.val <= l2.val){
            l1.next = merge_two_lists(l1.next,l2);
            return l1;
        }else{
            l2.next = merge_two_lists(l1,l2.next);
            return l2;
        }
    }
}

使用小顶堆,自定义数据结构要使用lambda表达式

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        // 使用优先队列,但是要注意,这里优先队列使用的是小顶堆,里面存放的是k条链表
        // 定义优先队列 默认小顶堆,但是listnode是自定义的,所以要使用lambada自定小顶堆
        if(lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> pq =  new PriorityQueue<>((a,b) -> a.val - b.val);
        // 储存
        for(ListNode node:lists){
            // 储存的是k条链表,按照链表头升序
            // 链表数组中有可能出现空链表的情况
            if(node == null) continue;
            pq.add(node);
        }
        // 定义虚拟头节点
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while( !pq.isEmpty()){
            // 取出链表头较小的点
            ListNode min = pq.remove();
            // 构造新链表
            cur.next = min;
            cur = cur.next;
            // 若取出的链表下面还有节点,那么重新加入队列中
            if(min.next != null){
                pq.add(min.next);
            }
        }
        return dummy.next;
    }
}

86. 分隔链表(分治思想)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
        /**
        分析:
        考察链表的基操:构建两条链表,一条小链表一条大链表
         */
         if(head == null || head.next == null) return head;
         ListNode smallHead = new ListNode(-1);
         ListNode small = smallHead;

         ListNode largeHead = new ListNode(-1);
         ListNode large = largeHead;

        while( head != null){
            if( head.val < x){
                // 构建小链表
                small.next = head;
                small = small.next;
            }else{
                // 构建大链表
                large.next = head;
                large = large.next;
            }
            // head移动
            head = head.next;
        }
        // 大链表 末尾为空
        large.next = null;
        // 小链表连接大链表
        small.next = largeHead.next;
        return smallHead.next;
         

    }
}

147. 对链表进行插入排序

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode insertionSortList(ListNode head) {
        /**
        分析:
        大的方向是考察链表的基操,小的方向是考察链表的插入操作
         */
         if(head == null || head.next == null){
             return head;
         }
         ListNode dummy = new ListNode(-1);
         dummy.next = head;
         ListNode pre = head;
         ListNode cur = head.next;
         while( cur != null){
             if(cur.val >= pre.val){
                 // 向后移动
                 pre = cur;
                 cur = cur.next;
             }else{
                 // 找到小于 cur的最大节点p
                 ListNode p = dummy;
                 while( p.next != null && p.next.val < cur.val){
                     p = p.next;
                 }
                 // 删除cur节点
                 pre.next = cur.next;
                 // 将cur插入到p和p.next之间
                 cur.next = p.next;
                 p.next = cur;
                 // 回正cur
                 cur = pre.next;
             }
         }
         return dummy.next;

    }
}

148. 排序链表(自顶向下归并排序)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode sortList(ListNode head) {
        /**
        分析:
        本题的时间复杂度要求是O(nlogn),自然会想到归并排序,快速排序和堆排序,链表的操作一般是使用归并排序。
        自顶向下的归并排序的核心是划分两个子链表(使用快慢指针法),然后将两个排好序的子链表合并(这里可以使用递归合并)
         */
         // 递归出口
         if(head == null || head.next == null) return head;
         // 找到中间节点
         ListNode fast = head.next, slow = head;
         while(fast != null && fast.next != null){
             // 快指针走两步
             fast = fast.next.next;
             // 慢指针走一步
             slow = slow.next;
         }
         // 记录右边子链表
         ListNode rightHead = slow.next;
         // 切割
         slow.next = null;
         // 递归寻找左子链表
         ListNode left = sortList(head);
         // 递归寻找右子链表
         ListNode right = sortList(rightHead);
         // 本级节点合并两个链表
         return merge(left,right);
    }
    // 定义合并两个链表的方法  21题合并两个链表
    // 这里使用两种办法,就当做是复习
    public ListNode merge(ListNode left,ListNode right){
        // 迭代法
        // ListNode dummy = new ListNode(-1);
        // ListNode cur = dummy;
        // while(left != null && right != null){
        //     if(left.val <= right.val){
        //         cur.next = left;
        //         cur =cur.next;
        //         left = left.next;
        //     }else{
        //         cur.next = right;
        //         cur = cur.next;
        //         right = right.next;
        //     }
        // }
        // if( left == null){
        //     cur.next = right;
        // } 
        // if( right == null){
        //     cur.next = left;
        // }
        // return dummy.next;

        // 递归法
        // 定义递归出口
        if( left == null) return right;
        if( right == null) return left;
        if( left.val <= right.val){
            left.next = merge(left.next,right);
            return left;
        }else{
            right.next = merge(left,right.next);
            return right;
        }
    }
}

160. 相交链表(数学方法)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        /**
        分析:
        解法一:哈希表走天下
        解法二:指针法,A指针走完就去走B,B指针走完就去走A,核心思想是 a+c+b = b+c+a,两个指针相遇的点就是焦点
         */
        // Set<ListNode> set = new HashSet<>();
        // while(headA != null){
        //     set.add(headA);
        //     headA = headA.next;
        // }
        // while(headB != null){
        //     if(set.contains(headB)){
        //         return headB;
        //     }
        //     headB = headB.next;
        // }
        // return null;


        if(headA == null || headB == null) return null;
        ListNode a = headA;
        ListNode b = headB;
        while( a != b){
            a = a != null ? a.next:headB;
            b = b != null ? b.next:headA;
        }
        return a;
    }
}

234. 回文链表(快慢指针找中间节点+反转链表)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        /**
        分析:
        双指针法:单链表的缺点就是无法往后走,那么从中间拆分两个链表然后一一比对,若值不同,那么必然不是回文链表
         */
         if(head == null || head.next == null) return true;
         // 通过快慢指针法找到中间节点
         ListNode fast = head.next, slow = head;
         while(fast != null && fast.next != null){
             fast = fast.next.next;
             slow = slow.next;
         }
         // 找到右边链表
         ListNode left = head;
         ListNode newHead = slow.next;
         // 切断链表
         slow.next = null;
         // 反转链表
        ListNode right = reverse(newHead);
         // 从右边的链表开始,因为左边的链表长度是大于等于右边的(考虑到奇数的存在)
         while(right != null){
             if(left.val != right.val) return false;
             left = left.next;
             right = right.next;
         }
         return true;
    }
    public ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode cur = head;
        while(cur != null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

445. 两数相加 II(反转链表 + 加法模板 + 头插法)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        /**
        分析:
        反转链表 + 加法模板 + 头插法
         */
         l1 = reverse(l1);
         l2 = reverse(l2);

         ListNode cur1 = l1;
         ListNode cur2 = l2;
         int pos = 0;
         ListNode dummy = new ListNode(-1);
        //  ListNode cur = dummy;
         while( cur1 != null || cur2 != null){
             // 定义节点值
             int x = cur1 == null ? 0 : cur1.val;
             int y = cur2 == null ? 0 : cur2.val;
             int sum = x + y + pos;
             // 生成节点
             ListNode temp = new ListNode(sum % 10);
             // 头插法
             temp.next = dummy.next;
             dummy.next = temp;
             // 更新进位
             pos = sum / 10;
             // 移动
             if(cur1 != null){
                cur1 = cur1.next;
             }
             if( cur2 != null){
                cur2 = cur2.next;
             }
         }
        // 判断最后进位
        if(pos != 0){
            ListNode temp = new ListNode(1);
             // 头插法
             temp.next = dummy.next;
             dummy.next = temp;
        }
        return dummy.next;

    }
    public ListNode reverse(ListNode head){
        ListNode pre = null;
        ListNode cur = head;
        while(cur != null){
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值