给自己复盘的随想录笔记-链表

news2024/12/27 3:20:28

链表

定义

数字域和指针域

种类

单链表,双链表,循环链表

链表的存储方式

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理

删除链表

增加链表

与数组的对比

再把链表的特性和数组的特性进行一个对比,如图所示:

链表-链表与数据性能对比

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

移除链表元素

移除操作,就是让节点next指针直接指向下下一个节点就可以了,

那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?

这里就涉及如下链表操作的两种方式:

  • 直接使用原来的链表来进行删除操作。
  • 设置一个虚拟头结点在进行删除操作。

这里我选择第二种方式,更好理解和记忆;

给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。

这样是不是就可以使用和移除链表其他节点的方式统一了呢?

来看一下,如何移除元素1 呢,还是熟悉的方式,然后从内存中删除元素1。

最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点

/**
 * 时间复杂度 O(n)
 * 空间复杂度 O(1)
 * @param head
 * @param val
 * @return
 */
public ListNode removeElements(ListNode head, int val) {
    // 设置一个虚拟的头结点
    ListNode dummy = new ListNode();
    dummy.next = head;

    ListNode cur = dummy;
    while (cur.next != null) {
        if (cur.next.val == val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;        
        }
    }
    return dummy.next;
}

设计链表

设计的为单链表时 

我分析这个代码的思路,因为是链表,内存空间不是连续的,所以对那个Index的理解不明确;

而且又是单链表,只有一个next指针,所以分析的时候要注意什么找到什么链表

707. 设计链表 - 力扣(LeetCode)

思路
这是链表最基础的操作,牢记于心!
首先初始化:由于给的是链表:MyLinkedList ,需要初始化size(长度),listnode(头节点)。而题目没有给出listnode的定义,需要自己定义一个ListNode类,记得定义构造函数,不然在MyLinkedList初始化的时候无法直接赋值。
1.核心是addAtIndex(int index, int val);需要判断index,如果大于size,返回;

如果<0,令其=0;

执行插入,由于是index的前面插入,所以要找到index的前驱节点循环次数=index;插入时先将新节点与pre.next连接,再将pre.next=新节点。这样是为了防止先执行 pre.next=新节点,会断链找不到后面的链表。
2.addAtHead,addAtTail直接调用addAtIndex即可。
3.deleteAtIndex(int index),首先找到index的前驱节点,然后将pre.next=pre.next.next即可。
4.get(int index):首先进行有效值判断,然后for循环即可。这里不需要找到前驱节点因此循环次数是Index+1.

解题过程
刚开始用的是while循环,每次pre指针往后移动的时候,Index--;这样肯定没有for循环看着舒适。而且没有搞明白什么时候需要找到先驱节点(插入,删除),以及循环多少次可以找到先驱节点(当pre=head的时候,循环次数=index)。以及不会写列表的构造函数(size,listnode),不过我现在都会啦~!

//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
class MyLinkedList {
    //size存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;

    //初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }

    //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode currentNode = head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = head.next;
        head.next = newNode;
        size++;

