数据结构(Java版)第七期:LinkedList与链表(二)

news2025/1/11 6:42:41

专栏:数据结构(Java版)

个人主页:手握风云

一、链表的实现(补)

        接上一期,下面我们要实现删除所有值为key的元素,这时候有的老铁就会想用我们上一期中讲到的remove方法,循环使用remove方法,去删除完值为key的元素。如下图所示,比如我们要删除值为22的节点,使用remove方法循环,此时这个算法的时间复杂度就会为O(n^{2}),算法效率就会比较低。那我们能不能只让cur遍历一遍这个链表,就删除所有值为22的节点呢?

    @Override
    public void removeAllKey(int key) {
        ListNode cur = head.next;
        ListNode prev = head;
        if(head == null){
            return;
        }
        while(cur != null){
            if(cur.val == key){
                prev.next = cur.next;
            }else{
                prev = cur;
            }
            cur = cur.next;
        }
    }

       这样我们就可以实现删除所有职位key的元素,但我们要思考一下,这段代码的问题。我们来运行测试一下。

public class Main {
    public static void main(String[] args) {
        IList mySingleList = new MySingleList();
        mySingleList.addLast(22);
        mySingleList.addLast(22);
        mySingleList.addLast(22);
        mySingleList.addLast(33);
        mySingleList.display();

        mySingleList.removeAllKey(22);
        mySingleList.display();
    }
}

     我们就会发现运行结果里面还有22。如下图所示,我们会看到,当cur走到第三个节点时,第二个22就会变成新的头,走得时候又会把新的22给忽略掉。我们可以这样解决这个问题。

    @Override
    public void removeAllKey(int key) {
        ListNode cur = head.next;
        ListNode prev = head;
        if(head == null){
            return;
        }
        while(head.val == key){
            head = head.next;
        }
        while(cur != null){
            if(cur.val == key){
                prev.next = cur.next;
            }else{
                prev = cur;
            }
            cur = cur.next;
        }
    }

二、链表中经典的面试题

2.1. 反转链表

    反转链表是将链表的结构进行反转,同时包括数据与地址。过程如下图所示。

       对于这道题,我们可以采用头插的思想来解决。我们需要定义两个变量cur和curNext,利用以下代码来解决。我们先把head.next置为空,把cur方法到第二个节点上,然后把第二个节点采用头插的方法进行插入。可我们把cur改了之后,就会找不到下一个节点了,我们再定义一个curNext

while(cur != null){
   curNext = cur.next;
   cur.next = head;
   head = cur;
   cur = curNext;
}

2.2. 链表的中间结点

        第一种方法,可以先求出链表长度,再定义一个引用,走到len/2的位置。但这种方法需要先定义cur节点去遍历一边数组得出链表的长度,再定义一个len变量走到中间节点的位置,这样就会需要遍历两遍链表。那我们能不能只遍历一遍数组得出中间节点。

       类比一下,试想两个人赛跑,其中一人是另一个人速度的两倍,当速度快的到达终点时,速度慢的刚到达中点。同样,我们定义一个fast和slow两个引用变量。fast一次走两步,slow一次走一步。如果链表有奇数个结点时,当fast.next==null时,slow指向中间结点;如果链表有偶数个结点时,当fast==null时,slow指向中间结点。

public class Solution {
    static class ListNode{
        private int val;
        private Solution.ListNode next;

        public ListNode(Solution.ListNode next) {
            this.next = next;
        }

        public ListNode(int val, Solution.ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
    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就会空指针异常
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

    public static void main(String[] args) {
        ListNode node5 = new ListNode(5,null);
        ListNode node4 = new ListNode(4,node5);
        ListNode node3 = new ListNode(3,node4);
        ListNode node2 = new ListNode(2,node3);
        ListNode node1 = new ListNode(1,node2);

        Solution solution = new Solution();
        ListNode middleNode = solution.middleNode(node1);
        System.out.println(middleNode.val);
    }
}

2.3. 返回倒数第k个结点

