无头单向非循环链表实现 and leetcode刷题

news2024/9/19 11:00:16

无头单向非循环链表实现

  • 1. 单链表的模拟实现
    • IList.java接口:
    • MySingleList.java文件:
  • 2. leetcode刷题
    • 2.1 获取链表的中间节点
    • 2.2 删除链表中所有值为value的元素
    • 2.3 单链表的逆置
    • 2.4 获取链表倒数第k个节点
    • 2.5 给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式
    • 2.6 判定链表是否是回文
    • 2.7 判定链表相交并求出交点
    • 2.8 判断链表带环
    • 2.9 求环的入口点
    • 2.10 合并两个有序链表

写在最前面,学习数据结构一定要结合画图!先画图分析,写出伪代码,再仔细分析伪代码是否成立,成立再写入题目中检验!

1. 单链表的模拟实现

单链表的模拟实现需要创建三个文件:IList.java接口文件,MySingleList.java文件,还有一个test.java测试文件。测试文件这里就不演示了。

IList.java接口:

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

MySingleList.java文件:

public class MySingleList implements IList{
    static class ListNode {
        public int val;
        public ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;

    public void creatList() {
        ListNode node1 = new ListNode(0);
        ListNode node2 = new ListNode(1);
        ListNode node3 = new ListNode(2);
        ListNode node4 = new ListNode(3);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }

    @Override
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    @Override
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        node.next = head;
        head = node;
    }

    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            return;
        }
        //找尾
        ListNode cur = head;
        while(cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
    }

    @Override
    public void addIndex(int index, int data) {
        if(index < 0 || index > size()) {
            System.out.println("index位置不合法!");
            return;
        }
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()){
            addLast(data);
            return;
        }
        ListNode node = new ListNode(data);
        ListNode cur = head;
        for (int i = 0; i < index-1; i++) {
            cur = cur.next;
        }
        node.next = cur.next;
        cur.next = node;
    }

    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    public void remove(int key) {
        if(head == null) {
            return;
        }
        if(head.val == key) {
            head = head.next;
            return;
        }
        ListNode pre = head;
        while(pre.next != null) {
            if(pre.next.val == key) {
                ListNode del = pre.next;
                pre.next =del.next;
                return;
            }
            pre = pre.next;
        }
    }


    @Override
    public void removeAllKey(int key) {
        if(head == null) {
            return;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == key) {
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        //该if语句只能放在最后面,如果头节点需要删除,
        //删除后有可能下一个节点(此时这个节点做头节点)依然是需要删除的
        //因此,只能放在最后,当后面的都删除好了,再检查头节点是否需要删除
        if(head.val == key) {
            head = head.next;
        }
    }

    @Override
    public int size() {
        int len = 0;
        ListNode cur = head;
        while(cur != null) {
            cur = cur.next;
            len++;
        }
        return len;
    }

    @Override
    public void clear() {
        head = null;
    }
}

2. leetcode刷题

2.1 获取链表的中间节点

题目链接:876. 链表的中间结点

注意:题目中说明当链表只有一个中间结点时,返回该节点;而当该链表有两个中间结点,返回第二个结点

解析:定义一对“快慢指针”,“快指针”为fast,一次走两步;“慢指针”为slow,一次走一步。

  • 当链表的结点个数为奇数个时,fast走到fast.next == null时,slow此时所在位置就是中间节点
  • 当链表的节点个数为偶数个时,fast走到fast == null时,slow此时所在位置就是中间节点

代码如下:

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

2.2 删除链表中所有值为value的元素

题目链接:203. 移除链表元素

这题的题解和模拟实现单链表的removeAllKey是一样的,故不再赘述。

代码如下:

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null) {
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        if(head.val == val) {
            head = head.next;
        }
        return head;
    }
}

2.3 单链表的逆置

题目链接:206. 反转链表

解析:只需将链表的每个箭头调转方向即可,即修改当前节点的next值为前一个节点的地址,修改后就无法获取下一个节点了,故需要一个curN来定位下一个节点,又由于是单链表,无法得到前一个节点的位置,所以还需要定义一个prev来定位前一个节点的位置

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

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) {
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        ListNode curN = head.next.next;
        pre.next = null;
        while(cur != null) {
            cur.next = pre;
            pre = cur;
            cur = curN;
            if(curN != null) {
                curN = curN.next;
            }
        }
        return pre;
    }
}

2.4 获取链表倒数第k个节点

题目链接:面试题 02.02. 返回倒数第 k 个节点

