数据结构------手撕链表(一)【不带头单向非循环】

请添加图片描述


文章目录

  • 链表概念
  • 链表的使用
  • LinkedList 的几种遍历方式
  • 单链表的模拟实现(不带头)
  • 链表面试题

  观察ArrayList 顺序表的源码发现,底层是使用数组实现的。由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。

链表概念

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
在这里插入图片描述

由上图可以看出链表在逻辑上是连续的,但是在逻辑上不一定连续

链表有很多不同的结构:
1.不带头单向非循环
2.不带头单向循环
3.不带头双向非循环
4.不带头双向循环
5.带头单向非循环
6.带头单向循环
7.带头双向非循环
8.带头双向循环
共八种不同的结构
本篇文章主要介绍 不带头单向非循环 的链表结构及其方法的实现。

链表的使用

LinkedList的构造
在这里插入图片描述
创建LinkedList的代码示例如下:

public static void main(String[] args) {
// 构造一个空的LinkedList
List<Integer> list1 = new LinkedList<>();
List<String> list2 = new java.util.ArrayList<>();
list2.add("JavaSE");
list2.add("JavaWeb");
list2.add("JavaEE");
// 使用ArrayList构造LinkedList
List<String> list3 = new LinkedList<>(list2);
}

LinkedList的常用方法
在这里插入图片描述

LinkedList 的几种遍历方式

public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
System.out.println(list.size());
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
} 
System.out.println();
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
} 
System.out.println();
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
} 
System.out.println();
}

单链表的模拟实现(不带头)

LinkedList中常用的方法如下:

// 1、无头单向非循环链表实现
public class SingleLinkedList {
//头插法
public void addFirst(int data){
} 
//尾插法
public void addLast(int data){
} 
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
} 
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
return false;
} 
//删除第一次出现关键字为key的节点
public void remove(int key){
}
//删除所有值为key的节点
public void removeAllKey(int key){
} 
//得到单链表的长度
public int size(){
return -1;
}
//清空链表
public void clear() {
}
//打印链表
public void display() {}
}

核心就是遍历链表,所有方法的具体实现如下:

public class MySingleList {//内部类   节点static class ListNode{public int val;public ListNode next;public ListNode(int val) {this.val = val;}}ListNode head;//创建单链表public void createList() {ListNode node1 = new ListNode(11);ListNode node2 = new ListNode(23);ListNode node3 = new ListNode(56);ListNode node4 = new ListNode(42);head = node1;node1.next = node2;node2.next = node3;node3.next = node4;}//打印单链表public void show() {ListNode cur = head;while(cur != null) {System.out.print(cur.val +" ");cur = cur.next;}System.out.println();}//计算单链表的长度public int size() {ListNode cur = head;int count = 0;while(cur != null) {count++;cur = cur.next;}return count;}//查找是否包含keypublic boolean contains(int key) {ListNode cur = head;while(cur != null) {if(cur.val == key) {return true;}cur = cur.next;}return false;}//头插法public void headAdd(int val) {ListNode headAdd = new ListNode(val);headAdd.next = head;head = headAdd;}//尾插法public void endAdd(int val) {ListNode endAdd = new ListNode(val);ListNode cur = head;while(cur.next != null) {cur = cur.next;}cur.next = endAdd;}//在index位置插入数据valpublic void addIndex(int index,int val) {if(index < 0 || index > size()-1) {throw new OutOfIndexException("index不合法");}else if(index == 0) {headAdd(val);}else if(index == size()-1) {endAdd(val);}else {ListNode cur = head;for (int i = 1; i < index; i++) {cur = cur.next;}ListNode addVal = new ListNode(val);addVal.next = cur.next;cur.next = addVal;}}//删除数据第一次出现的keypublic void remove(int key) {if(head == null) return;ListNode cur = head;while(cur != null) {//判断第一个节点的val值是否为keyif(head.val == key) {head = head.next;return;}ListNode lastKeyNode = findLastKey(key);if(lastKeyNode == null) {return;//没有找到要删除 val值为key 的节点}lastKeyNode.next = lastKeyNode.next.next;return;}}//找到第一个值为key的节点  的前一个节点public ListNode findLastKey(int key) {ListNode cur = head;while(cur.next != null) {if(cur.next.val == key) {return cur;}cur = cur.next;}return null;}//删除 所有 值为key 的节点public void removeAll(int key) {if(head == null) return;ListNode cur = head.next;ListNode pre = head;while(cur != null) {if(cur.val == key) {pre.next = cur.next;cur = cur.next;}else {pre = cur;cur = cur.next;}}if(head.val == key) {head = head.next;}}//清空所有的节点public void clear() {if(head == null) return;ListNode cur = head;while(cur.next != null) {ListNode tmp = cur.next;cur.next = null;cur = tmp;}head = null;}
}

