• 《代码随想录》刷题笔记——链表篇【java实现】


    链表节点定义

    public class ListNode {
        // 结点的值
        int val;
    
        // 下一个结点
        ListNode next;
    
        // 节点的构造函数(无参)
        public ListNode() {
        }
    
        // 节点的构造函数(有一个参数)
        public ListNode(int val) {
            this.val = val;
        }
    
        // 节点的构造函数(有两个参数)
        public ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    *移除链表节点

    题目链接:https://leetcode.cn/problems/reverse-linked-list/

    【思路】

    有时候增加一个虚拟节点,可以让后面的操作都一致

    public static ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
    
        // 增加一个虚拟节点
        ListNode virtualNode = new ListNode();
        virtualNode.next = head;
    
        ListNode cur = virtualNode;
        ListNode next = cur.next;
        while (next != null) {
            if (next.val == val) {
                cur.next = next.next;
            } else {
                cur = next;
            }
            next = cur.next;
        }
    
        return virtualNode.next;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    *反转链表

    双指针

    • 时间复杂度: O(n)
    • 空间复杂度: O(1)

    在这里插入图片描述

    public static ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null;
        ListNode temp = null;
        while (cur != null) {
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    递归法

    • 时间复杂度: O(n)
    • 空间复杂度: O(n)

    这里的逻辑和双指针法的逻辑是一样的,只是实现方式是递归,那为什么空间复杂度更高呢?

    答:因为递归本质上是一种函数调用,在每一次递归调用时,都会在函数栈中分配一段内存空间来保存函数的局部变量、返回地址以及参数等信息。因此,递归实现相对于循环实现的空间复杂度更高,会占用更多的内存空间。尤其是在递归深度较大的情况下,可能会导致栈溢出等问题。而使用循环实现通常不需要使用额外的栈空间,因此循环实现的空间复杂度比递归实现要低

    public static ListNode reverseList1(ListNode head) {
        return reverse(null, head);
    }
    
    public static ListNode reverse(ListNode pre, ListNode cur) {
        if (cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = reverse(cur, temp);
        }
        return pre;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    *两两交换链表中的节点

    https://leetcode.cn/problems/swap-nodes-in-pairs/

    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

    在这里插入图片描述

    public static ListNode swapPairs(ListNode head) {
        // 如果链表中没有元素 或者 只有一个元素,直接返回就行
        if (head == null || head.next == null) {
            return head;
        }
    
        // 增加一个虚拟头节点
        ListNode virtualNode = new ListNode();
        virtualNode.next = head;
        ListNode last = virtualNode;
        ListNode cur = head;
        ListNode next = cur.next;
        ListNode temp = null;
    
        while (next != null) {
            // 交换
            temp = next.next;
            cur.next = next.next;
            next.next = cur;
            last.next = next;
    
            // 指针移动到新的位置
            last = cur;
            cur = temp;
            if (cur != null) {
                next = cur.next;
            } else {
                break;
            }
        }
        return virtualNode.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

    *删除链表的倒数第N个节点

    https://leetcode.cn/problems/remove-nth-node-from-end-of-list/submissions/

    快慢指针法

    在这里插入图片描述

    /**
     * 让快指针比慢指针先移动 n 步
     *
     * @param head
     * @param n
     * @return
     */
    public static ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode virtual = new ListNode();
        virtual.next = head;
        // 让快指针先移动到n个节点
        ListNode fast = virtual;
        for (int i = 0; i < n; i++) {
            if (fast.next != null) {
                fast = fast.next;
            }
        }
    
        // slow和fast一起移动,等fast到达最后一个节点的时候,slow也就到达了要删除的节点前面的节点
        ListNode slow = virtual;
        while (fast.next != null) {
            slow = slow.next;
            fast = fast.next;
        }
    
        // 执行删除操作
        slow.next = slow.next.next;
        return virtual.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
    • 时间复杂度: O(n)
    • 空间复杂度: O(1)

    *链表相交

    https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

    注意点

    并不是判断val相等,而是hashcode相等

    暴力求解

    直接两重循环,循环两个链表

    • 时间复杂度:O(n^2)
    • 空间复杂度:O(1)

    进阶解法

    利用题目中的信息,已经是链表的尾部才有可能相交,可以先让一条链表前进 两条链表长度差值 的位置,最后再两条链表一起前进

    在这里插入图片描述

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int sizeA = 0;
        int sizeB = 0;
    
        // 统计链表A的长度
        while (cur1 != null) {
            sizeA += 1;
            cur1 = cur1.next;
        }
        // 统计链表B的长度
        while (cur2 != null) {
            sizeB += 1;
            cur2 = cur2.next;
        }
        // 回到初始位置
        cur1 = headA;
        cur2 = headB;
    
        // 先让元素较多的链表的指针移动一段距离
        if (sizeA > sizeB) {
            // 链表A的元素较多,先走一段距离
            for (int i = 0; i < (sizeA - sizeB); i++) {
                cur1 = cur1.next;
            }
        } else if (sizeA < sizeB) {
            for (int i = 0; i < (sizeB - sizeA); i++) {
                cur2 = cur2.next;
            }
        }
    
        // 两个链表同时走动
        while (cur1 != null && cur2 != null) {
            // 注意,需要的是hash值
            if (cur1.hashCode() == cur2.hashCode()) {
                return cur1;
            }
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return null;
    }
    
    • 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
    • 时间复杂度:O(n + m)
    • 空间复杂度:O(1)

    *环形链表

    https://leetcode.cn/problems/linked-list-cycle-ii/

    在这里插入图片描述

    public static ListNode detectCycle1(ListNode head) {
        if (head == null) {
            return null;
        }
    
        ListNode slow = head;
        ListNode fast = head;
        ListNode node2 = null;
    
        // 先找到相交点
        while (slow != null && fast.next != null) {
            // slow移动一步
            slow = slow.next;
            // fast移动两步
            fast = fast.next.next;
    
            if (slow.hashCode() == fast.hashCode()) {
                // slow 和 fast相交
                node2 = slow;
    
                // 让node1从起点出发,node2从相交点出发,当node1和node2相交的时候,相交时的节点即环的入口
                ListNode node1 = head;
                while (true) {
                    if (node1.hashCode() == node2.hashCode()) {
                        return node1;
                    } else {
                        node1 = node1.next;
                        node2 = node2.next;
                    }
                }
            }
        }
        return null;
    }
    
    • 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
    • 时间复杂度: O(n)。因为快慢指针相遇前,指针走的次数小于链表长度;快慢指针相遇后,两个node指针走的次数也小于链表长度,因此走的总次数小于 2n
    • 空间复杂度: O(1)
  • 相关阅读:
    消灭指标二义性!提效30%的指标管理如何炼成?
    华为机试 - 任务最优调度
    浅谈食品加工厂能耗情况分析平台解决方案
    试试这些方法,误删文件怎么恢复?
    前端模块化导入导出
    机器学习笔记 - 图解对象检测任务(2)
    苹果手机自身的ip地址怎么查
    线上问题定位分析宝典-Arthas篇
    一次服务器被入侵的处理过程分享
    java毕业设计班主任管理系统mybatis+源码+调试部署+系统+数据库+lw
  • 原文地址:https://blog.csdn.net/laodanqiu/article/details/132700388