解析:定义一对“快慢指针”,”快指针“fast先走k步,然后”快指针“fast和”慢指针“slow一起一次走一步,直至fast == null结束,这时slow指向的便是倒数第k个节点

代码如下:

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

2.5 给定 x, 把一个链表整理成前半部分小于 x, 后半部分大于等于 x 的形式

题目链接:CM11 链表分割

注意:这题是将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序

public class Partition {
    public ListNode partition(ListNode head, int x) {
        // write code here
        if(head == null) {
            return null;
        }
        ListNode bs = null;//bs:beforestart
        ListNode be = null;//be:beforeend
        ListNode as = null;//as:afterstart
        ListNode ae = null;//ae:afterend
        ListNode cur = head;
        while(cur != null) {
            if(cur.val < x) {
                if(bs == null) {//找到bs和be的起始位置
                    bs = be = cur;
                }else {
                    be.next = cur;
                    be = cur;
                }
            }else {
                if(as == null) {//找到as和ae的起始位置
                    as = ae = cur;
                }else {
                    ae.next = cur;
                    ae = cur;
                }
            }
            cur = cur.next;
        }
        //ae的next需要手动置为null
        if(ae != null) {
            ae.next = null;
        }
        //如果链表的节点都大于x,则返回as
        if(bs == null) {
            return as;
        }
        //bs不为null,be自然也不为空
        be.next = as;
        return bs;
    }
}

2.6 判定链表是否是回文

题目链接:OR36 链表的回文结构

public class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        // write code here
        if(A == null) {
            return false;
        }
        if(A.next == null) {
            return true;
        }
        //1.找到中间节点
        ListNode mid = getMiddleNode(A);
        //2.反转后半部分
        ListNode as = reseverList(mid);
        mid.next = null;//一定要置null!
        //3.从前往后依次对比两个链表的val值是否相同
        ListNode bs = A;
        while(bs.next != null && as.next != null) {
            if(bs.val != as.val) {
                return false;
            }
            bs = bs.next;
            as = as.next;
        }
        if(bs.val != as.val) {
            return false;
        }
        return true;
    }
    private ListNode reseverList(ListNode head) {
        ListNode prev = head;
        ListNode cur = head.next;
        ListNode curN = cur.next;
        while(cur != null) {
            cur.next = prev;
            prev = cur;
            cur = curN;
            if(curN != null){
                curN = curN.next;
            }
        }
        return prev;
    }
    private ListNode getMiddleNode(ListNode A) {
        ListNode fast = A;
        ListNode slow = A;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

2.7 判定链表相交并求出交点

题目链接:160. 相交链表

解题思路:

  1. 分别求出两个链表的长度,并得到两链表的长度差值(正数)
  2. 先让长链表的“l指针”走长度差值步,再让“l指针”和“s指针”一起走,如果相遇,相遇点即为相交链表的交点,如果没有相遇,则最后l和s同时为null
  3. 检验当两个链表同时为null时,代码是否满足

代码如下:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA = size(headA);
        int lenB = size(headB);
         //先假设headA链表的长度大于headB链表
        ListNode l = headA;
        ListNode s = headB;
        int len = lenA-lenB;
        //如果是headB链表更长,则进入if语句,进行调换
        if(len < 0) {
            len = -len;
            l = headB;
            s = headA;
        }
        for(int i = 0; i < len; i++) {
            l = l.next;
        }
        while(l != s) {
            l = l.next;
            s = s.next;
        }
        if(l == null) {
            return null;//没相交
        }
        return l;
    }
    public int size(ListNode head) {
        int len = 0;
        ListNode cur = head;
        while(cur != null) {
            cur = cur.next;
            len++;
        }
        return len;
    }
}

2.8 判断链表带环

题目链接:141. 环形链表

解题思路:

  1. 定义一对“快慢指针”,快指针fast一次走两步,慢指针slow一次走一步
  2. 如果最后fast == slow,则说明该链表存在环形结构;如果最后 fast == null || fast.next == null,则说明该链表不存在环形结构
  3. 检验链表为null时,代码是否满足

代码如下:

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

2.9 求环的入口点

题目链接:142. 环形链表 II

解题思路:

  1. 先判断链表结构中是否存在环(在2.8代码中进行略微修改即可)
  2. 求交点: 让一个引用从链表起始位置开始,一个引用从相遇点位置开始,两个引用每次都走一步,最终相遇时的节点即为交点(原因如下)

