当前位置: 首页 > article >正文

链表知识回顾

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

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

删除操作:即让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 nulllist.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) {// 如果链表为空,则直接返回nullif(head == null){return null;}// prev指针初始化为null,最后会成为反转后链表的头节点ListNode prev = null;// cur指针指向当前要处理的节点,开始时指向链表头headListNode 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后移到下一个节点,也就是之前保存的tempcur = 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 指向 firstsecond.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设为nullnodes.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;}
}

相关文章:

链表知识回顾

类型&#xff1a;单链表&#xff0c;双链表、循环链表 存储&#xff1a;在内存中不是连续存储 删除操作&#xff1a;即让c的指针指向e即可&#xff0c;无需释放d&#xff0c;因为java中又内存回收机制 添加节点&#xff1a; 链表的构造函数 public class ListNode {// 结点…...

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信号发…...

【数据结构入门训练DAY-18】信息学奥赛一本通T1331-后缀表达式的值

文章目录 前言一、题目二、解题思路总结 前言 本次训练内容&#xff1a; 栈的复习。栈模拟四则运算计算问题的练习。训练解题思维。 一、题目 从键盘读入一个后缀表达式&#xff08;字符串&#xff09;&#xff0c;只含有0-9组成的运算数及加&#xff08;&#xff09;、减…...

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;。 &…...

Qt中读写结构体字节数据

在Qt中读写结构体字节数据通常涉及将结构体转换为字节数组(QByteArray)或直接从内存中读写。以下是几种常见方法&#xff1a; 方法1&#xff1a;使用QDataStream读写结构体 cpp #include <QFile> #include <QDataStream>// 定义结构体 #pragma pack(push, 1) //…...

鸿蒙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;它专注于将多种形式的内容智能转换成…...

深入剖析 Java Web 项目序列化:方案选型与最佳实践

在 Java Web 开发中&#xff0c;“序列化”是一个你无法绕过的概念。无论是缓存数据、共享 Session&#xff0c;还是进行远程过程调用&#xff08;RPC&#xff09;或消息传递&#xff0c;序列化都扮演着底层数据搬运工的角色。它负责将内存中的 Java 对象转换成可传输或可存储的…...

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

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

零基础上手Python数据分析 (19):Matplotlib 高级图表定制 - 精雕细琢,让你的图表脱颖而出!

写在前面 —— 超越默认样式,掌握 Matplotlib 精细控制,打造专业级可视化图表 上一篇博客,我们学习了 Matplotlib 的基础绘图功能,掌握了如何绘制常见的折线图、柱状图、散点图和饼图,并进行了基本的图表元素定制,例如添加标题、标签、图例等。 这些基础技能已经能让我…...

将 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…...

PHP实现图片自动添加水印效果

<?php // 设置原始图片路径和水印图片路径 $original_image original.jpg; $watermark_image watermark.png;// 创建图片资源 $original imagecreatefromjpeg($original_image); $watermark imagecreatefrompng($watermark_image);// 获取图片尺寸 $original_width im…...

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

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

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

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

stm32(gpio的四种输出)

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

系统架构设计师:计算机组成与体系结构(如CPU、存储系统、I/O系统)案例分析与简答题、详细解析与评分要点

计算机组成与体系结构 10道案例分析与简答题 案例分析题&#xff08;5道&#xff09; 1. Cache映射与主存编址计算 场景&#xff1a;某计算机系统采用32位地址总线&#xff0c;主存容量为4GB&#xff0c;Cache容量为512KB&#xff0c;块大小为64B&#xff0c;使用4路组相联映射…...

Zookeeper 可观测性最佳实践

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

位运算---总结

位运算 基础 1. & 运算符 : 有 0 就是 0 2. | 运算符 : 有 1 就是 1 3. ^ 运算符 : 相同为0 相异为1 and 无进位相加位运算的优选级 不用在意优先级,能加括号就加括号给一个数 n ,确定它的二进制位中第 x 位是 0 还是 1? 规定: 题中所说的第x位指:int 在32位机器下4个…...

2. 什么是最普通的自动化“裸奔状态”?

什么是最普通的自动化"裸奔状态"&#xff1f;从大厂案例看测试代码的生存困境 一个典型的"裸奔代码"示例 # 打开目标网站 driver.get(http://test-site.com/login-page)# 登录操作 driver.find_element_by_id(user).send_keys(tester) driver.find_eleme…...

头歌java课程实验(函数式接口及lambda表达式)

第1关&#xff1a;利用lambda表达式对Book数组按多个字段进行排序 任务描述 本关任务&#xff1a;利用Comparator接口完成对Book数组同时按多个字段进行排序。 编程要求 1、本任务共有三个文件&#xff0c;可查看各文件的内容 2、无需修改SortBy.java枚举文件及Book.java类文…...

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

效果图 .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;在…...