【Java数据结构】 链表

news2024/11/26 17:21:00
【本节目标】
1. ArrayList 的缺陷
2. 链表
3. 链表相关 oj题目

一. ArrayList的缺陷

        上节课已经熟悉了ArrayList 的使用,并且进行了简单模拟实现。通过源码知道, ArrayList 底层使用数组来存储元素:
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // ...
// 默认容量是10
    private static final int DEFAULT_CAPACITY = 10;
    //...
// 数组:用来存储元素
    transient Object[] elementData; // non-private to simplify nested class access
    // 有效元素个数
    private int size;
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                    initialCapacity);
        }
    }
// ...
}
        由于其底层是一段连续空间,当 ArrayList 任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后 搬移,时间复杂度为 O(n) ,效率比较低,因此 ArrayList 不适合做任意位置插入和删除比较多的场景 。因此: java集合中又引入了LinkedList ,即链表结构。

二. 链表

1 链表的概念及结构

        链表是一种物理存储结构上非连续 存储结构,数据元素的 逻辑顺序 是通过链表中的 引用链接 次序实现的 。

实际中链表的结构非常多样,以下情况组合起来就有 8 种链表结构:
单向------双向
循环------ 非循环
带头------ 不带头

(1)  单向或者双向
(2)  带头或者不带头
(3) 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种 :
  • 无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多

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

2 链表的实现

1、无头单向非循环链表实现

链表的基本表示:

static class ListNode{
        public int val;
        public ListNode next;
        public ListNode(int data){
            this.val=data;
        }
    }
    public ListNode head;//链表的头

对应的方法:

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

实现对应的方法: 

(1)display

public void display() {
        ListNode cur=head;//不能改变头的引用
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
    }

(2)size

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

(3)contains

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

 (4)addFirst/头插

  public void addFirst(int data) {
        ListNode listNode=new ListNode(data);//该节点是新的头节点
        listNode.next=head;
        head=listNode;
    }

 (5)addFirst/尾插

public void addLast(int data) {
        ListNode listNode=new ListNode(data);
        if(head==null){
            head=listNode;
            return;
        }
        ListNode cur=head;
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next=listNode;
    }

 (6)addIndex/任意位置插

public void addIndex(int index, int data) {
        int len=size();
        if (index<0||index>len){
            new IndexOutOfBoundary("index输入有误");
            return;
        }
        if(index==0){
            addFirst(data);
            return;
        }
        if(index==len){
            addLast(data);
            return;
        }
        int curLen=0;
        ListNode cur=head;
        ListNode listNode=new ListNode(data);
        while(curLen<index-1){
            curLen++;
            cur=cur.next;
        }
        listNode.next=cur.next;
        cur.next=listNode;
    }

   (7)remove

    public void remove(int key) {
        if(head==null){//链表为空
            return;
        }
        if(head.val==key){//删除的位置在头节点
            head=head.next;
            return;
        }
        ListNode pre=findPreNodeOfKey(key);
        if(pre!=null){
            ListNode del=pre.next;//找到需要删除的节点
            pre.next=del.next;
        }

    }
    private  ListNode findPreNodeOfKey(int key){
        ListNode cur=head;
        while(cur.next!=null){//最后一个节点也已经判断过了
            if (cur.next.val==key){
                return cur;
            }
            cur=cur.next;
        }
        return null;
    }

  (8)removeAllKey

 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=prev.next;
                 cur= cur.next;
             }
        }
        //把前面的除头节点之外的都删完之后删除头节点
        if (head.val==key){
            head=head.next;
        }
    }

  (9)clear

   public void clear() {
        ListNode cur=head;
        while (cur!=null){
            ListNode curN=cur.next;
            cur.next=null;//基本数据类型的val不需要回收
            cur=curN;
        }
    }

三.链表相关题目

