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

简介: 《代码随想录》刷题笔记——链表篇【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;
    }
}

*移除链表节点

题目链接: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;
}

*反转链表

双指针

  • 时间复杂度: 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;
}

递归法

  • 时间复杂度: 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;
}

*两两交换链表中的节点

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;
}

*删除链表的倒数第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;
}
  • 时间复杂度: 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;
}
  • 时间复杂度: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;
}
  • 时间复杂度: O(n)。因为快慢指针相遇前,指针走的次数小于链表长度;快慢指针相遇后,两个node指针走的次数也小于链表长度,因此走的总次数小于 2n
  • 空间复杂度: O(1)
目录
相关文章
|
2月前
|
Java 数据安全/隐私保护
快手小红书抖音留痕工具,自动留痕插件工具,java代码开源
这个框架包含三个核心模块:主操作类处理点赞评论、配置管理类和代理管理类。使用时需要配合
|
1月前
|
算法 IDE Java
Java 项目实战之实际代码实现与测试调试全过程详解
本文详细讲解了Java项目的实战开发流程,涵盖项目创建、代码实现(如计算器与汉诺塔问题)、单元测试(使用JUnit)及调试技巧(如断点调试与异常排查),帮助开发者掌握从编码到测试调试的完整技能,提升Java开发实战能力。
188 0
|
2月前
|
Java 机器人 API
tiktok群控脚本,养号关注私信点赞脚本插件,java代码分享
这个代码模拟了一个社交机器人的基本行为模式,包括登录、关注、点赞、私信等操作。请注意
|
2月前
|
Java 编译器 数据库连接
Java异常处理:写出更健壮的代码
Java异常处理:写出更健壮的代码
145 0
|
4月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
2月前
|
安全 Java 测试技术
Java 项目实战中现代技术栈下代码实现与测试调试的完整流程
本文介绍基于Java 17和Spring技术栈的现代化项目开发实践。项目采用Gradle构建工具,实现模块化DDD分层架构,结合Spring WebFlux开发响应式API,并应用Record、Sealed Class等新特性。测试策略涵盖JUnit单元测试和Testcontainers集成测试,通过JFR和OpenTelemetry实现性能监控。部署阶段采用Docker容器化和Kubernetes编排,同时展示异步处理和反应式编程的性能优化。整套方案体现了现代Java开发的最佳实践,包括代码实现、测试调试
89 0
|
3月前
|
人工智能 前端开发 Java
Java 面试资料中相关代码使用方法与组件封装方法解析
这是一份详尽的Java面试资料代码指南,涵盖使用方法与组件封装技巧。内容包括环境准备(JDK 8+、Maven/Gradle)、核心类示例(问题管理、学习进度跟踪)、Web应用部署(Spring Boot、前端框架)、单元测试及API封装。通过问题库管理、数据访问组件、学习进度服务和REST接口等模块化设计,帮助开发者高效组织与复用功能,同时支持扩展如用户认证、AI推荐等功能。适用于Java核心技术学习与面试备考,提升编程与设计能力。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
87 6
Java 面试资料中相关代码使用方法与组件封装方法解析
|
2月前
|
SQL Java 数据库连接
Java 期末考试救急必备涵盖绝大多数核心考点及五大类经典代码助你过关
本文为Java期末考试复习指南,涵盖基础语法、面向对象编程、异常处理、文件操作、数据库连接五大核心考点,提供详细解析与实用代码示例,助力快速掌握重点,高效备考,轻松应对考试。
59 0
|
3月前
|
Java 调度 流计算
基于Java 17 + Spring Boot 3.2 + Flink 1.18的智慧实验室管理系统核心代码
这是一套基于Java 17、Spring Boot 3.2和Flink 1.18开发的智慧实验室管理系统核心代码。系统涵盖多协议设备接入(支持OPC UA、MQTT等12种工业协议)、实时异常检测(Flink流处理引擎实现设备状态监控)、强化学习调度(Q-Learning算法优化资源分配)、三维可视化(JavaFX与WebGL渲染实验室空间)、微服务架构(Spring Cloud构建分布式体系)及数据湖建设(Spark构建实验室数据仓库)。实际应用中,该系统显著提升了设备调度效率(响应时间从46分钟降至9秒)、设备利用率(从41%提升至89%),并大幅减少实验准备时间和维护成本。
221 0