Java数据结构LinkedList单链表和双链表模拟实现及相关OJ题秒AC总结知识点

news2025/1/11 14:21:45

本篇文章主要讲述LinkedList链表中从初识到深入相关总结,常见OJ题秒AC,望各位大佬喜欢


一、单链表

1.1链表的概念及结构

1.2无头单向非循环链表模拟实现

1.3测试模拟代码

 1.4链表相关面试OJ题

1.4.1 删除链表中等于给定值 val 的所有节点

1.4.2 反转一个单链表

1.4.3 给你单链表的头结点 head ,请你找出并返回链表的中间结点

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

1.4.5 合并俩个有序链表

二、双链表

2.1双向链表模拟实现

2.2LinkedList其他常用方法介绍

2.3ArrayList和LinkedList的区别


1.1链表的概念及结构

由于顺序表ArrayList不适合从任意位置插入或者删除元素,因此引入了LinkedList链表,链表是一种物理存储结构上非连续存储结构,也称链式存储,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1. 单向或者双向

 2. 带头或者不带头

 

 3. 循环或者非循环

 4.无头单向非循环链表或者无头双向链表

 在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

1.2无头单向非循环链表模拟实现

public class MySingleList {

    static class ListNode {
        public int val;
        public ListNode next;

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

    public ListNode head;

    public void createLink() {
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(13);
        ListNode node3 = new ListNode(14);
        ListNode node4 = new ListNode(16);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        head = node1;
    }

    /**
     * @author 徐延焜xyk
     * @Description://遍历链表
     */
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.println(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }


    /**
     * @author 徐延焜xyk
     * @Description://查找是否包含关键字key是否在单链表当中
     */

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

    /**
     * @author 徐延焜xyk
     * @Description://得到单链表的长度 O(N)
     */

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

    /**
     * @author 徐延焜xyk
     * @Description://头插法 O(1)
     */

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

    /**
     * @author 徐延焜xyk
     * @Description://尾插法 O(N)    找尾巴的过程
     */
    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;
    }

    /**
     * @author 徐延焜xyk
     * @Description: //任意位置插入,第一个数据节点为0号下标
     */

    public void addIndex(int index, int data) throws ListIndexOutofException {
        checkIndex(index);
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addFirst(data);
            return;
        }
        ListNode cur = findIndexSubOne(index);
        ListNode node = new ListNode(data);
        node.next = cur.next;
        cur.next = node;
    }

    /**
     * @author 徐延焜xyk
     * @Description:找到 index-1位置的节点的地址
     */
    private ListNode findIndexSubOne(int index) {
        ListNode cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }


    private void checkIndex(int index) throws ListIndexOutofException {
        if (index < 0 || index > size()) {
            throw new ListIndexOutofException("index位置不合法!");
        }
    }

    /**
     * @author 徐延焜xyk
     * @Description://删除第一次出现关键字为key的节点 O(N)
     */

    public void remove(int key) {
        if (head == null) {
            return;
        }
        if (head.val == key) {
            head = head.next;
            return;
        }
        ListNode cur = searchPrev(key);
        if (cur == null) {
            return;
        }
        ListNode del = cur.next;
        cur.next = del.next;
    }

    /**
     * @author 徐延焜xyk
     * @Description:找到关键字key的前一个节点
     */
    private ListNode searchPrev(int key) {
        ListNode cur = head;
        while (cur.next != null) {
            if (cur.next.val == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    /**
     * @author 徐延焜xyk
     * @Description://删除所有值为key的节点
     */
    public void removeAllKey(int key) {
        if (head == null) {
            return;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            } else {
                prev = cur;
                cur = cur.next;
            }
            if (head.val == key) {
                head = head.next;
            }
        }
    }

    /**
     * @author 徐延焜xyk
     * @Description:保证链表当中 所有的节点 都可以被回收
     */
     public void clear() {
        head = null;
    }
}

1.3测试模拟代码

public static void main(String[] args) {
        MySingleList mySingleList = new MySingleList();
        //LinkedList<Integer> stack = new LinkedList<Integer>();
        //Queue<MySingleList.ListNode> queue = new ArrayDeque<>();
        mySingleList.display();
        System.out.println("=======");
        System.out.println(mySingleList.contains(90));
        System.out.println(mySingleList.size());
        System.out.println("====测试插入===");
        mySingleList.addLast(1);
        mySingleList.addLast(2);
        mySingleList.addLast(3);
        mySingleList.addLast(4);
        try {
            mySingleList.addIndex(0,1);
        }catch (ListIndexOutofException e) {
            e.printStackTrace();
        }
        mySingleList.display();
        System.out.println("=============");
        mySingleList.removeAllKey(1);
        mySingleList.display();

    }

 1.4链表相关面试OJ题

1.4.1 删除链表中等于给定值 val 的所有节点

1. 删除链表中等于给定值 val 的所有节点。
203. 移除链表元素 - 力扣(LeetCode)

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

1.4.2 反转一个单链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

206. 反转链表 - 力扣(LeetCode)

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

1.4.3 给你单链表的头结点 head ,请你找出并返回链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

876. 链表的中间结点 - 力扣(LeetCode)

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;
    }
}

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

