链表知识回顾

news2025/4/21 18:24:43

类型:单链表,双链表、循环链表

存储:在内存中不是连续存储

删除操作:即让c的指针指向e即可,无需释放d,因为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;
    }
}

可以直接用java自带的LinkedList类实现链表的初始化

import java.util.LinkedList;

public class Main {
    public static void main(String[] args) {
        // 创建一个空的链表
        LinkedList<Integer> list = new LinkedList<>();

        // 向链表中添加元素
        list.add(1);
        list.add(2);
        list.add(3);

        // 打印链表内容
        System.out.println(list);
    }
}

链表的声明:

Java标准库提供了LinkedList类,位于java.util包中。它的特点如下:

  • 实现细节LinkedList底层通常实现为双向链表,这意味着每个节点除了指向下一个节点,还保存对前一个节点的引用。
  • 接口实现:除了实现List接口外,LinkedList还实现了Deque接口,使其既可以当作列表使用,也可以当作队列(或双端队列)使用。
  • 增删操作add(), remove(), offer(), poll()等方法在链表头尾插入或删除元素时性能较高。
  • 遍历操作:由于链表没有下标索引,随机访问通常较慢。如果频繁使用随机访问,可以考虑使用ArrayList,ArrayList 底层是基于动态数组实现的
LinkedList<Integer> list = new LinkedList<Integer>();
List<Integer> list1 =new LinkedList<Integer>();
List<Integer> list2 =new ArrayList<>();
LinkedList list3 = new LinkedList();

 上述是常见的链表的声明方式

第一种,变量的声明类型和实际实现类型都是LinkedList,可直接调用 LinkedList 类中特有的方法,并且声明了泛型Integer,确保只能存储 Integer 类型的数据,编译期间就能进行类型检查,避免了类型转换异常。

第二种,声明类型是接口 List类型,实际实现的类型确实LinkedList,但只能调用 List 接口中定义的方法。如果需要使用 LinkedList 特有的方法(如队列或双端队列相关的方法),则需要显式地进行类型转换。同样使用了泛型 <Integer>,确保类型安全。

第三种声明类型是接口List,实现的是ArrayList类型,ArrayList 支持快速随机访问,时间复杂度为 O(1)。在数组中间插入或删除元素时需要移动元素,时间复杂度为 O(n);而 LinkedList 在任意位置添加或删除(假如已经有相应节点引用)通常更高效。

第四种实现的是原始类型(因为没有使用泛型),编译器不会对集合中的数据进行类型检查,

手搓链表

public class Linked {
    static class Node{
        int data;
        Node next;
        public Node(){};
        public Node(int data){
            this.data = data;
            this.next =null;
        }
    }
    public static class SingleLinkedList{
        private Node head;
        public void addFirst(int data){
            Node newNode = new Node(data);
            newNode.next = head;
            head = newNode;
        }

        public void addLast(int data){
            Node newNode = new Node(data);
            if(head ==null){
                head = newNode;
                return;
            }
            Node curr = head;
            while(curr.next != null){
                curr = curr.next;
            }

            curr.next = newNode;
        }
        public boolean remove(int data){
            if(head == null){//若链表为空,则删除失败
                return false;
            }
            if(head.data == data){//还要先判断头节点是否时要删除的
                head = head.next;
                return true;
            }
            Node curr = head;
            while(curr.next != null && curr.next.data != data){
                curr = curr.next;
            }
            if (curr.next != null) {
                curr.next = curr.next.next;
                return true;
            }
            return false;
        }
        public void printList() {
            Node curr = head;
            while (curr != null) {
                System.out.print(curr.data + " ");
                curr = curr.next;
            }
            System.out.println("null");
        }
    }
    public static void main(String[] args) {
        SingleLinkedList list = new SingleLinkedList();
        list.addFirst(4);
        list.addFirst(3);
        list.addFirst(2);
        list.addFirst(1);
        list.addLast(5);
        list.addLast(6);
        list.printList();//1 2 3 4 5 6 null
        list.remove(6);
        list.printList();//1 2 3 4 5 null
    }
}

反转链表

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL,即右移