        // 在链表最前面插入一个节点,等价于在第0个元素前添加
        // addAtIndex(0, val);
    }

    
    public void addAtTail(int val) {
        ListNode newNode = new ListNode(val);
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }

        cur.next = newNode;
        size++;

        // 在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
        // addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        //因为有虚拟头节点,所以不用对Index=0的情况进行特殊处理
        ListNode pred = head;
        for (int i = 0; i < index ; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

当设计的为双链表时

class MyLinkedList {  

    //记录链表中元素的数量
    int size;
    //记录链表的虚拟头结点和尾结点
    ListNode head,tail;
    
    public MyLinkedList() {
        //初始化操作
        this.size = 0;
        this.head = new ListNode(0);
        this.tail = new ListNode(0);
        //这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
        head.next=tail;
        tail.prev=head;
    }
    
    public int get(int index) {
        //判断index是否有效
        if(index>=size){
            return -1;
        }
        ListNode cur = this.head;
        //判断是哪一边遍历时间更短
        if(index >= size / 2){
            //tail开始
            cur = tail;
            for(int i=0; i< size-index; i++){
                cur = cur.prev;
            }
        }else{
            for(int i=0; i<= index; i++){
                cur = cur.next; 
            }
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        //等价于在第0个元素前添加
        addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
        //等价于在最后一个元素(null)前添加
        addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
        //index大于链表长度
        if(index>size){
            return;
        }

        size++;
        //找到前驱
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        //新建结点
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next;
        pre.next.prev = newNode;
        newNode.prev = pre;
        pre.next = newNode;
        
    }
    
    public void deleteAtIndex(int index) {
        //判断索引是否有效
        if(index>=size){
            return;
        }
        //删除操作
        size--;
        ListNode pre = this.head;
        for(int i=0; i<index; i++){
            pre = pre.next;
        }
        pre.next.next.prev = pre;
        pre.next = pre.next.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

反转链表

这个题目有点难理解

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

方法一:双指针法

class Solution {
	public ListNode reverseList(ListNode head) {
		//申请节点,pre和 cur,pre指向null
		ListNode pre = null;
		ListNode cur = head;
		ListNode tmp = null;
		while(cur!=null) {
			//记录当前节点的下一个节点
			tmp = cur.next;
			//然后将当前节点指向pre
			cur.next = pre;
			//pre和cur节点都前进一位
			pre = cur;
			cur = tmp;
		}
		return pre;
	}
}

方法二:递归的方法

这个思路真的没有看懂。。

class Solution {
	public ListNode reverseList(ListNode head) {
		//递归终止条件是当前为空,或者下一个节点为空
		if(head==null || head.next==null) {
			return head;
		}
		//这里的cur就是最后一个节点
		ListNode cur = reverseList(head.next);
		//这里请配合动画演示理解
		//如果链表是 1->2->3->4->5,那么此时的cur就是5
		//而head是4,head的下一个是5,下下一个是空
		//所以head.next.next 就是5->4
		head.next.next = head;
		//防止链表循环,需要将head.next设置为空
		head.next = null;
		//每层递归函数都返回cur,也就是最后一个节点
		return cur;
	}
}

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

 递归过程与迭代类似,我们可以抽象出一个函数每次执行反转节点的动作,然后递归处理下一个节点,直到节点为空链表处理结束。

class Solution {
    private ListNode reversedHead; // 反转后链表的头节点

    public ListNode reverseList(ListNode head) {
        reverse(null, head); // 从头节点开始反转,头节点的上一个节点为空
        return reversedHead;
    }
	// 反转节点curr,last为curr的上一个节点
    private void reverse(ListNode last, ListNode curr){
        if(curr == null){
            // curr为空,到达链表尾部,last就是原链表最后一个节点,即反转后的链表的头节点
            reversedHead = last;
            return;
        }
        reverse(curr, curr.next);  // 递归反转下一个节点
        curr.next = last;  // 反转当前节点
    }
}

两两交换链表中的节点

递归

又是递归,我真的看不懂递归,感觉脑子炸了,明天再来好好整理复盘递归这两个代码

class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }
} 

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

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

相关文章

FFmpeg下载与集成:.NET开发者入门指南

文章目录 前言一、FFmpeg下载1.访问FFmpeg官网2.选择合适的版本3.下载并安装 二、.NET项目中集成FFmpeg1.引入FFmpeg库2.编写代码调用FFmpeg3.处理FFmpeg输出和错误 三、进阶使用总结 前言 在音视频处理领域&#xff0c;FFmpeg无疑是一个强大的工具库&#xff0c;它支持几乎所有…

3min 告诉你!如何用波特五力分析模型解读行业格局?

在瞬息万变&#xff0c;竞争激烈的市场环境中&#xff0c;企业不仅要关注自身的发展&#xff0c;还需要时刻了解竞争对手的动态。通过波特五力模型来分析行业竞争状况&#xff0c;企业可以提前制定战略&#xff0c;获得竞争优势。 什么是波特五力模型&#xff1f; 波特五力模…

edge跟谷歌浏览器配置浏览器可跨域

在D盘下创建 vscode\test 文件夹 D:\xxxx\vscode\test 直接复制edge或者谷歌的一个快捷方式 名字就叫可跨域 可以随便取 右键选中目标浏览器点击属性 配置edge的目标路径 "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --disable-web-security …

【操作系统】实验:内存管理

目录 一、实验目的 二、实验要求 三、实验步骤 四、核心代码 五、记录与处理 六、思考 七、完整报告和成果文件提取链接 一、实验目的 1、加深对固定和动态分区分配内存管理方式的理解 2、理解动态分区分配算法&#xff1a;首次适应算法&#xff0c;循环首次适应算法&a…

文心一言 VS 讯飞星火 VS chatgpt (334)-- 算法导论23.1 3题

三、证明&#xff1a;如果图G的一条边(u&#xff0c;v)包含在图G的某棵最小生成树中&#xff0c;则该条边是横跨图G的某个切割的一条轻量级边。 文心一言&#xff1a; 为了证明这个命题&#xff0c;我们可以按照以下步骤进行&#xff1a; 第一步&#xff0c;假设图 G G G的一…

程序员对未来思考:面向AI编程;继2B,2C后,2AI开发工程师能否成为未来?

目录 前言当前AI状况幻觉问题上下文长度限制人在回路、提示工程师 AI对未来的影响AI改变了交互形式穿孔纸带命令行时代图形用户界面对话式交互 AI无法直接影响物理世界AI 与开发者AI2B\2C\2G\2AI2AI、AI的幻想 前言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&a…

【三指针法】颜色分类

目录 1.前言2.题目简介3.求解思路4.示例代码 1.前言 2.题目简介 题目链接&#xff1a;LINK 3.求解思路 求解思路&#xff1a;三指针法 4.示例代码 class Solution { public:void sortColors(vector<int>& nums) {int i 0;int left -1;int right nums.size…

JAVA安全之Velocity模板注入刨析

文章前言 关于Velocity模板注入注入之前一直缺乏一个系统性的学习和整理&#xff0c;搜索网上大多数类似的内容都是一些关于漏洞利用的复现&#xff0c;而且大多都仅限于Velocity.evaluate的执行&#xff0c;对于载荷的构造以及执行过程并没有详细的流程分析&#xff0c;于是乎…

大模型学习必备指南:深入解析技术原理与应用,从入门到精通一应俱全

目录 1. 深度神经网络 2. 激活函数 3. 损失函数 4. 优化算法 5. 正则化 6. 模型结构 7. 预训练与微调 8. 模型压缩与加速 9. 解释性与可解释性 10. 隐私与安全 11. 总结 推荐阅读 有人说&#xff0c;最近我们都患上了大模型焦虑症。 随着计算能力的提升和数据量的…

视频MOV如何转换成MP4?介绍这些转换方案

视频MOV如何转换成MP4&#xff1f;不同的应用场景往往需要使用不同格式的视频文件,其中MOV 和 MP4 是两种常见的视频格式。MOV 格式由苹果公司开发&#xff0c;主要用于 QuickTime 平台&#xff1b;而 MP4 格式则是一种更为通用的标准&#xff0c;广泛应用于互联网、移动设备以…

Java 输入与输出之 NIO【非阻塞式IO】【NIO核心原理】探索之【一】

Java标准的输入/输出&#xff08;Input/Output&#xff0c;简称I/O&#xff09;是Java程序与外部世界进行交互的重要机制&#xff0c;它允许程序读取和写入数据到各种类型的源&#xff0c;如文件、网络套接字、管道、内存缓冲区等。Java I/O API主要位于java.io包中&#xff0c…

SqlHelper 使用EF-Core框架 连接池处理并发

定义数据库 数据库名称&#xff1a;T_dicomPatientMsg 注意5大约束条件&#xff1a; 1.主键约束&#xff1a;primary key IDKEY设置为主键&#xff0c;主键设置自增长 2.唯一性约束&#xff1a;unique 3.默认约束&#xff1a;default 所有值都要设置默认值&#xff0c…

Unraid 手动安装docker

目录 常用镜像链接一.安装示例1[firefox浏览器]:1.离线下载docker镜像2.将xxx.tar镜像数据加载到 Docker 中3.手动添加docker 二.安装示例2[等我有东西需要安装再回来补教程吧]:三.获取UDI和GID 常用镜像链接 特别版 emby 文件管理器 filebrowser内外穿透 zerotierNAS媒体库管…

Python和Pycharm安装

有需要的私聊我吧&#xff01;&#xff01;&#xff01;

伺服电机最佳速度范围是多少?

伺服电机的最佳速度范围取决于多种因素&#xff0c;包括电机的规格、负载类型、控制要求和应用环境等。一般来说&#xff0c;伺服电机的最佳速度范围是其额定转速的70%到100%之间。这一范围内&#xff0c;电机能够提供最佳的效率、精度和响应速度。 关键因素影响伺服电机速度范…

从B端工程师到AI绘画工程师:我的转行之路与实战指南

一、背景&#xff1a;B端工程师的迷茫与探索 大家好&#xff0c;我是一名有着五年B端服务经验的软件工程师。在长期的B端工作中&#xff0c;我逐渐感到自己的技术栈和视野受限&#xff0c;对未来的职业发展产生了迷茫。在一次偶然的机会中&#xff0c;我接触到了AI绘画这一领域…

nacos 安装

1. 环境准备 使用此快速开始方法进行Nacos安装及部署&#xff0c;需要安装Docker和Docker Compose。 如何下载不下来&#xff0c;可换镜像加速地址 vi /etc/docker/daemon.json {"registry-mirrors": ["https://docker.registry.cyou"] }可用镜像加速地…

合宙Air700EAQ硬件设计手册——应用接口2

Air700EAQ是一款基于移芯EC716E平台设计的LTE Cat 1无线通信模组。 支持亚洲FDD-LTE的4G远距离无线传 输技术。 以极小封装&#xff0c;极高性价比&#xff0c;满足IoT行业的数传应用需求。 例如共享应用场景&#xff0c;定位器场景&#xff0c;DTU数 传场景等。 在上文我们…

E82EV752K4C变频器可议价

E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器可议价 E82EV752K4C变频器参数表 E82EV752K4C变频器引脚图 E82EV752K4C变频器线路图 E82EV752K4C变频器节能主要表现在风机、水泵的应用上。风机、泵类负载采用变频调速后&#xff0c;节电率为20%&…

基于Java+SpringBoot+Vue的知识管理系统

基于JavaSpringBootVue的知识管理系统 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 哈喽…