1. 删除链表中等于给定值 val 的所有节点。   
/**
 * 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 removeElements(ListNode head,int data){
        if(head==null){
            return head;
        }
        ListNode prev=head;
        ListNode cur=head.next;
        while(cur!=null){
            if(cur.val==data){
                prev.next=cur.next;
                cur=cur.next;
            }else{
                prev=prev.next;
                cur=cur.next;
            }
        }
        if(head.val==data){
            head=head.next;
        }
        return head;
    }
 }

2. 反转一个单链表。

可以采取不停的进行头插,将后面的节点不停的插到前面 

/**
 * 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) {
        if(head==null){
            return head;
        }
        ListNode cur=head.next;
        head.next=null;
        while(cur!=null){ 
            ListNode curN=cur.next;
            cur.next=head;
            head=cur;
            cur=curN;
        }
        return head;

    }
}

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
/**
 * 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 middleNode(ListNode head) {
        if(head==null){
            return head;
        }
        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        return slow;
    }
}

4. 输入一个链表,输出该链表中倒数第 k 个结点。
import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        int count=0;
        ListNode f=pHead;
        while(f!=null){
            count++;
            f=f.next;
        }
        if(k>count||k<=0){
            return null;
        }
        if(pHead==null){
            return pHead;
        }
        // write code here
        ListNode prev=pHead;
        ListNode cur=pHead;
        while((k-1)!=0){
            cur=cur.next;
            k--;
        }
        while(cur!=null&&cur.next!=null){
            prev=prev.next;
            cur=cur.next;
        }
        return prev;
    }
}

经过重新调整,除了计算出有多少元素,防止多删除之外,可以在快指针走的时候就进行判断,走太多就会超过限制,那么就会走到空指针出,也可以判断

while((k-1)!=0){
            cur=cur.next;
            k--;
            if(cur==null){
                return null;
            }
        }

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
 * 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 mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode cur1=list1;
        ListNode cur2=list2;
        ListNode newHead=new ListNode();
        ListNode tmp=newHead;
        if(cur1==null&&cur2==null){
            return null;
        }
        while(cur1!=null&&cur2!=null){
            if(cur1.val<=cur2.val){
                tmp.next=cur1;
                cur1=cur1.next;
                tmp=tmp.next;
            }else{
                tmp.next=cur2;
                cur2=cur2.next;
                tmp=tmp.next;
            }
        }
        if(cur1!=null){
            tmp.next=cur1;
            cur1=cur1.next;
            tmp=tmp.next;
        }
        if(cur2!=null){
            tmp.next=cur2;
            cur2=cur2.next;
            tmp=tmp.next;
        }
        return newHead.next;
    }
}

6. 编写代码,以给定值 x 为基准将链表分割成两部分,所有小于 x 的结点排在大于或等于 x 的结点之前 。
  • 利用两个链表,一个存放小于x的值,一个存放大于x的值
  • 要注意的是最后的判断条件,可能不存在小于x的,则直接返回第二个链表;且如果第二个链表不为空,链表结尾要置空,防止越界。
import java.util.*;

/*
public class ListNode {
    int val;
    ListNode next = null;
    ListNode() { 
    }
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        // write code here
        ListNode list1start=null;
        ListNode list1end=null;
        ListNode list2start=null;
        ListNode list2end=null;
        while(pHead!=null){
            if(pHead.val<x){
                if(list1start==null){
                    list1start=list1end=pHead;
                }else{
                list1end.next=pHead;
                list1end=list1end.next;
                }
            }else{
                 if(list2start==null){
                    list2start=list2end=pHead;
                }else{
                list2end.next=pHead;
                list2end=list2end.next;   
                }  
        }
         pHead=pHead.next;
        }
        if(list1start==null){
            return list2start;
        }
        list1end.next=list2start;
        if(list2start!=null){
            list2end.next=null;
        }
        return list1start;
    }
}

7. 链表的回文结构。

使用快慢指针,找到中间节点,将中间节点之后的结点进行反转。然后分别向中间比较。
import java.util.*;

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class PalindromeList {
   public boolean chkPalindrome(ListNode head) {
        // write code here
        if(head == null) return true;
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //slow 指向的位置 就是中间节点
        //2.进行翻转
        ListNode cur = slow.next;
        while (cur != null) {
            ListNode curN = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curN;
        }
        //3.判断回文
        while (head != slow) {
            if(head.val != slow.val) {
                return false;
            }
            if(head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }
}

8. 输入两个链表,找出它们的第一个公共结点。

分别去求两个链表的长度,让长的链表去走两个链表的差值

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pl=headA;
        ListNode ps=headB;
        int len1=0;
        int len2=0;
        while(pl!=null){
            pl=pl.next;
            len1++;
        }
         while(ps!=null){
            ps=ps.next;
            len2++;
        }
        pl=headA;
        ps=headB;
        int k=len1-len2;
        if(k<0){
            pl=headB;
            ps=headA;
            k=0-k;
        } 
        while(k!=0){
            pl=pl.next;
            k--;
        }
        while(pl!=ps){
            pl=pl.next;
            ps=ps.next;
        }
        //若两个链表不相交
        if(pl==null){
            return null;
        }
        return pl;

        
    }
}

9. 给定一个链表,判断链表中是否有环。
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow){
                return true;
            }
        }
        return false;
        
    }
}

【思路】
        快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
【扩展问题】
  • 为什么快指针每次走两步,慢指针走一步可以?
        假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
  • 快指针一次走3步,走4步,...n步行吗?
10. 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回  NULL 

起点到入口点的距离和相遇点到入口点的距离相等

此时slow从开头处开始走,fast从相遇点开始走,以相同的速度运动,相遇时则为相交点

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow=head;
        ListNode fast=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(slow==fast){
                break;
            }
        }
        if(fast==null||fast.next==null){
            return null;
        }
        slow=head;
        while(slow!=fast){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;    
    }
}

结论
让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针 都是每次均走一步,最终肯定会在入口点的位置相遇
证明

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

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

相关文章

探索Spring Boot:实现“衣依”服装电商平台

1系统概述 1.1 研究背景 如今互联网高速发展&#xff0c;网络遍布全球&#xff0c;通过互联网发布的消息能快而方便的传播到世界每个角落&#xff0c;并且互联网上能传播的信息也很广&#xff0c;比如文字、图片、声音、视频等。从而&#xff0c;这种种好处使得互联网成了信息传…

深入理解 CSS 浮动(Float):详尽指南

“批判他人总是想的太简单 剖析自己总是想的太困难” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;目录1. 什么是 CSS 浮动&#xff1f;2. CSS 浮动的历史背景3. 基本用法float 属性值浮动元素的行为 4. 浮动对文档流的影响5. 清除浮动clear 属性清除浮动的技巧1. 使用…

从零开始讲PCIe(1)——PCI概述

一、前言 在之前的内容中&#xff0c;我们已经知道了PCIe是一种外设总线协议&#xff0c;其前身是PCI和PCI-X&#xff0c;虽然PCIe在硬件上有了很大的进步&#xff0c;但其使用的软件与PCI系统几乎保持不变。这种向后兼容性设计&#xff0c;目的是使从旧设计到新设计的迁移更加…

【QGis】生成规则网格/渔网(Fishnet)

【QGis】生成规则网格/渔网&#xff08;Fishnet&#xff09; QGis操作案例参考 QGIS下载安装及GIS4WRF插件导入可参见另一博客-【QGIS】软件下载安装及GIS4WRF插件使用。 QGis操作案例 1、加载中国省级边界&#xff0c;QGis界面如下&#xff1a; 查看坐标系&#xff1a; 如…

详解JVM类加载机制

❝ 前几篇文章我们分别详细描述了 JVM整体的内存结构 JVM对象内存是如何布局的以及内存分配的详细过程 但是对JVM内存结构各个模块没有深入的分析&#xff0c;为了熟悉JVM底层结构&#xff0c;接下来将把JVM运行时数据区的各个模块逐一分析&#xff0c;体系化的理解JVM的各个模…

【S32K3 RTD LLD篇5】K344 ADC SW+HW trigger

【S32K3 RTD LLD篇5】K344 ADC SWHW trigger 一&#xff0c;文档简介二&#xff0c;ADC SW HW 触发2.1 软硬件平台2.2 SWADC 软件触发2.3 SWBCTUADC 软件BCTU触发2.4 PITTRIGMUXADC 硬件PIT TRIGUMX触发2.5 EMIOSBCTUHWADC硬件EMIOS BCTU触发2.6 EMIOSBCTUHW LISTADC硬件EMIOS …

【计算机毕业设计】springboot游戏分享网站

摘 要 网络的广泛应用给生活带来了十分的便利。所以把游戏分享管理与现在网络相结合&#xff0c;利用java技术建设游戏分享网站&#xff0c;实现游戏分享的信息化。则对于进一步提高游戏分享管理发展&#xff0c;丰富游戏分享管理经验能起到不少的促进作用。 游戏分享网站能够…

Oracle架构之物理存储中各种文件详解

文章目录 1 物理存储1.1 简介1.2 数据文件&#xff08;data files&#xff09;1.2.1 定义1.2.2 分类1.2.2.1 系统数据文件1.2.2.2 撤销数据文件1.2.2.3 用户数据文件1.2.2.4 临时数据文件 1.3 控制文件&#xff08;Control files&#xff09;1.3.1 定义1.3.2 查看控制文件1.3.3…

【重学 MySQL】五十五、浮点和定点数据类型

【重学 MySQL】五十五、浮点和定点数据类型 种类选择数据精度说明浮点数据精度定点数据精度总结 精度误差说明浮点数据精度误差定点数据精度误差总结 示例注意事项开发中经验 在MySQL中&#xff0c;浮点和定点数据类型用于存储小数和实数。 种类 MySQL提供了两种主要的浮点数…

SuiteCRM系统 responseEntryPoint SQL注入复现(CVE-2024-36412)

0x01 产品描述&#xff1a; SuiteCRM是一款开源的CRM&#xff08;客户关系管理&#xff09;系统&#xff0c;它致力于为世界各地的用户提供高效、灵活和可定制的CRM解决方案。它为企业和组织提供了一套完整的客户关系管理解决方案&#xff0c;无论是中小型企业还是大型企业&…

Python和R及Julia妊娠相关疾病生物剖析算法

&#x1f3af;要点 算法使用了矢量投影、现代优化线性代数、空间分区技术和大数据编程利用相应向量空间中标量积和欧几里得距离的紧密关系来计算使用妊娠相关疾病&#xff08;先兆子痫&#xff09;、健康妊娠和癌症测试算法模型使用相关性投影利用相关性和欧几里得距离之间的关…

linux第二课:常用命令

Kali Linux&#xff0c;黑客必备神器。跟着我&#xff0c;带你从入门到入狱&#xff01; 第二课&#xff0c;常用命令。 1.whoami 翻译为中文&#xff1a;我是谁 用途&#xff1a;输出现在自己的用户身份(用户名) 参数&#xff1a;无参数 好吧&#xff0c;这透明度一调都不…

基于SpringBoot+Vue的汽车保险理赔系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

mysql UDF提权(实战案例)

作者&#xff1a;程序那点事儿 日期&#xff1a;2024/09/29 16:10 什么是UDF? 全称 User Define Function &#xff08;用户自定义函数&#xff09;UDF提权&#xff0c;就是通过自定义函数&#xff0c;实现执行系统的命令。 dll&#xff08;windows&#xff0c;dll文件是c语…

10.1 10.3 图DFS 中等 207 Course Schedule 210 Course Schedule Ⅱ

207 Course Schedule class Solution { public:bool hasCycle(int course ,unordered_map<int,vector<int>>& graph,vector<int>& visitStatus){//正在访问的结点再次被访问&#xff0c;存在环if(visitStatus[course] 1)return true;//该结点已经被…

【CViT】Deepfake Video Detection Using Convolutional Vision Transformer

文章目录 Deepfake Video Detection Using Convolutional Vision Transformerkey points**卷积视觉变压器**FLViT实验总结Deepfake Video Detection Using Convolutional Vision Transformer 会议/期刊:2021 作者: key points 提出了一种用于检测深度伪造的卷积视觉变压器…

Linux学习之路 -- 线程 -- 死锁及线程安全相关问题

在上文中&#xff0c;我们已经介绍了线程池的编写&#xff0c;下面补充一下线程的相关知识。 目录 1、线程安全与可重入 <1>概念 <2>区别联系 <3>常见线程不安全的情况 <4>常见的不可重入情况 2、死锁问题 <1>死锁概念 <2>死锁四…

sql-labs靶场第二关测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、寻找注入点 2、注入数据库 ①Order by判断列数 ②判断回显地方 ③爆库&#xff0c;查看数据库名称 ④爆表&#xff0c;查看security库的所有表 ⑤爆列&#xff0c;查看users表的所有…

Redis-哨兵

概念 Redis Sentinel 相关名词解释 注意: 哨兵机制不负责存储数据,只是对其它的redis-server进程起到监控的作用哨兵节点,也会搞一个集合,防止一个挂了 ⼈⼯恢复主节点故障 用户监控: 实际开发中,对于服务器后端开发,监控程序,是很重要的 服务器长期运行,总会有一些意外,…

16.数据结构与算法-串,数组与广义表(串,BF算法,KMP算法)

串&#xff08;String&#xff09; 串的定义 关于串的术语 串的案例引入 串的类型定义 串的顺序存储结构 串的链式存储结构-块链结构 串的模式匹配算法&#xff08;BF算法与KMP算法&#xff09; BF算法 BF算法时间复杂度 KMP算法