        我们依然可以参照上面的双引用的例子,先让slow不动,fast引用先走k-1步。然后两个引用在同时走。当fast走到最后的时候,slow就能走到倒数第k个结点。 

public class Solution {
    static 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;
        }
    }

    public int kthToLast(ListNode head, int k){
        ListNode fast = head;
        ListNode slow = head;
        int count = 0;
        while(count != k-1){
            fast = fast.next;
            count++;
        }
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow.val;
    }
}

       这样的代码还不是特别严谨的。加入我们输入了5个结点,要求我们返回第6个结点,那我们的fast就需要走5步,直接指向了空指针,我们可以再写一个if语句来返回-1。或者是我们可以写成异常来接收,但在OJ测试上,异常不会通过。

if(fast == null){
     return -1;
}

     以下为完整代码:

import java.util.Scanner;

public class Solution {
    static 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;
        }
    }

    public int kthToLast(ListNode head, int k){
        ListNode fast = head;
        ListNode slow = head;
        int count = 0;
        while(count != k-1){
            fast = fast.next;
            if(fast == null){
                return -1;
            }
            count++;
        }
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow.val;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        ListNode node5 = new ListNode(5,null);
        ListNode node4 = new ListNode(4,node5);
        ListNode node3 = new ListNode(3,node4);
        ListNode node2 = new ListNode(2,node3);
        ListNode node1 = new ListNode(1,node2);

        int k = in.nextInt();

        Solution solution = new Solution();
        int result = solution.kthToLast(node1,k);
        System.out.println(result);
    }
}

2.4. 合并两个有序链表

       两个链表合并之后,要满足升序的条件,就需要对两个链表所指向的结点值进行比较,这就需要两个引用都不能为空。我们先定义一个傀儡结点newH,如上图所示,起初headA的val值比headB的val值小,那么headA就会指向下一个结点,再把0x23赋给我们的傀儡结点,再与headB的val值进行比较。那我们就可以写一个循环来对val进行比较。

       我们还需要再定义一个ListNode.tmp,当headA走到下一个结点时,tmp走到上一个结点,这样就能保证刚进行比较的两个结点中最小的结点值是新创建链表的最后一个结点。

while(headA != null && headB != null){
     if(headA.val < headB.val){
            tmp.next = headA;
            headA = headA.next;
            tmp = tmp.next;
     } else {
          tmp.next = headB;
          headB = headB.next;
          tmp = tmp.next;
     }
}

      在这个循环当中,一定会出现一种情况,其中一个链表先走完,而另一个链表还没有走完,此时先走完的链表已经指向空引用了,while循环就会跳出。我们利用下面的伪代码来遍历未完成的结点。

if(headA != null){
     tmp.next = headA;
} else {
   tmp.next = headB;
}

以下为完整代码:

public class Solution {
    static 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;
        }
    }
    public ListNode mergeTwoLists(ListNode list1, ListNode list2){
        ListNode newH = new ListNode(-1);
        ListNode tmp = newH;

        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                tmp.next = list1;
                list1 = list1.next;
            } else {
                tmp.next = list2;
                list2 = list2.next;
            }
            tmp = tmp.next;
        }
        if(list1 != null){
            tmp.next = list1;
        } else {
            tmp.next = list2;
        }
        return newH.next;
    }

    public static void main(String[] args) {
        ListNode list1 = new ListNode(1);
        list1.next = new ListNode(2);
        list1.next.next = new ListNode(4);

        ListNode list2 = new ListNode(0);
        list2.next = new ListNode(3);
        list2.next.next = new ListNode(4);

        Solution solution = new Solution();
        ListNode mergedList = solution.mergeTwoLists(list1,list2);

        while (mergedList != null) {
            System.out.print(mergedList.val + " ");
            mergedList = mergedList.next;
        }

    }
}

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

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

相关文章

初学stm32 --- ADC单通道采集

