java算法day3

  • 移除链表元素
  • 设计链表
  • 翻转链表
  • 两两交换链表中的结点

移除链表元素

ps:有时候感觉到底要不要写特判,你想到了就写!因为一般特判有一劳永逸的作用。

解法有两种,一种是不用虚拟头结点,另一种就是用虚拟头结点。
这里我习惯用虚拟头结点。不用虚拟头结点就需要自己写头结点的特判。

请添加图片描述
虚拟头结点的好处:方便对每个结点进行统一处理,不用写特判。

解法:双指针
链表的删除操作并不是要找目标元素才能完成删除,而是找目标元素的前一个元素才能完成删除。所以需要一个pre用于指向目标元素的前一个元素,cur用来指向目标元素。

算法流程
cur指针进行判断,如果不是cur指向的不是目标元素,双方一起往后走。pre = cur。cur = cur.next。
如果cur指的是目标元素,那么就要完成上面图里面的操作。

pre的next应该指向cur的后面那个元素,所以是pre.next = cur.next。
此时你的pre还在原地,然后cur指的那个元素被删了,然后就后移。

对于写代码:
可以发现不管是不是目标元素,cur始终是要后移的。而pre如果是cur指目标元素后才后移,如果不是目标元素,就一起后移。

/**
 * 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 val) {
        if (head == null){
            return head;  //空链表判断
        }
		//链表不空,进入下面的移除逻辑
        ListNode dummy = new ListNode(-1,head);  //初始化虚拟头结点
        ListNode pre = dummy;        //由于要进行删除操作,pre应该指向虚拟头结点。
        ListNode cur = head;		//cur用于遍历,用于寻找目标结点。
        while(cur!=null){          //遍历结点不为null
            if(cur.val==val){        //如果等于目标结点
                pre.next = cur.next;  //前一个结点的后驱指向遍历结点的后驱,因为此时这个cur指向的元素是要删除的
            }else{
                pre = cur;  //这个就是不等于目标结点,就两者同时后移
            }
            cur = cur.next;  //不管如何,cur始终在进行遍历,所以要一直后移。
        }
        
        return dummy.next;  //返回虚拟头结点的后一个结点,就是答案。
        
    }
}

设计链表

都是链表的基础操作,需要注意的地方就是题目的要求。

这个题是一个非常好的模板。一定要擅长写模板。

本题为了操作方便,所以用了虚拟头结点作为默认初始化

class ListNode{ //结点类。这个数据结构一定要会写
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val,ListNode next){
        this.val = val;
        this.next = next;
    }
    ListNode(int val){
        this.val = val;
    }
}


class MyLinkedList { //定义i链表
	//链表两个属性:1.长度和最开始的结点
	//但是注意这里链表的第一个结点是虚拟头结点
	//这么设计是为了方便统一操作。

    int size;

    ListNode head; //注意这是虚拟头结点

	//初始化链表,长度为0,然后创建了一个虚拟头结点。
	//所以一定要注意后续的操作的两个细节
	//此时链表第一个结点是虚拟头结点,但是我并不把它计入链表长度
	//然后链表元素下标是从0开始的。后续对index循环那里很重要,小心坑。
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    //
    public int get(int index) {
        if(index<0 || index>=size){ //因为下标从0开始,所以index=size也会判错
            return -1;
        }
        //创建一个遍历指针,注意这里是指的虚拟头结点。所以后面边界才是<=index
        ListNode currentNode = head;
        //开始遍历查找
        for(int i = 0;i<=index;i++){  //就是因为从虚拟头结点开始,而且结点又是从下标0开始。所以才是取等。相当于多+1
            currentNode = currentNode.next;  
        }
        //最后currentNode会停在index的位置。
        
        return currentNode.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0,val); //这就是下面一般情况的应用,由于我是用的带虚拟头结点,所以可以当一般情况来处理。
    }
    
    public void addAtTail(int val) {
        addAtIndex(size,val);//这就是下面一般情况的应用,由于我是用的带虚拟头结点,所以可以当一般情况来处理。
    }
    
    public void addAtIndex(int index, int val) {
        //先进行特判 当下标大于链表长度,则不执行插入
        if(index > size){
            return;
        }
        if(index<0){ //当下标长度为负值,插入到第一个元素,所以这里进行下标的重新修改
            index = 0;
        }
        size++; //要插入,那么链表长度+1
        ListNode pre = head; //由于要插入到目标之前,所以pre是初始化为虚拟头结点。
        for(int i = 0;i<index;i++){//由于是要插入到index元素之前,而pre一开始是在虚拟头结点。所以这样会导致pre最终停在index元素之前的那个元素。
            pre = pre.next;
        }
        ListNode insertNode = new ListNode(val);  //定义新插入的结点,这里用一参构造,next默认初始化为null了
        insertNode.next = pre.next;              //插入操作:新结点的next指向pre的后面那个元素
        pre.next = insertNode;                   //pre再指向新插入的结点。
    }
    
    public void deleteAtIndex(int index) {
        //先对index是否有效进行检查
        if(index < 0  || index>=size){ //下标小于0或者是下标大于或等于链表长度,由于是从0开始,所以等于size也是无效索引。
            return;
        }    
        size--;//删除元素就可以先长度--,因为过了特判就进入了删除逻辑,一定有元素删除
        ListNode pre = head;//删除就必须要找目标元素的前一个元素,所以从虚拟头结点开始最方便
        for(int i = 0;i<index;i++){//由于下标从0开始,而pre一开始在虚拟头结点,所以pre最终停在index的前一个位置
            pre = pre.next;            //不断后移找目标元素的前一个元素
        }
        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);
 */

