• LeetBook 刷题笔记:链表(三)


    经典问题

    206. 反转链表(Easy)

    题目描述

    • 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
    • 提示:
      • 链表中节点的数目范围是 [0, 5000]。
      • -5000 <= Node.val <= 5000。

    举个栗子

    • 示例 1:
      • 输入:head = [1,2,3,4,5]。
      • 输出:[5,4,3,2,1]。
        在这里插入图片描述
    • 示例 2:
      • 输入:head = [1,2]。
      • 输出:[2,1]。
        在这里插入图片描述
    • 示例 3:
      • 输入:head = []。
      • 输出:[]。

    解题思路

    • 双指针迭代
      • 申请两个指针,第一个指针叫 pre,最初是指向 null 的。
      • 第二个指针 cur 指向 head,然后不断遍历 cur。
      • 每次迭代到 cur,都将 cur 的 next 指向 pre,然后 pre 和 cur 前进一位。
      • 都迭代完了(cur 变成 null 了),pre 就是最后一个节点了。
    • 递归解法
      • 递归解法就是先假设 head 节点之外的节点都已经反转成功了,那么 head 节点反转(head.next.next = head)则是递归的基本条件。
      • 递归的两个条件:
        • 终止条件是当前节点或者下一个节点==null。
        • 在函数内部,改变节点的指向,也就是 head 的下一个节点指向 head。

    代码来了

    /**
     * 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 pre = null;
            ListNode cur = head;
            while (cur != null) {
                /*
                 * 保存 cur 的下一个指针避免指针丢失
                 */
                ListNode tmp = cur.next;
                cur.next = pre;
                pre = cur;
                cur = tmp;
            }
            return pre;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    /**
     * 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;
            }
            /*
             * 这里的 cur 就是最后一个节点
             */
            ListNode cur = reverseList(head.next);
            /*
             * 如果链表是 1->2->3->4->5,那么此时的 cur 就是 5
             * 而 head 是 4,head 的下一个是 5,下下一个是空
    		 * 所以 head.next.next 就是 5->4
             * 防止链表循环,需要将 head.next 设置为空
             */
            head.next.next = head;
            head.next = null;
            /*
             * 每层递归函数都返回 cur,也就是最后一个节点
             */
            return cur;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    203. 移除链表元素(Easy)

    题目描述

    • 给你一个链表的头节点 head 和一个整数 val,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。
    • 提示:
      • 列表中的节点数目在范围 [0, 104] 内。
      • 1 <= Node.val <= 50。
      • 0 <= val <= 50。

    举个栗子

    • 示例 1:
      • 输入:head = [1,2,6,3,4,5,6], val = 6。
      • 输出:[1,2,3,4,5]。
        在这里插入图片描述
    • 示例 2:
      • 输入:head = [], val = 1。
      • 输出:[]。
    • 示例 3:
      • 输入:head = [7,7,7,7], val = 7。
      • 输出:[]。

    代码来了

    /**
     * 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(val - 1);
            dummy.next = head;
            /*
             * cur 为遍历结点的前一个结点
             */
            ListNode cur = dummy;
            /*
             * 顺序遍历,如果 cur 的下一个节点非空且等于 val, 则删除它
             */
            while (cur.next != null) {
                if (cur.next.val == val) {
                    cur.next = cur.next.next;
                }else {
                    cur = cur.next;
                }
            }
            return dummy.next;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    328. 奇偶链表(Medium)

    题目描述

    • 给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
    • 第一个节点的索引被认为是奇数, 第二个节点的索引为偶数,以此类推。
    • 请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
    • 你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
    • 提示:
      • n == 链表中的节点数。
      • 0 < = n < = 1 0 4 0 <= n <= 10^4 0<=n<=104
      • − 1 0 6 < = N o d e . v a l < = 1 0 6 -10^6 <= Node.val <= 10^6 106<=Node.val<=106

    举个栗子

    • 示例 1:
      • 输入: head = [1,2,3,4,5]。
      • 输出: [1,3,5,2,4]。
        在这里插入图片描述
    • 示例 2:
      • 输入: head = [2,1,3,5,6,4,7]。
      • 输出: [2,3,6,7,1,5,4]。
        在这里插入图片描述

    解题思路

    • 如果链表为空,则直接返回链表。
    • 对于原始链表,每个节点都是奇数节点或偶数节点。
      • 头节点是奇数节点,头节点的后一个节点是偶数节点,相邻节点的奇偶性不同。
      • 因此可以将奇数节点和偶数节点分离成奇数链表和偶数链表,然后将偶数链表连接在奇数链表之后,合并后的链表即为结果链表。
    • 原始链表的头节点 head 也是奇数链表的头节点以及结果链表的头节点,head 的后一个节点是偶数链表的头节点。
      • 令 evenHead = head.next,则 evenHead 是偶数链表的头节点。
      • 维护两个指针 odd 和 even 分别指向奇数节点和偶数节点,初始时 odd = head,even = evenHead。
      • 通过迭代的方式将奇数节点和偶数节点分离成两个链表,每一步首先更新奇数节点,然后更新偶数节点。
        • 更新奇数节点时,奇数节点的后一个节点需要指向偶数节点的后一个节点,因此令 odd.next = even.next,然后令 odd = odd.next,此时 odd 变成 even 的后一个节点。
        • 更新偶数节点时,偶数节点的后一个节点需要指向奇数节点的后一个节点,因此令 even.next = odd.next,然后令 even = even.next,此时 even 变成 odd 的后一个节点。
    • 全部节点分离完毕的条件是 even 为空节点或者 even.next 为空节点,此时 odd 指向最后一个奇数节点(即奇数链表的最后一个节点)。
    • 最后令 odd.next = evenHead,将偶数链表连接在奇数链表之后,即完成了奇数链表和偶数链表的合并,结果链表的头节点仍然是 head。

    代码来了

    /**
     * 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) {
                return head;
            }
            /*
             * 在奇偶链表上设立头结点,偶数链表的头结点为 head.next;
             */
            ListNode oddDummy = new ListNode(0);
            ListNode evenDummy = new ListNode(0);
            oddDummy.next = head;
            evenDummy.next = head.next;
            /*
             * 遍历原链表,将奇数节点插入奇链表,偶数节点插入偶链表
             */
            ListNode curOdd = head;
            ListNode curEven = head.next;
            while(curEven != null && curEven.next != null) {
                curOdd.next = curEven.next;
                curOdd =curOdd.next;
                curEven.next = curOdd.next;
                curEven = curEven.next;
            }
            /*
             * 将偶数链表链接到奇数链表的后面
             */
            curOdd.next = evenDummy.next;
            return oddDummy.next;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    234. 回文链表(Easy)

    题目描述

    • 给你一个单链表的头节点 head,请你判断该链表是否为回文链表。
    • 如果是,返回 true;否则,返回 false。
    • 提示:
      • 链表中节点数目在范围 [ 1 , 1 0 5 ] [1, 10^5] [1,105] 内。
      • 0 <= Node.val <= 9。

    举个栗子

    • 示例 1:
      • 输入:head = [1,2,2,1]。
      • 输出:true。
        在这里插入图片描述
    • 示例 2:
      • 输入:head = [1,2]。
      • 输出:false。
        在这里插入图片描述

    解题思路

    • 避免使用 O(n) 额外空间的方法就是改变输入。
      • 我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。
      • 比较完成后我们应该将链表恢复原样。
      • 虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
    • 该方法虽然可以将空间复杂度降到 O(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 boolean isPalindrome(ListNode head) {
            if (head == null) {
                return true;
            }
    
            /*
             * 找到前半部分链表的尾节点并反转后半部分链表
             */
            ListNode firstHalfEnd = endOfFirstHalf(head);
            ListNode secondHalfStart = reverseList(firstHalfEnd.next);
    
            /*
             * 判断是否回文
             */ 
            ListNode p1 = head;
            ListNode p2 = secondHalfStart;
            boolean result = true;
            while (result && p2 != null) {
                if (p2.val != p1.val) {
                    return false;
                }
                p1 = p1.next;
                p2 = p2.next;
            }
    
            /*
             * 还原链表并返回结果
             */ 
            firstHalfEnd.next = reverseList(secondHalfStart);
            return result;
        }
    
        private ListNode reverseList(ListNode head) {
            ListNode prev = null;
            ListNode curr = head;
            while (curr != null) {
                ListNode nextTemp = curr.next;
                curr.next = prev;
                prev = curr;
                curr = nextTemp;
            }
            return prev;
        }
    
        private ListNode endOfFirstHalf(ListNode head) {
            ListNode fast = head;
            ListNode slow = head;
            while (fast.next != null && fast.next.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
  • 相关阅读:
    2023湖南省赛-B Square game
    SQL Server如何建表
    Java学习--day16(集合)
    OSPFv3报文讲解
    梳理一下我所知的输入输出流
    为什么说做亚马逊站外引流必须有独立站着陆页
    如何提升爬虫IP使用效率?精打细算的方法分享
    Nginx代理解决CORS跨域
    Go十大常见错误第8篇:并发编程中Context使用常见错误
    什么是Nginx?
  • 原文地址:https://blog.csdn.net/fangzhan666/article/details/125404225