数学推导:
在这里插入图片描述
代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            //这个if语句必须放在下面,否则该if语句第一次就会成立,
            //因为fast和slow第一次都是head
            if(fast == slow){
                break;
            }
        }
        if(fast == null || fast.next == null) {
            return null;
        }
        slow = head;
        while(fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

2.10 合并两个有序链表

题目链接:21. 合并两个有序链表

解题思路:

  1. 创建一个带头结点的单链表
  2. 依次对比两个链表的数值大小,小的尾插到新链表尾部
  3. 当一个链表被新链表连接完时,另一个链表剩下的部分直接尾插到新链表的尾部
  4. 检验当一个链表为null时,代码是否满足

代码如下:

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        ListNode newHead = new ListNode();//NewHead为带头结点
        ListNode curN = newHead;
        while(cur1 != null && cur2 != null) {
            if(cur1.val < cur2.val) {
                curN.next = cur1;
                cur1 = cur1.next;
            } else {
                curN.next = cur2;
                cur2 = cur2.next;
            }
            curN = curN.next;
        }
        if(cur1 == null) {
            curN.next = cur2;
        }
        if(cur2 == null) {
            curN.next = cur1;
        }
        return newHead.next;
    }
}

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

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

相关文章

Docker存储目录问题,如何修改Docker默认存储位置?(Docker存储路径、Docker存储空间)etc/docker/daemon.json

文章目录 如何更改docker默认存储路径&#xff1f;版本1&#xff08;没测试&#xff09;版本2&#xff08;可行&#xff09;1. 停止 Docker 服务&#xff1a;2. 创建新的存储目录&#xff1a;3. 修改 Docker 配置文件&#xff1a;4. 移动现有的 Docker 数据&#xff1a;5. 重新…

盲人出行体验攻略:蝙蝠避障,点亮前行的明灯

在繁华喧嚣的都市中&#xff0c;每一步都充满了未知与挑战&#xff0c;而对于盲人朋友们来说&#xff0c;出行更是一场无声的冒险。他们凭借着内心的勇气和坚韧的意志&#xff0c;在黑暗中摸索前行&#xff0c;每一步都承载着对生活的热爱与追求。今天&#xff0c;我们要深入探…

YOLOv8改进 | 注意力机制| 利用并行子网络构建深度较浅但性能卓越的网络【全网独家】

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

jdevelope安装

准备 1.jdk1.8&#xff08;已经安装不做记录&#xff09; 2.下载jdevelope安装包 3.安装包安装jdevelope开发工具 4.创建或导入项目 下载jdevelope安装包 官网下载地址&#xff1a;https://edelivery.oracle.com 安装包安装jdevelope开发工具 cmd管理员权限运行安装脚本…

新质生产力赛道核心解读,机械制造何以“向智向新”

机械行业&#xff0c;国民经济的坚实基石与核心驱动力&#xff0c;为各行各业提供高效生产工具。立于产业链的中游&#xff0c;向上游紧密连接着高精尖的核心零部件制造商与基础原材料供应商&#xff1b;向下游&#xff0c;则广泛渗透并深刻影响着基础设施建设、交通运输&#…

7.11日学习打卡----初学Redis(六)

7.11日学习打卡 目录&#xff1a; 7.11日学习打卡一. redis事务事务的概念与ACID特性Redis事务三大特性Redis事务执行的三个阶段Redis事务基本操作 二. redis集群主从复制主从复制环境搭建主从复制原理剖析 哨兵监控哨兵监控环境搭建哨兵工作原理剖析 故障转移Cluster模式Clust…

如何用脉购CRM健康档案管理系统显著提升用户粘性与忠诚度

如何用脉购CRM健康档案管理系统显著提升用户粘性与忠诚度 脉购CRM健康档案管理系统以其创新的健康管理理念和先进的技术手段&#xff0c;为医疗机构和用户之间建立了一座高效的沟通桥梁。通过该系统&#xff0c;用户的健康档案得到了全面而细致的管理&#xff0c;不仅提高了医…

Node多版本管理器NVM安装使用

背景 有的时候开发前端项目需要用到不同版本的nodejs环境&#xff0c;此时就是需要有一个版本管理工具可以灵活的切换node版本&#xff0c;方便维护不同的开发环境 node版本管理工具 nvm工具介绍 安装node很方便&#xff0c;只需要一条命令可以轻松切换node版本可以多版本n…

MyBatis框架学习笔记(三):MyBatis重要文件详解:配置文件与映射文件