目录 ADC寄存器介绍&#xff08;F1&#xff09; ADC控制寄存器 1(ADC_CR1) ADC控制寄存器 2(ADC_CR2) ADC采样时间寄存器1(ADC_SMPR1) ADC采样时间寄存器2(ADC_SMPR2) ADC规则序列寄存器 1(ADC_SQR1) ADC规则序列寄存器 2(ADC_SQR2) ADC规则序列寄存器 3(ADC_SQR3) AD…

Eclipse配置Tomcat服务器(最全图文详解)

前言&#xff1a; 本章使用图文讲解如何在Eclipse开发工具中配置Tomcat服务器、如何创建和启动JavaWeb工程&#xff0c;欢迎童鞋们互相交流。觉得不错可以三连订阅喔。 目标&#xff1a; 一、配置Tomcat服务器 1. 切换Eclipse视图 2. 打开菜单 3. 找到服务选项 4. 选择…

Apache Hudi vs Delta Lake vs Apache Iceberg

[一]功能对比 Hudi Delta Lake Iceberg 读写功能对比 ACID Transactions 我可以对列式文件进行版本控制和重写吗&#xff1f; Copy-On-Write 我可以在不重写整个文件的情况下高效地摊销更新吗&#xff1f; Merge-On-Read 我可以高效地将初始加载布局到表中吗&…

鸿蒙UI开发——日历选择器

1、概 述 在项目开发中&#xff0c;我们时常会用到日历选择器&#xff0c;效果如下&#xff1a; ArkUI已经为我们提供了组件&#xff0c;我们可以直接使用&#xff0c;下面针对日历组件做简单介绍。 2、CalendarPickerDialog 接口定义如下&#xff1a; // 定义日历选择器弹…

磁盘满造成业务异常问题排查

最近遇到一个因为磁盘满导致的问题&#xff0c;分享一下&#xff0c;希望能够帮助到以后遇到同样问题的朋友。 早上突然收到业务老师反馈说&#xff1a;上传文件不能正常上传了。 想想之前都好好的&#xff0c;最近又没有更新&#xff0c;为什么突然不能使用了呢&#xff1f;…

Java SPI机制介绍及原理分析

概念介绍 SPI 即 Service Provider Interface &#xff0c;字面意思就是&#xff1a;“服务提供者的接口”&#xff0c;我的理解是专门给服务提供者使用的接口&#xff0c;也就是定义接口的人&#xff0c;和实现接口的人并不是同一个人 SPI 将服务接口和具体的服务实现分离开来…

数据分析-55-时间序列分析之获取时间序列的自然周期时间区间

文章目录 1 获取某年的总天数1.1 get_year_days()1.2 应用函数2 获取某年的总周数2.1 get_year_weeks()2.2 应用函数3 获取某日期属于某年的周数3.1 get_time_yearweek()3.2 应用函数4 获取某年某周的开始时间和结束时间4.1 get_week_start_end()4.2 应用函数5 获取往前num周期…

基于Spring Boot的房屋租赁系统源码(java+vue+mysql+文档)

项目简介 房屋租赁系统实现了以下功能&#xff1a; 基于Spring Boot的房屋租赁系统的主要使用者管理员可登录系统后台&#xff0c;登录后可对系统进行全面管理&#xff0c;包括个人中心、公告信息管理、租客管理、户主管理、房屋信息管理、看房申请管理、租赁合同管理、收租信…

MySQL--2.1MySQL的六种日志文件

大家好&#xff0c;我们来说一下MySQL的6中日志文件。 1.查询日志 查询日志主要记录mysql的select查询的&#xff0c;改配置是默认关闭的。不推荐开启&#xff0c;因为会导致大量查询日志文件储存占用你的空间。 举例查询一下 select * from class&#xff1b; 开启查询日志的命…

汽车供应链关键节点:物流采购成本管理全解析