一定要注意题目要求:带虚拟头结点,而且虚拟头结点不计入链表长度。并且下标从0开始。

翻转链表

注意:没有用到虚拟头结点

这个题怎么想:
首先拿到一个链表:
1 -> 2 -> 3 -> null
翻转链表那就是变成3 -> 2 -> 1 -> null
那很容易就能想到,把指针方向翻转就完成了修改。那就往这个方向去做。

如何完成修改,这里采用双指针,加一个tmp指针用于后移的方法。
请添加图片描述
pre一开始指向null,这里可以理解为在翻转过程中,那么第一个结点的next那必然是null。

cur用于遍历构造。

此时想到cur.next = pre ,那不就完成了next后驱的翻转,但是还有个问题,如果直接cur.next = pre,那就找不到后面的结点,无法继续遍历修改链表了。所以这里再加一个tmp用于存往后遍历的位置,这个tmp的值显然就是cur的后驱:cur.next。

所以现在来看完整的过程:
请添加图片描述
先用tmp记录cur的后驱。方便之后后移。
然后现在就可以放心的改指针了。
cur.next = pre。
请添加图片描述
然后pre也要后移,因为之后还有进行cur.next = pre来进行指针修改。你不后移之后不就改不了了。

pre = cur;
请添加图片描述

然后cur进行后移到tmp。
请添加图片描述
从这个过程往后走,cur最终会走到null上,pre最终停留在最后一个结点。所以循环终止条件就是cur!=null,最后返回pre。请添加图片描述