1 mybatis-config.xml-配置文件详解 1.1 说明 &#xff08;1&#xff09;mybatis 的核心配置文件(mybatis-config.xml)&#xff0c;比如配置 jdbc 连接信息&#xff0c;注册 mapper 等等都是在这个文件中进行配置,我们需要对这个配置文件有详细的了解 &#xff08;2&#x…

4.感知机

感知机 ​ 给定输入 x x x&#xff0c;权重 w w w&#xff0c;和偏移 b b b,感知机输出&#xff1a; KaTeX parse error: Unknown column alignment: o at position 16: \begin{array} o̲ \sigma(<w,x>… 或者是二分类&#xff1a;-1或1 Expected node of symbol gro…

全网最适合入门的面向对象编程教程:14 类和对象的 Python 实现-类的静态方法和类方法,你分得清吗?

全网最适合入门的面向对象编程教程&#xff1a;14 类和对象的 Python 实现-类的静态方法和类方法&#xff0c;你分得清吗&#xff1f; 摘要&#xff1a; 本文主要介绍了Python中类和对象中的类方法和静态方法&#xff0c;以及类方法和静态方法的定义、特点、应用场景和使用方…

机器学习(五) -- 监督学习(7) --SVM2

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;7&#xff09; --SVM1 下篇&#xff1a; 前言 tips&#xff1a;标题前有“***”的内容为补充内容&#xff0c;是给好奇心重的宝宝看的&#xff0c;可自行跳过。文章内容被“文…

Spring支持人工智能应用框架-SpringAi

简介 人工智能技术和日益成熟&#xff0c;开发企业级人工智能的应用已成为一个热门的趋势。Spring AI 是一个用于 AI 工程的应用框架&#xff0c;目的是为了简化AI应用的对接、部署、维护和扩展。 SpringAi的灵感来自LangChain和LlamaIndex&#xff0c;但是SpringAi并不是直接…

RK3568平台(显示篇)主屏副屏配置

一.主屏副屏配置 目前在RK3568平台上有两路HDMIOUT输出&#xff0c;分别输出到两个屏幕上&#xff0c;一路配置为主屏&#xff0c;一路配置为副屏。 硬件原理图&#xff1a; &hdmi0_in_vp2 {status "okay"; };&hdmi1_in_vp0 {status "okay"; }…

断电的固态硬盘数据能放多久?

近日收到一个网友的提问&#xff0c;在这里粗浅表达一下见解&#xff1a; “网传固态硬盘断电后数据只能放一年&#xff0c;一年之后就会损坏。但是我有一个固态硬盘已经放了五六年了&#xff08;上次通电还是在2018年左右&#xff0c;我读初中的时候&#xff09;&#xff0c;…

css实现渐进中嵌套渐进的方法

这是我们想要的实现效果&#xff1a; 思路&#xff1a; 1.有一个底色的背景渐变 2.需要几个小的块级元素做绝对定位通过渐变filter模糊来实现 注意&#xff1a;这里的采用的定位方法&#xff0c;所以在内部的元素一律要使用绝对定位&#xff0c;否则会出现层级的问题&…

HCIP.ppp协议(点到点)认证阶段

ppp协议 ppp是点到点的协议 1.兼容性很好 2.可以进行认证和授权 3.可移植性强 三个阶段 1.链路协商阶段 LCP协商------去协商ppp链路会话 2.认证&#xff08;可选&#xff09; 3.NCP协商------网络层协商阶段&#xff08;根据网络层的不同NCP协议就会存在一个对应的NC…

【电子通识】无源元件与有源元件的定义和区别是什么?

当提到构成电路的电子器件时,许多人可能会想到晶体管、电容器、电感器和电阻器等器件。一般情况下,我们使用的电子器件分为两大类,即“有源元件”和“无源元件”。 有源元件是主动影响(如放大、整流、转换等)所供给电能的元件。 无源元件是对所供给的电能执行被动…

代码随想录算法训练营Day62|冗余连接、冗余连接II

冗余连接 108. 冗余连接 (kamacoder.com) 考虑使用并查集&#xff0c;逐次将s、t加入并查集中&#xff0c;当发现并查集中find(u)和find(v)相同时&#xff0c;输出u和v&#xff0c;表示删除的边即可。 #include <iostream> #include <vector> using namespace s…

Android高级——Logger日志系统

Logger日志系统 Logger日志系统是基于内核中的Logger日志驱动程序实现将日志记录保存在内核空间中使用一个环形缓冲区来保存日志&#xff0c;满了之后&#xff0c;新的日志就会覆盖旧的日志 日志类型 main&#xff0c;记录应用程序级别system&#xff0c;记录系统级别radio&…