链表面试题

  1. 删除链表中等于给定值 val 的所有节点
    上述方法已经实现,代码示例如下:
    //删除 所有 值为key 的节点public void removeAll(int key) {if(head == null) return;ListNode cur = head.next;ListNode pre = head;while(cur != null) {if(cur.val == key) {pre.next = cur.next;cur = cur.next;}else {pre = cur;cur = cur.next;}}if(head.val == key) {head = head.next;}}

  1. 反转一个单链表。 OJ链接
    在这里插入图片描述
    【解题思路】每个链表中的结点都有自己的地址,假设原链表的指向如下图所示:
    在这里插入图片描述
    实现翻转链表就可以把后一个结点的next域,指向前一个结点,遍历整个链表之后,这样每一个结点的next域就会指向前一个结点,此时head走到了最后一个节点,返回head。实现这些操作后的链表中结点的指向如下图:
    在这里插入图片描述
class Solution {public ListNode reverseList(ListNode head) {if(head == null) return null;ListNode cur = head.next;  ListNode tmp = null;head.next = null;while(cur != null) {tmp = cur.next; //使用tmp标记cur的下一个指向的结点cur.next = head; //后一个结点的next域指向前一个结点head = cur; //head 向后走一步cur = tmp; //cur 向后走一步}return head;}
}

  1. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ链接
      方法一:先求出链表的长度 len,不管长度len 是奇数还是偶数,中间结点就是 head 向前移动 len/2 次后所在的结点。
    I. 先求出链表的长度:遍历链表,代码示例如下:
public int size(ListNode head) {if(head == null) {return -1;}int count = 0;ListNode cur = head;while(cur != null) {count++;cur = cur.next;}return count;}

II.求出长度len后,让head结点向前移动 len/2 次,此时head结点指向的就是中间节点,完整代码示例如下:

class Solution {public ListNode middleNode(ListNode head) {if(head == null) {return null;}int len = size(head);int count = len / 2;ListNode cur = head;while(count != 0) {cur = cur.next;count--;}return cur;}public int size(ListNode head) {if(head == null) {return -1;}int count = 0;ListNode cur = head;while(cur != null) {count++;cur = cur.next;}return count;}
}

  方法二:使用快慢指针。定义快指针 fast,慢指针 slow ,初始 fast 和 slow 都指向head,fast 一次向前移动两步,slow 一次向前移动一步,当fast走到最后时,此时slow就指向 中间结点。那么fast走到最后的循环终止条件如何设置? —> 当链表中结点个数为奇数时,fast走完 刚好指向最后一个结点,所以终止条件为 fast.next == null;当当链表中结点个数为偶数时,fast走完 刚好指向最后一个结点的下一个结点(也就是null),所以终止条件为 fast == null。所以只要满足这两个条件中的其中一个就会终止,转换到代码层面为: fast.next == null || fast == null因此while()中的条件应该为 fast.next != null && fast != null
注意 】这里while() 中的判断条件并不是fast.next != null || fast != null !!!
代码示例如下:

class Solution {public ListNode middleNode(ListNode head) {if(head == null) {return null;}ListNode fast = head;ListNode slow = head;while(fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}
}

4.输入一个链表,输出该链表中倒数第k个结点

这道题和上一道题一样的思路,也是有两种解决方法。
  方法一:求链表长度len ,让head 向后走 len - k 步,此时head 就指向了倒数第K个结点

        //输入一个链表,输出该链表中倒数第k个结点//方法一public int kthToLast(ListNode head, int k) {if(k <= 0 || k > size(head)) {return -1;}int len = size(head);int count = 0;ListNode cur = head;while(count < len -k) {cur = cur.next;count++;}return cur.val;}private int size(ListNode head) {int count = 0;while(head != null) {count++;head = head.next;}return count;}

  方法二:还是使用快慢指针,快指针 fast ,慢指针 slow,初始 fast 和 slow 都指向head ,此时需要先让fast 向前走 k-1 步,然后 fast 和 slow 绑着一块向前走,一次走一步,当 fast 走到最后时,slow 就指向了倒数第K个结点.代码示例如下:

        //方法二public int kthToLast2(ListNode head, int k) {if(k <= 0) {return -1;}ListNode fast = head;ListNode slow = head;int count = k - 1;while(count != 0) {fast = fast.next;if(fast == null) {return -1;}count--;}while(fast.next != null) {fast = fast.next;slow = slow.next;}return slow.val;}