/**
 * 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) {
        ListNode pre = null; //由于要倒过来,所以pre指在null用于cur改后驱指向。
        ListNode cur = head; //cur指向head用于遍历
        while(cur!=null){
            ListNode tmp = cur.next; //用tmp记录后继,方便改指针之后的后移
            cur.next = pre; //翻转指针
            pre = cur; //翻转成功后,前向指针进行后移
            cur = tmp; //遍历指针后移
        }
        return pre;
    }
}

两两交换链表中的结点

方法:迭代
思路出发:从模拟的角度来
本题是要加虚拟头结点的。因为这更方便处理。上一题就不用加,因为太简单了。对极端情况不敏感还是用虚拟头结点比较好。
请添加图片描述
现在想完成两两交换,那就直接按图来即可。
这里目的是node1,node2完成结点交换。

显然temp.next指向node2,这样node2就到node1的位置来了。请添加图片描述
然后node1现在要作为第二个结点,那就node1指向node2的next结点。请添加图片描述
现在node2的next再指向node1即可。请添加图片描述
现在得到请添加图片描述
前两个就已经改完了,那么现在就要到下一轮,显然temp要变到node1的位置,然后开启下一轮的逻辑。请添加图片描述

最后返回的结果是dummyhead.next

所以现在还剩下两个问题:
1、node1和node2如何定义?
很简单,node1就是temp.next即node1 = temp.next
node2就是temp.next.next即node2 = temp.next.next

2、循环什么时候停止。
思考的突破口在于后移操作,因为在后移的过程中非常害怕空指针。所以后移的空指针是处理就是循环终止的关键。

后移与否就要考虑后面的指针空不空,而且一开始我的temp在虚拟头结点上。
所以循环条件就是:
while(temp.next!=null && temp.next.next!=null)
不能往后移了,代表循环该停了。

如果对极端情况有疑问,把上面那个图截取一部分思考即可。效果是一样的。这就是虚拟头结点的统一处理效果。

/**
 * 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 swapPairs(ListNode head) {
        ListNode dummyhead = new ListNode(0);//创建一个虚拟头结点
        dummyhead.next = head;  //把虚拟头结点加到链表的头部去
        ListNode temp = dummyhead;  //创建迭代的指针temp。
        while(temp.next!=null && temp.next.next!=null){
            ListNode node1 = temp.next; //定义node1
            ListNode node2 = temp.next.next; //定义node2
            //开始进行交换操作,下面三行代码看图来做很清晰
            temp.next = node2;  
            node1.next = node2.next;
            node2.next = node1;
            //注意是指向node1,因为node1被换到了后面去。
            temp = node1;
        }

        return dummyhead.next;
    }
}

这里我还发现一个习惯,一般迭代操作,都习惯去创建一个遍历指针去迭代。这样更加方便,原本代表链表的指针不要动,可以用来做结果的返回。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python安装的详细步骤

下载 1.打开Python官网.我们建议工具类的测试软件还是官网下载比较靠谱. https://www.python.org/getit/ 2.在下图界面中选择需要的方式进行点击 3.直接点击下载.可以进入保存界面,进行保存即可下载,后续安装 4.鼠标放在Downloads显示平台和版本选择界面,点击Windows,进入wi…

Spring-dataSource事务案例分析-使用事务嵌套时,一个我们容易忽略的地方

场景如下&#xff1a; A_Bean 中的方法a()中调用B_Bean的b();方法都开启了事务&#xff0c;使用的默认的事务传递机制&#xff08;即&#xff1a;属于同一事务&#xff09;&#xff1b; 如下两种场景会存在较大的差异&#xff1a; 在b()方法中出现了异常&#xff0c;在b()中进…

Unity射击游戏开发教程:(2)实例化和销毁游戏对象

现在我们有了“飞船”,我们可以在屏幕上移动它,现在我们需要发射一些激光!与宇宙飞船一样,我们将让事情变得简单并使用 Unity 自己的基本形状。舱体的效果很好,所以我们来创建一个。 我们保存了有关位置、旋转和缩放的信息。我们想要缩小这个对象,假设每个轴上缩小到 0.2…

【树莓派学习】hello,world!

系统安装及环境配置详见【树莓派学习】系统烧录及VNC连接、文件传输-CSDN博客 树莓派内置python3&#xff0c;可以直接利用python输出。

Jetson nx 外接OLED屏幕

40 针 GPIO 引脚 GPIO引脚可以用作输入或输出端口&#xff0c;它们提供了一个数字电平以使用户在外界设备上进行控制或读取。Jetson TX2 NX共有198个GPIO引脚&#xff0c;分为三个不同的管脚组&#xff1a;J1、J21和J22。每个管脚组都具有数字输入/输出和PWM功能。 以下是 TX2…

语音转换中的扩散模型——DDDM-VC

DDDM-VC: Decoupled Denoising Diffusion Models with Disentangled Representation and Prior Mixup for Verifed Robust Voice Conversion https://ojs.aaai.org/index.php/AAAI/article/view/29740https://ojs.aaai.org/index.php/AAAI/article/view/29740 1.概述 首先,语…

谷歌Gemini 1.5 Pro国内怎么用?国内镜像来了

长期以来&#xff0c;许多人向我咨询是否存在一个稳定而高效的全球AI大模型测试平台&#xff0c;这个平台需要不仅真实可靠&#xff0c;而且能够提供稳定和快速的服务&#xff0c;不会频繁出现故障或响应缓慢的问题。然而&#xff0c;当我发现了AskManyAI时&#xff0c;我被其所…

ModuleNotFoundError: No module named ‘scripts.animatediff_mm‘ 解决方案

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 大家好,我是水滴~~ 本文主要介绍在使用 Stable Diffusion WebUI 安装 AnimateDiff 插件后出现的ModuleNotFoundError: No module named scripts.animatediff_mm异常的解决方案,希望…

初识ansible变量及实例配置

目录 1、为什么要使用变量 2、变量分类 3、 变量详解 3.1 vars,vars_files , group_vars 3.1 .1 vars 剧本中定义变量 3.1.2 vars_file 将变量存放到一个文件中&#xff0c;并在剧本中引用 3.1.3 group_vars 创建一个变量文件给某个组使用 实例1-根据不同的主机…

力扣HOT100 - 19. 删除链表的倒数第N个节点

解题思路&#xff1a; 链表题目&#xff1a;哑节点、栈、快慢指针&#xff08;双指针&#xff09; 方法一&#xff1a;计算链表长度 class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode dum new ListNode(0, head);int len getLen(head);…

linux内核初始化成功后是如何过渡到android初始化的

Android用的linux内核&#xff0c;以完成OS该有的功能&#xff0c;例如&#xff0c;文件系统&#xff0c;网络&#xff0c;内存管理&#xff0c;进程调度&#xff0c;驱动等 &#xff0c;向下管理硬件资源向上提供系统调用。另一些Android特有驱动也放在内核之中。 当linux内核…

Java关键字和API

1 this和super关键字 1.this和super的意义 this&#xff1a;当前对象 在构造器和非静态代码块中&#xff0c;表示正在new的对象 在实例方法中&#xff0c;表示调用当前方法的对象 super&#xff1a;引用父类声明的成员 无论是this和super都是和对象有关的。 2.this和sup…

详解数据结构之-「数组篇」

详解数据结构之-「数组篇」 太久没有写算法了&#xff0c;最近在回顾数据结构和算法&#xff0c;准备把这块重新捡起来&#xff0c;给自己立一个flag&#xff0c;每周花时间练习算法&#xff0c;写一篇总结。 数组 数组数据结构&#xff08;英语&#xff1a;array data stru…

Mysql学习2

目录 一.数据库&#xff1a; 1.创建数据库&#xff1a; 2.查看数据库&#xff1a; 3.备份恢复数据库&#xff1a; 二.表 1.创建表指令&#xff1a; 2.MySQL常用数据类型&#xff1a; 3.删除与修改表&#xff08;重点&#xff09;&#xff1a; 4.数据库CRUD语句&#xf…

网络 (TCP/IP 四层协议中常见网络协议)

应用层 DNS (Domain Name System) 域名系统. DNS 是一整套从域名映射到 IP的系统 NAT 技术 解决 IP 地址不够用的问题. 可以实现私有 IP 和全局 IP 的相互转换 NAPT 技术 使用 IP Port 唯一确定局域网中的主机 传输层 TCP 协议 (Transmission Control Protocol 传输控制协议…

C++:范围-based for 循环

范围-based for 循环是 C11 引入的一种循环语法&#xff0c;它简化了遍历容器和数组等序列的操作&#xff0c;使代码更加清晰和简洁。它通常用于遍历容器类&#xff08;如数组、向量、列表等&#xff09;中的元素&#xff0c;或者以范围的形式遍历初始化列表。 范围-based for …

AI大模型探索之路-实战篇1:基于OpenAI智能翻译助手实战落地

文章目录 前言一、需求规格描述二、系统架构设计三、技术实施方案四、核心功能说明五、开源技术选型六、代码实现细节1.图形用户界面&#xff08;GUI&#xff09;的开发2.大型模型调用的模块化封装3.文档解析翻译结果处理 总结 前言 在全球化的浪潮中&#xff0c;语言翻译需求…

HarmonyOS NEXT 使用Canvas实现模拟时钟案例

介绍 本示例介绍利用 Canvas 和定时器实现模拟时钟场景&#xff0c;该案例多用于用户需要显示自定义模拟时钟的场景。 效果图预览 使用说明 无需任何操作&#xff0c;进入本案例页面后&#xff0c;所见即模拟时钟的展示。 使用说明 无需任何操作&#xff0c;进入本案例页…

20240330-2-XGBoost面试题

XGBoost面试题 1. RF和GBDT的区别 相同点&#xff1a; 都是由多棵树组成&#xff0c;最终的结果都是由多棵树一起决定。 不同点&#xff1a; 集成学习&#xff1a; R F RF RF属于 B a g g i n g Bagging Bagging思想&#xff0c;而 G B D T GBDT GBDT是 B o o s t i n g Bo…

【数据结构2-线性表】

数据结构2-线性表 1 线性表-数组2 线性表-单链式结构2.1 前插顺序单链表2.2 后插顺序单链表2.3 循环单链表2.4 双向链表 总结 线性表、栈、队列、串和数组都属于线性结构。 线性结构的基本特点是除第一个元素无直接前驱&#xff0c;最后一个元素无直接后继之外&#xff0c;其他…