链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

仅仅用一个指针进行遍历注定是没有办法很优美地实现此问题解答的,所以要用两个指针,这两个指针的位置相差k-1个距离,当快指针走到最后一个节点的时候,慢指针指向的位置就是我们要的倒数第k个节点了。思想就是这么简单了,很多链表类的题目都是活用指针就可以解决的,一个指针不可以的时候就两个指针来完成。

public class Solution {
    public ListNode FindKthToTail(ListNode head, int k) {
        if (k <= 0 || head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (k - 1 != 0) {
            fast = fast.next;
            if (fast == null) {
                return null;
            }
            k--;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

1.4.5 合并俩个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

21. 合并两个有序链表 - 力扣(LeetCode)

class Solution {
   public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
        ListNode newHead = new ListNode(0);
        ListNode tmp = newHead;
        while (head1 != null && head2 != null){
            if (head1.val < head2.val){
                tmp.next = head1;
                head1 = head1.next;
                tmp = tmp.next;
            }else {
                tmp.next = head2;
                head2 = head2.next;
                tmp = tmp.next;
            }
        }
            if (head1 != null){
                tmp.next = head1;
            }
            if (head2 != null){
                tmp.next = head2;
            }
        return newHead.next;
    }
}

上述这些oj题都是最基本的题目,请关注后续播客会有难度题上线!!

二、双链表

2.1双向链表模拟实现

public class MyLinkedList {
    static class ListNode {
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继

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

    public ListNode head;
    public ListNode last;

    //头插法O(1)
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }


    //尾插法O(1)
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            throw new ListIndexOutOfException();
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = findIndex(index);
        ListNode node = new ListNode(data);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }

    public ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //1. 删除的是头节点
                if (cur == head) {
                    head = head.next;
                    //只有一个节点
                    if (head != null) {
                        head.prev = null;
                    }
                } else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;
                    } else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //1. 删除的是头节点
                if (cur == head) {
                    head = head.next;
                    //只有一个节点
                    if (head != null) {
                        head.prev = null;
                    }
                } else {
                    //中间  尾巴
                    cur.prev.next = cur.next;
                    //不是尾巴节点
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;
                    } else {
                        //是尾巴节点
                        last = last.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

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

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

    public void clear() {
        ListNode cur = head;
        while (cur != head) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }
测试代码:
public static void main(String[] args) {
        MyLinkedList linkedList = new MyLinkedList();
        linkedList.addLast(1);
        linkedList.display();
    }

1. LinkedList实现了List接口
2. LinkedList的底层使用了双向链表
3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
5. LinkedList比较适合任意位置插入的场景

2.2LinkedList其他常用方法介绍

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)截取部分 list
LinkedList<Integer> list = new LinkedList<>();
list.add(1); // add(elem): 表示尾插
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
System.out.println(list.size());
System.out.println(list);
// 在起始位置插入0
list.add(0, 0); // add(index, elem): 在index位置插入元素elem
System.out.println(list);
list.remove(); // remove(): 删除第一个元素,内部调用的是removeFirst()
list.removeFirst(); // removeFirst(): 删除第一个元素
list.removeLast(); // removeLast(): 删除最后元素
list.remove(1); // remove(index): 删除index位置的元素
System.out.println(list);

2.3ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

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

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

相关文章

【H2实践】之认识 H2

一、H2 官网 H2 官网 http://www.h2database.com/html/main.html H2 是一款短小精干的 Java 内存数据库,性能强劲。 H2 的优点&#xff1a; 非常快的数据库引擎开源Java 编写支持标准 SQL, JDBC API内嵌和服务器模式&#xff0c;支持集群强大的安全特性可使用 PostgreSQL OD…

如何实现云原生?推荐的几个实用工具

云原生是一种软件开发和部署的方法&#xff0c;它依赖于容器、微服务和自动化运维。它能使应用更高效、可靠和可扩展&#xff0c;并适用于不同的云平台。 如果要更直接、更通俗地解释上述概念的话。 云的本源更准确地说是一种文化&#xff0c;一种潮流&#xff0c;它必然是云…

更新 Python 100道基础入门检测练习题【下篇】(附答案)

前言 大家早好、午好、晚好吖 ❤ ~ 爆肝更新 Python 100道基础入门练习题【篇上】 更多精彩内容、资源皆可点击文章下方名片获取此处跳转 实例021&#xff1a;猴子偷桃 题目&#xff1a; 猴子吃桃问题&#xff1a;猴子第一天摘下若干个桃子&#xff0c;当即吃了一半&#xf…

微服务一 实用篇 - 1. SpringCloud01

《微服务一 实用篇 - 1. SpringCloud01》 提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!! 《微服务一 实用篇 - 1. SpringCloud01》《微服务一 实用篇 - 1. SpringCloud01》0. 微服务课程简介1.认识微服务1.0.学习目标1.1.单体架构1.2.分布式架构1.3.微服务…

1000亿数据、30W级qps如何架构?来一个天花板案例

1000亿级存储、30W级qps系统如何架构&#xff1f;来一个天花板案例 说在前面 在尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常遇到一个 非常、非常高频的一个架构面试题&#xff0c;类似如下&#xff1a; 千万级数据&#xff0c;如何做系统架构&#xff1f;亿…

外网跨网远程控制内网计算机3种方案

远程控制&#xff0c;通俗来讲就是在自己个人电脑直接远程访问另台主机电脑桌面操作。 如何远程控制电脑&#xff1f;远程控制别人计算机的方案通常有两种&#xff0c;一种是开启电脑系统自带的远程桌面功能&#xff0c;如果涉及跨网内、外网互通可以同时用快解析内网映射外网&…

windows 下 安裝mysql 5.7.41 (64位) 超简单方式

文章目录1. 安装包下载2.安装步骤3. 服务卸载方式4. 配上 my.ini 常用配置1. 安装包下载 注意&#xff0c;截至2023年2月23日&#xff0c;MySQL所有版本不提供ARM芯片架构的Windows版本(8.0.12开始支持Red Hat系统的ARM版本)&#xff0c;所以ARM架构的Windows无法安装MySQL&am…

如何批量重命名,把文件(夹)名的内容位置调整(前后移动)

首先&#xff0c;需要用到的这个工具&#xff1a; 百度 密码&#xff1a;qwu2 蓝奏云 密码&#xff1a;2r1z 情况是这样&#xff0c;把“中文[数字]”的名称&#xff0c;改为"中文 - 数字" 打开工具&#xff0c;切换到 文件批量复制 模块&#xff0c;快捷键Ctrl5 …

【同步工具类:CountDownLatch】

同步工具类:CountDownLatch介绍源码分析继承图核心方法分析await()countDown()业务场景代码实现测试结果总结介绍 Jdk原文翻译 CountDownLatch 一种同步辅助工具&#xff0c;允许一个或多个线程等待&#xff0c;直到在其他线程中执行的一组操作完成。 CountDownLatch用给定的计…

css3的重点内容

css3的重点内容 浮动 父级边框塌陷问题 浮动的清除 clear:left; //清除左侧浮动 clear:right; //清除右侧浮动 clear:both; //清除两侧浮动解决方案 增加父级元素的高度增加一个空的div&#xff0c;之后清除浮动通过overflow来进行相关元素的修剪给父类添加相应的伪类元素…

使用 docker 部署 MySQL 会导致数据丢失吗

2023年2月28日&#xff0c;今天下午电话面试 java 岗位&#xff0c;经过一些提问后&#xff0c;面试官问了一个问题&#xff0c;“那么你最近在关注什么方面的技术点呢&#xff1f;”&#xff0c;可能是我之前的回答不太理想&#xff0c;且说辞都是“不好意思&#xff0c;可能最…

JS逆向-百度翻译sign

前言 本文是该专栏的第36篇,后面会持续分享python爬虫干货知识,记得关注。 有粉丝留言,近期需要做个翻译功能,考虑到百度翻译语言语种比较全面,但是它的参数被逆向加密了,对于这种情况需要怎么处理呢?所以本文以它为例。 废话不多说,跟着笔者直接往下看正文详细内容。…

狂飙狂飙,一年都沉浸在狂飙中,教你们用python来实现在图片中添加日历

前言 我是小废物&#xff0c;才开始看狂飙&#xff0c;这热点还能蹭上吗 该说不说&#xff0c;是真的很喜欢里面的大嫂啊&#xff0c;现在壁纸都一直是她&#xff0c;大美女谁不喜欢啊 昨天还是前天刷到了她出席活动&#xff0c;太帅了吧 不多说&#xff0c;先看大美女 &am…

【面试1v1实景模拟】Redis面试官会怎么提问?本文助你面试入坑,赶快收藏吧~

笑小枫专属目录老面&#x1f474;&#xff1a;小伙子&#xff0c;咱来聊聊Redis&#xff0c;你知道什么是Redis吗?老面&#x1f474;&#xff1a;除了key-value类型的数据&#xff0c;你还知道Redis的哪几种数据结构?老面&#x1f474;&#xff1a;你说说在什么场景下使用了R…

taobao.opensecurity.uid.get( open security uid 获取接口 )

&#xffe5;免费不需用户授权 根据明文 taobao user id 换取 app的 open_uid 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 点击获取key和secret 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey, …

2023年湖北助理工程师(初级职称)怎么评?需要什么资料?启程别

2023年湖北助理工程师&#xff08;初级职称&#xff09;怎么评&#xff1f;需要什么资料&#xff1f;启程别 助理工程师主要是指初级工程技术人员的职务名称&#xff0c;他是通过相关考试和相关部门评审通过之后所获得的相应名称&#xff0c;想要了解职称更多相关资料可以咨询启…

使用字典快速输入编号(匹配编码表)

实例需求&#xff1a;在A列输入样品名称之后&#xff0c;在B列字段填写编号&#xff0c;其规则如下&#xff1a; 如果A列已经存在相同样品名称&#xff0c;则将编号递增&#xff0c;例如&#xff1a;A15输入沥青&#xff0c;最后一行相同样品名称在第12行&#xff0c;B15填写递…

第十届蓝桥杯省赛——4质数(质数判断,数学函数:开方函数)

题目&#xff1a;试题 D: 质数本题总分&#xff1a;10 分【问题描述】我们知道第一个质数是 2、第二个质数是 3、第三个质数是 5……请你计算第 2019 个质数是多少&#xff1f;【答案提交】这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数&…

环形缓冲区(c语言)

1、概念介绍 在我们需要处理大量数据的时候&#xff0c;不能存储所有的数据&#xff0c;只能先处理先来的&#xff0c;然后将这个数据释放&#xff0c;再去处理下一个数据。 如果在一个线性的缓冲区中&#xff0c;那些已经被处理的数据的内存就会被浪费掉。因为后面的数据只能…

使用码匠连接一切|二

目录 Elasticsearch Oracle ClickHouse DynamoDB CouchDB 关于码匠 作为一款面向开发者的低代码平台&#xff0c;码匠提供了丰富的数据连接能力&#xff0c;能帮助用户快速、轻松地连接和集成多种数据源&#xff0c;包括关系型数据库、非关系型数据库、API 等。平台提供了…