  1. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。OJ
    链接
    两个有序链表的合并,一定是要遍历两个链表,最后返回拼接后的链表的头结点,
      我们可以定义一个伪结点 newH(用来表示合并后新链表的带头结点),newH.next 中存的是新链表的第一个结点,也是data域最小的结点,所以最终返回的结点为 newH.next 。
      定义 tmpH 结点,使 tmpH 指向 newH 所指向的对象,只操作tmpH 的next 域 按照升序来拼接结点,最终返回的是newH.next,此时tmpH已经完成了结点的拼接

  遍历两个链表,循环条件为 headA 和 headB 都不为null 。每次比较 headA.val 和 headB.val 的大小,哪个小 就把哪个结点 赋值给 tmpH.next。遍历完成之后,如果其中一个结点不为空,就把 这一个结点 赋给 tmpH.next(原本都是升序链表,连接后 后面也一定是升序)

本人注意:【newH 和 tmpH 的关系理解为 head 和 cur 的关系】

在这里插入图片描述代码示例如下:
  

    public ListNode mergeTwoLists(ListNode headA, ListNode headB) {if(headA == null && headB == null) return null;ListNode newH = new ListNode();ListNode tmpH = newH;while(headA != null && headB != null) {if(headA.val < headB.val) {tmpH.next = headA;tmpH = headA;headA = headA.next;}else {tmpH.next = headB;tmpH = headB;headB = headB.next;}}if(headA == null) {tmpH.next = headB;}else {tmpH.next = headA;}return newH.next;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java基础(7)图书管理系统

目录 1.前言 2.正文 2.1思路 2.2Book包 2.3people包 2.4operation包 2.5主函数 3.小结 1.前言 哈喽大家好吖&#xff0c;今天来给前面Java基础的学习来一个基础的实战&#xff0c;做一个简单的图书管理系统&#xff0c;这里边综合利用了我们之前学习到的类和对象&…

研发运营一体化(DevOps)能力成熟度模型

目录 应用设计 安全风险管理 技术运 持续交付 敏捷开发管理 基于微服务的端到端持续交付流水线案例 应用设计 安全风险管理 技术运 持续交付

Android调用系统相机录像并设置参数

最近要做一个 Android上的录像功能&#xff0c;由于之前做拍照功能完全是用自定义方式&#xff0c;太麻烦。故这次决定直接调用系统相机来录像。 一、添加权限 首先&#xff0c;添加必要的权限 <!-- 授予该程序使用摄像头的权限 --><uses-permission android:name&q…

Could not find artifact cn.hutool:hutool-all:jar:8.1 in central 导入Hutool报错

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.9</version></dependency> 引入hutool 8.1版本的工具…

攻防世界-流量分析WP

流量分析1来自 <攻防世界> 题目描述:流量分析&#xff0c;你知道这堆流量做了什么事情吗&#xff0c;你能恢复出来flag吗&#xff1f; 1&#xff0c;首先查看IPv4统计信息 如果该流量记录的是黑客的攻击行为产生的流量&#xff0c;那么出现频率最高的流量应该来自攻击者…

2024软考网络工程师笔记 - 第8章.网络安全

文章目录 网络安全基础1️⃣网络安全威胁类型2️⃣网络攻击类型3️⃣安全目标与技术 &#x1f551;现代加密技术1️⃣私钥密码/对称密码体制2️⃣对称加密算法总结3️⃣公钥密码/非对称密码4️⃣混合密码5️⃣国产加密算法 - SM 系列6️⃣认证7️⃣基于公钥的认证 &#x1f552…

论文笔记:LaDe: The First Comprehensive Last-mile Delivery Dataset from Industry

2023 KDD 1 intro 1.1 背景 随着城市化进程的加快和电子商务的发展&#xff0c;最后一公里配送已成为一个关键的研究领域 最后一公里配送&#xff0c;如图1所示&#xff0c;是指连接配送中心和客户的包裹运输过程&#xff0c;包括包裹的取件和配送除了对客户满意度至关重要外…

cpp的vector类

本篇将讲述vector类中的各种重要和常用函数&#xff08;begin&#xff08;&#xff09;、end&#xff08;&#xff09;、rbegin&#xff08;&#xff09;、rend&#xff08;&#xff09;cbegin&#xff08;&#xff09;、cend&#xff08;&#xff09; 、crbegin&#xff08;&a…

Vuejs设计与实现 — 渲染器核心:挂载与更新

前言 挂载 与 更新 是 渲染器 的核心功能&#xff0c;也是渲染器应该要提供的基本功能&#xff0c;而 挂载 和 更新 又是基于 VNode 虚拟节点的&#xff0c;因为 VNode 节点描述了其对应的 真实 DOM 应该是什么样子的。 挂载与卸载 VNode 节点 无论是 vue 还是 react 都引入…

k8s 综合项目笔记

综述 这篇笔记主要是为了记录下自己写 k8s 综合项目的过程。 由于自己之前已经写过简单的开发和运维项目&#xff0c;所以这里就结合一下&#xff0c;在搭建 k8s 集群后安装运维常用服务&#xff0c;比如 ansible 和 prometheus&#xff0c;用 NFS 实现数据存储同步&#xff0c…

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的&#xff1f;在文章开头我们提出这个疑问&#xff0c;带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容&#xff0c;在日常App 中非常常见&#xff0c;比如微博的发布与展示&#xff0c;朋友圈的发布与展示&#xff0c;都在使用富文…

LeetCode_231. 2 的幂_java

1、题目 231. 2 的幂https://leetcode.cn/problems/power-of-two/ 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n &#xff0c;则认为 n 是 2 的幂次方…

ComfyUI初体验

ComfyUI 我就不过多介绍了&#xff0c;安装和基础使用可以看下面大佬的视频&#xff0c;感觉自己靠图文描述的效果不一定好&#xff0c;大家看视频比较方便。 ComfyUI全球爆红&#xff0c;AI绘画进入“工作流时代”&#xff1f;做最好懂的Comfy UI入门教程&#xff1a;Stable D…

ArcGIS001:ArcGIS10.2安装教程

摘要&#xff1a;本文详细介绍arcgis10.2的安装、破解、汉化过程。 一、软件下载 安装包链接&#xff1a;https://pan.baidu.com/s/1T3UJ7t_ELZ73TH2wGOcfpg?pwd08zk 提取码&#xff1a;08zk 二、安装NET Framework 3.5 双击打开控制面板&#xff0c;点击【卸载程序】&…

dbt-codegen: dbt自动生成模板代码

dbt项目采用工程化思维&#xff0c;数据模型分层实现&#xff0c;支持描述模型文档和测试&#xff0c;非常适合大型数据工程项目。但也需要用户编写大量yaml描述文件&#xff0c;这个过程非常容易出错且无聊。主要表现&#xff1a; 手工为dbt模型编写yaml文件&#xff0c;这过…

STM32传感器模块编程实践(十一) ADC模数转换模块ADS1115简介及驱动源码

文章目录 一.概要二.ADS1115芯片介绍三.ADS1115芯片主要特性四.ADS1115模块接线说明五.ADS1115参考原理图六.通讯协议介绍七.STM32单片机与ADS1115模块实现电压采集实验1.硬件准备2.软件工程3.软件主要代码4.实验效果 八.源代码工程下载九.小结 一.概要 ADC&#xff0c;全称为…

认识和使用 Vite 环境变量配置,优化定制化开发体验

Vite 官方中文文档&#xff1a;https://cn.vitejs.dev/ 环境变量 Vite 内置的环境变量如下&#xff1a; {"MODE": "development", // 应用的运行环境"BASE_URL": "/", // 部署应用时使用的 URL 前缀"PROD": false, //应用…

JavaScript完整笔记

JS引入 JavaScript 程序不能独立运行&#xff0c;它需要被嵌入 HTML 中&#xff0c;然后浏览器才能执行 JavaScript 代码。 通过 script 标签将 JavaScript 代码引入到 HTML 中&#xff0c;有两种方式&#xff1a; 内部方式 通过 script 标签包裹 JavaScript 代码 我们将 &…

使用FRP搭建内网穿透服务(新版toml配置文件,搭配反向代理方便内网网站访问)【使用frp搭建内网穿透】

FRP&#xff08;Fast Reverse Proxy&#xff09;是一个高性能的反向代理应用程序&#xff0c;主要用于内网穿透。它允许用户将内部网络服务暴露到外部网络&#xff0c;适用于 NAT 或防火墙环境下的服务访问。 他是一个开源的 服务 如果大家不想用 花生壳 软件&#xff0c;可以尝…

卷积神经网络评价指标

1.评价指标的作用 1. 性能评估&#xff1a;评价指标提供了一种量化的方式来衡量CNN模型的性能。通过这些指标&#xff0c;我们可以了解模型在特定任务上的表现&#xff0c;比如图像分类、目标检测或图像分割等。 2. 模型比较&#xff1a;不同的模型架构或训练策略可能会产生不…