/**
 * 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) {
        // 如果链表为空,则直接返回null
        if(head == null){
            return null;
        }
        
        // prev指针初始化为null,最后会成为反转后链表的头节点
        ListNode prev = null;
        // cur指针指向当前要处理的节点,开始时指向链表头head
        ListNode cur = head;
        // temp用于保存当前节点cur的下一个节点,防止在改变指针关系后丢失引用
        ListNode temp = null;
        
        // 当当前节点不为空时,循环执行反转操作
        while (cur != null) {
            // 保存cur的下一个节点,防止链表断裂
            temp = cur.next; // 此时temp指向cur的下一节点
            
            // 将当前节点的next指针指向前一个节点,实现局部反转
            cur.next = prev; // 当前节点的next由原来的下一个节点变为前一个节点
            
            // 将prev移动到当前节点位置,为下一次反转操作做准备
            prev = cur;
            
            // 将cur后移到下一个节点,也就是之前保存的temp
            cur = temp;
        }
        
        // 循环结束后,prev指向反转后的链表头节点,返回它作为新的链表起点
        return prev;
    }
}

链表内两两交换

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

解1:

  • 创建一个虚拟头节点dummy指向head,定义current指针真相虚拟头节点
  • 进入循环体内,确定current每次后面都能有两个节点进行交换操作
  • 定义first和second分别指向第一个节点和第二个节点,
  • 然后让第二个节点指向第一个节点,第一个节点指向下一个要交换的第一个节点,最后让current指向交换后的第一个节点
  • 2,3,4循环操作,直至不够节点互换退出循环,返回虚拟头节点之后的head
public class Solution {
    public ListNode swapPairs(ListNode head) {
        // 创建哑结点,它的 next 指向原链表的头
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode current = dummy;
        
        // 循环条件:当前结点后面至少有两个节点
        while (current.next != null && current.next.next != null) {
            // 定义要交换的两个节点
            ListNode first = current.next;
            ListNode second = current.next.next;
            
            // 交换节点:
            // 1. 先将 first 指向 second 的下一个节点
            first.next = second.next;
            // 2. second 指向 first
            second.next = first;
            // 3. current 指向 second,完成与前面的连接
            current.next = second;
            
            // 移动 current,跳过刚才交换的两个节点
            current = first;
        }
        
        // 返回哑结点的 next,即新的头节点
        return dummy.next;
    }
}

解2:将链表转换为队列处理,在重建链表

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        // 1. 将链表节点存入数组
        List<ListNode> nodes = new ArrayList<>();
        ListNode current = head;
        while (current != null) {
            nodes.add(current);
            current = current.next;
        }

        // 2. 在数组中交换相邻节点
        for (int i = 0; i < nodes.size() - 1; i += 2) {
            // 交换nodes[i]与nodes[i+1]
            ListNode temp = nodes.get(i);
            nodes.set(i, nodes.get(i+1));
            nodes.set(i+1, temp);
        }

        // 3. 重建链表(根据交换后的数组重设每个节点的next指针)
        for (int i = 0; i < nodes.size() - 1; i++) {
            nodes.get(i).next = nodes.get(i + 1);
        }
        // 最后一个节点的next设为null
        nodes.get(nodes.size() - 1).next = null;

        // 4. 返回新的链表头(数组中第一个元素)
        return nodes.get(0);
    }
}

删除链表中倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

解1:首先找到长度,然后循环遍历找到那个结点的位置在删除,但过程中没有考虑到如果删除到头节点的情况

public ListNode removeNthFromEnd(ListNode head, int n) {
    // 1) 先算长度
    int len = 0;
    for (ListNode cur = head; cur != null; cur = cur.next) {
        len++;
    }
    // 2) n == len,删头
    if (n == len) {
        return head.next;
    }
    // 3) 找到第 (len - n) 个节点,它的 next 就是要删的
    ListNode cur = head;
    for (int i = 1; i < len - n; i++) {
        cur = cur.next;
    }
    // 4) 删除
    cur.next = cur.next.next;
    return head;
}

解2:利用虚拟头节点,和快慢指针,快指针先走n步,然后快慢指针一块走直至快指针走到头,这是慢指针指向待删除节点的前一个节点。创建虚拟头节点时,记得让虚拟头节点指向head

为什么尽量推荐使用虚拟头节点,因为可以避免头节点被删除或者更换情况。

设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode first =dummy;
        ListNode slow = dummy;
        for(int i=0;i<n;i++) {
            first = first.next;
        }
        while(first.next != null){
            first = first.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
}
}

链表相交 

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

思想:这里并不是简单的找到数值相等的节点,而是找到指针相等的节点,所以大概步骤就是;找到AB链表的长度,先让A指针走到和B链表长度一样的地方一起走,若有相同的节点,则返回值

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA=0,lenB=0;
        for (ListNode cur = headA; cur != null; cur = cur.next) {
            lenA++;
        }
        for (ListNode cur = headB; cur != null; cur = cur.next) {
            lenB++;
        }
        ListNode A = (lenA >= lenB) ? headA : headB;
        ListNode B = (lenA >= lenB) ? headB : headA;
        int gap = Math.abs(lenA - lenB);
        while(gap > 0){
            A = A.next;
            gap --;
        }
        while(A != null && B != null){
            if(A == B) return A;
            A = A.next;
            B = B.next;
        }
        return null;
    }
}

环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

思想:利用快慢指针,让快指针一次走两个,慢指针一次走一个,若有环则一定会相交。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head;
        // 注意这里用 &&,两者都非 null 时才能继续下走
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

环形链表2

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

思想:首先,利用快慢指针判断是否有环,并且在快慢指针相等的地方做标记,

最重要的是如何找到环的入口,在定义指向头结点的指针和指向快慢相等结点的指针,两个指针同时向前走,当相遇时即是环的入口。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2339600.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA学习(五)——DDS信号发生器设计

FPGA学习(五)——DDS信号发生器设计 目录 FPGA学习(五)——DDS信号发生器设计一、FPGA开发中常用IP核——ROM/RAM/FIFO1、ROM简介2、ROM文件的设置&#xff08;1&#xff09;直接编辑法&#xff08;2&#xff09;用C语言等软件生成初始化文件 3、ROM IP核配置调用 二、DDS信号发…

OpenCv高阶(六)——图像的透视变换

目录 一、透视变换的定义与作用 二、透视变换的过程 三、OpenCV 中的透视变换函数 1. cv2.getPerspectiveTransform(src, dst) 2. cv2.warpPerspective(src, H, dsize, dstNone, flagscv2.INTER_LINEAR, borderModecv2.BORDER_CONSTANT, borderValue0) 四、文档扫描校正&a…

性能比拼: Go vs Bun

本内容是对知名性能评测博主 Anton Putra Go (Golang) vs. Bun: Performance (Latency - Throughput - Saturation - Availability) 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 我对 Bun 在之前的基准测试中的出色表现感到惊讶&#xff0c;因此我决定将它与 Go …

定制化 Docsify 文档框架实战分享

&#x1f31f; 定制化 Docsify 文档框架实战分享 在构建前端文档平台时&#xff0c;我们希望拥有更友好的用户界面、便捷的搜索、清晰的目录导航以及实用的代码复制功能。借助 Docsify&#xff0c;我实现了以下几个方面的定制优化&#xff0c;分享给大家 &#x1f64c;。 &…

鸿蒙ArkUI之布局实战,线性布局(Column,Row)、弹性布局(Flex)、层叠布局(Stack),详细用法

本文聚焦于ArkUI的布局实战&#xff0c;三种十分重要的布局&#xff0c;线性布局、弹性布局、层叠布局&#xff0c;在实际开发过程中这几种布局方法都十分常见&#xff0c;下面直接上手 线性布局 垂直布局&#xff08;Column&#xff09; 官方文档&#xff1a; Column-行列…

测试基础笔记第七天

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、cat命令二、ls -al命令三、>重定向符号四、>>追加重定向符号五、less/more命令六、grep命令七、|管道符八、clear命令九、head命令十、tail命令十一、…

[Windows] Adobe Camera Raw 17.2 win/Mac版本

[Windows] Adobe Camera Raw 链接&#xff1a;https://pan.xunlei.com/s/VOOIAXoyaZcKAkf_NdP-qw_6A1?pwdpd5k# Adobe Camera Raw&#xff0c;支持Photoshop&#xff0c;lightroom等Adobe系列软件&#xff0c;对相片无损格式进行编辑调色。 支持PS LR 2022 2023 2024 2025版…

开源模型应用落地-Podcastfy-从文本到声音的智能跃迁-Gradio(一)

一、前言 在当今信息呈现方式越来越多样化的背景下&#xff0c;如何将文字、图片甚至视频高效转化为可听的音频体验&#xff0c;已经成为内容创作者、教育者和研究者们共同关注的重要话题。Podcastfy是一款基于Python的开源工具&#xff0c;它专注于将多种形式的内容智能转换成…

Python 深度学习实战 第11章 自然语言处理(NLP)实例

Python 深度学习实战 第11章 自然语言处理(NLP)实例 内容概要 第11章深入探讨了自然语言处理&#xff08;NLP&#xff09;的深度学习应用&#xff0c;涵盖了从文本预处理到序列到序列学习的多种技术。本章通过IMDB电影评论情感分类和英西翻译任务&#xff0c;详细介绍了如何使…

将 DeepSeek 集成到 Spring Boot 项目实现通过 AI 对话方式操作后台数据

文章目录 项目简介本项目分两大模块 GiteeMCP 简介环境要求项目代码核心实现代码MCP 服务端MCP 客户端 DeepSeek APIDockersse 连接ws 连接&#xff08;推荐&#xff09;http 连接 vue2-chat-windowCherry Studio配置模型配置 MCP调用 MCP 项目简介 在本项目中&#xff0c;我们…

《前端面试题之 Vue 篇(第三集)》

目录 1、 nvm的常用命令①.Node.js 版本与 npm 版本的对应关系②Vue2 与 Vue3 项目的 Node.js 版本分界线③版本管理实践建议 2、Vue2 项目搭建&#xff08;基于 vue-cli Webpack&#xff09;① 环境准备② 安装 Vue CLI&#xff08;脚手架&#xff09;③.创建项目&#xff08…

嵌入式C语言位操作的几种常见用法

作为一名老单片机工程师&#xff0c;我承认&#xff0c;当年刚入行的时候&#xff0c;最怕的就是看那些密密麻麻的寄存器定义&#xff0c;以及那些让人眼花缭乱的位操作。 尤其是遇到那种“明明改了寄存器&#xff0c;硬件就是不听话”的情况&#xff0c;简直想把示波器砸了&am…

基于Djiango实现中药材数据分析与可视化系统

中药材数据分析与可视化系统 项目截图 登录 注册 首页 药材Top20 药材价格 产地占比 历史价格 新闻资讯 后台管理 一、项目概述 中药材数据分析与可视化系统是一个基于Django框架开发的专业Web应用&#xff0c;致力于对各类中药材数据进行全面、系统的采集、分析和可视化展示…

stm32(gpio的四种输出)

其实GPIO这个片上外设的功能&#xff1a; 用于控制IO引脚。 CPU就如同大脑&#xff0c;而这些片上外设就如同四肢一样的关系 如图 —————————————————————————————— OK类比了以上 其实GPIO是有 八种工作模式的 这八种工作模式 因为GPIO是面向IO…

Zookeeper 可观测性最佳实践

Zookeeper 介绍 ZooKeeper 是一个开源的分布式协调服务&#xff0c;用于管理和协调分布式系统中的节点。它提供了一种高效、可靠的方式来解决分布式系统中的常见问题&#xff0c;如数据同步、配置管理、命名服务和集群管理等。本文介绍通过 DataKit 采集 Zookeeper 指标&#…

微信小程序三种裁剪动画有效果图

效果图 .wxml <image class"img inset {{status?action1:}}" src"{{src}}" /> <image class"img circle {{status?action2:}}" src"{{src}}" /> <image class"img polygon {{status?action3:}}" src&quo…

C语言笔记(鹏哥)上课板书+课件汇总(结构体)-----数据结构常用

结构体 目录&#xff1a; 1、结构体类型声明 2、结构体变量的创建和初始化 3、结构体成员访问操作符 4、结构体内存对齐*****&#xff08;重要指数五颗星&#xff09; 5、结构体传参 6、结构体实现位段 一、结构体类型声明 其实在指针中我们已经讲解了一些结构体内容了&…

git清理--解决.git文件过大问题

背景&#xff1a;为什么.git比我仓库中的文件大很多 为什么我的git中只有一个1KB的README&#xff0c;但是.git却又1G多&#xff1f;当我想把这个git库push到gitee时&#xff0c;还会报错&#xff1a; 根据报错信息&#xff0c;可看出失败的原因是&#xff1a;有文件的大小超过…

Jetson Orin NX 部署YOLOv12笔记

步骤一.创建虚拟环境 conda create -n yolov12 python3.8.20 注意&#xff1a;YOLOv12/YOLOv11/YOLOv10/YOLOv9/YOLOv8/YOLOv7a/YOLOv5 环境通用 步骤二.激活虚拟环境 conda activate yolov12 #激活环境 步骤三.查询Jetpack出厂版本 Jetson系列平台各型号支持的最高Jetp…

微服务2--服务治理与服务调用

前言 &#xff1a;本文主要阐述微服务架构中的服务治理&#xff0c;以及Nacos环境搭建、服务注册、服务调用&#xff0c;负载均衡以及Feign实现服务调用。 服务治理 服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。 服务注册&#xff1a;在…