在汽车行业&#xff0c;供应链管理是一项至关重要的任务。汽车制造从零部件的生产到整车的交付&#xff0c;涉及多个环节&#xff0c;其中物流、采购与成本管理是核心节点。本文将深入分析这些关键环节&#xff0c;探讨如何通过供应商管理系统及相关工具优化供应链管理。 一、…

Phidata源码分析

https://www.phidata.app/是一家agent saas公司&#xff0c;他们开源了phidata框架&#xff0c;从github介绍上看(https://github.com/phidatahq/phidata)&#xff0c;功能很齐全&#xff0c;我们来学习一下。 首先&#xff0c;明确目的&#xff0c;我想了解下面的实现方式&…

TypeScript Jest 单元测试 搭建

NPM TypeScript 项目搭建 创建目录 mkdir mockprojectcd mockproject初始化NPM项目 npm init -y安装TypeScript npm i -D typescript使用VSCode 打开项目 创建TS配置文件tsconfig.json {"compilerOptions": {"target": "es5","module&…

FPGA技术的深度理解

目录 引言 FPGA的基本原理 结构组成 工作原理 FPGA的设计流程 设计阶段 编程阶段 实现阶段 FPGA的应用领域 FPGA编程技巧和示例代码 编程技巧 示例代码 结论 引言 FPGA&#xff08;现场可编程门阵列&#xff09;是一种可编程的集成电路&#xff0c;它允许用户根据…

Mysql进阶篇

一&#xff1a;存储引擎 二&#xff1a;索引 2.1 索引概述 索引&#xff08;index&#xff09;帮助mysql高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&…

《Spring Framework实战》15:4.1.4.6.方法注入

欢迎观看《Spring Framework实战》视频教程 方法注入 在大多数应用场景中&#xff0c;容器中的大多数bean都是单例&#xff08;singletons&#xff09;的。当单例bean需要与另一个单例bean协作或非单例bean需与另一非单例bean协作时&#xff0c;通常通过将一个bean定义为另一个…

Flutter:使用FVM安装多个Flutter SDK 版本和使用教程

一、FVM简介 FVM全称&#xff1a;Flutter Version Management FVM通过引用每个项目使用的Flutter SDK版本来帮助实现一致的应用程序构建。它还允许您安装多个Flutter版本&#xff0c;以快速验证和测试您的应用程序即将发布的Flutter版本&#xff0c;而无需每次等待Flutter安装。…

目标客户营销(ABM)结合开源AI智能名片2+1链动模式S2B2C商城小程序的策略与实践

摘要&#xff1a;在数字化营销日益盛行的今天&#xff0c;目标客户营销&#xff08;Account Based Marketing, ABM&#xff09;作为一种高度定制化的营销策略&#xff0c;正逐步成为企业获取高质量客户、提升市场竞争力的重要手段。与此同时&#xff0c;开源AI智能名片21链动模…

docker(目录挂载、卷映射)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、目录挂载1.命令2.案例3.补充 二、卷映射1.命令2.案例 总结 前言 在使用docker部署时&#xff0c;我们如果要改变一些配置项目&#xff0c;不可能每次都进入…

opencv warpAffine仿射变换C++源码分析

基于opencv 3.1.0源代码 sources\modules\imgproc\src\imgwarp.cpp void cv::warpAffine( InputArray _src, OutputArray _dst,InputArray _M0, Size dsize,int flags, int borderType, const Scalar& borderValue ) {...if( !(flags & WARP_INVERSE_MAP) ){//变换矩阵…

使用 IntelliJ IDEA 创建简单的 Java Web 项目

以下是使用 IntelliJ IDEA 创建几个简单的 Java Web 项目的步骤&#xff0c;每个项目实现基本的登录、注册和查看列表功能&#xff0c;依赖 Servlet/JSP 和基本的 Java Web 开发。 前置准备 确保安装了 IntelliJ IDEA Ultimate&#xff08;社区版不支持 Web 应用&#xff09;。…