链表经典刷题--快慢指针与双指针

news2024/12/26 9:19:50

本篇总结链表解题思路----快慢指针,其实也就是双指针,这个快慢并不单纯指“快慢”,它更多的可以表示,速度快慢,距离长度,时间大小等等,用法很有趣也很独特,理解它的思想,是很有必要的。

🍭1.链表的中间结点----‘速度’

加粗样式
要求返回链表中的中间结点;
当有奇数个结点时,返回中间的结点,当有偶数个结点时,返回第二个中间结点。
传统的思想是遍历链表,算链表长度,然后再找中间值。
但如果只允许遍历一遍链表呢?这种方法就不好使了。

使用快慢指针,轻松解决。
什么叫快慢指针,就是让两个指针都指向链表同时往后走,一个指针每次走两步,称为快指针,一个指针每次走一步,称为慢指针。
当快指针走到尾时,而慢指针所指向的地方就是中间结点。
这种天才想法在后面的许多链表题中都有应用,对解题很棒。
本题需要注意一下奇数个和偶数个结点有什么不同,当结点为偶数时,fast走到了最后一个结点的后面,所以fast成为NULL,当结点为奇数时,fast走到最后一个结点停止,所以fast->next为NULL。所以这个循环的条件就是fast和fast->NULL都不为空时,快慢指针才能往后走。
在这里插入图片描述

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* middleNode(struct ListNode* head)
{
   struct ListNode *fast,*slow;//设置两个指针
   fast=slow=head;//让快慢指针同时从开头走
   while(fast!=NULL&&fast->next!=NULL)//当fast不为NULL和fast->next不为空时,才可以正常走,一旦有一个为NULL,则说明两种情况中有一个完成。
   {
       slow=slow->next;//慢指针每次走一步
       fast=fast->next->next;//快指针每次走两步
   }
   return slow;//最后不管结点是偶数个还是奇数个,返回值都是慢指针指向的位置

}

在这里插入图片描述

🍭2.链表中倒数第k个结点----‘距离’

在这里插入图片描述
先输入k,要求输出该链表的第k的位置上的结点。
传统思想,遍历链表,算出长度,然后用长度减k再加1即可。
那如果跟上面一样只给你遍历一遍链表那该怎么做呢?

同样方法,快慢指针
只不过这时比较的不是速度了,而是两个指针之间的距离。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以让快指针先走k-1步,然后再让慢指针开始走,当快指针走到最后一个结点时停止,慢指针指向的就是要输出的。
在这里插入图片描述

我们还可以让快指针先走k步,然后让慢指针开始走,这时,快慢指针之间的距离就变成了k,当快指针走到最后一个结点时,慢指针其实指向的是前面的结点,而要让慢指针指向后面的结点,让快指针再走一步就可以了。
代码如下:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode *fast,*slow;//定义两个指针
    fast=slow=pListHead;//一开始快慢指针都指向开头
    if(fast==NULL)//如果链表为空链表则无法返回,返回NULL
    return NULL;
    int num=k-1;
    if(k==0)//如果是倒数0个那么也返回NULL
    {
        return NULL;
    }
    while(num--)//先让快指针走k-1步
    {
        if(fast->next==NULL)//我们要确保k的大小不能大于链表的长度对吧,当k的长度大于链表时,就会让链表访问越界,所以先给fast往后走时,要注意不能越界了,如果fast->next为NULL,则表明k是大于链表长度的,之间返回NULL
       {
         return NULL;
       }
        fast=fast->next;
    }
    while(fast->next)//判断结束的条件是当fast->next为NULL时,结束,也就是fast走到最后一个结点时停止,这时快慢指针之间的距离为k-1,而最好一个结点与慢指针所指向的结点的距离就是k-1
    {
        slow=slow->next;//慢指针和快指针一起走
        fast=fast->next;
    }
    return slow;//最后返回slow位置即可
}

在这里插入图片描述
第二种图解:
在这里插入图片描述

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode *fast,*slow;//定义两个指针
    fast=slow=pListHead;
    if(fast==NULL)//当链表为空时,返回NULL
    return NULL;
    while(k--)//快指针先走k步
    {
        //要考虑k如果大于链表本身长度时就非法
        if(fast==NULL)
        {
            return NULL;
        }
        fast=fast->next;//
    }
    while(fast)//这个和快指针先走k-1的结束条件不一样喔,这个结束条件应该是快指针走到最后结点的后面,所以是fast为NULL循环结束
    {
        slow=slow->next;//快慢指针一起走
        fast=fast->next;
    }
    return slow;//返回慢指针所在的位置
}

🍭3.移除链表元素

在这里插入图片描述
方法1:
我们可以按照传统单链表是如何删除来写。
单链表如何删除呢?
我们要删除这个结点,还要将前面的结点和后面的结点链接起来。这时我们就必须要记录下前面结点的地址了,不然单链表无法再找到前一个结点。
所以我们可以用双指针,一个是进行遍历的指针cur,另一个是用来记录它前面结点的位置的指针prev。
当cur->val!=val时,cur先赋值给prev记录这个位置,然后cur再往后走。
当cur->cal==val时,那么就要将前一个结点链接到后一个结点上。
pre->next=cur->next;
然后释放掉cur。
大体思路是这样,不过还有一些细节问题。
比如如果最后一个元素是要被删的,那么前面一个结点的next就变成野指针了。
所以我们需要将他置为NULL。
还有个问题,如果第一个就是要被删除的结点,那么prev->next就非法,所以要进行讨论
当prev= =NULL时,说明第一个结点就要被删除,相当于头删。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val){

struct ListNode*cur=head;//cur用来遍历链表,首先指向最前面的
struct ListNode*prev=NULL;//一开始prev为空
while(cur)//利用cur进行遍历
{
    if(cur->val!=val)//如果不等于
    {
        prev=cur;//让prev记录这个位置
        cur=cur->next;//cur往后走
    }
    else//如果等于
    {
        if(prev==NULL)//要考虑是否是第一个要删除的,当prev==NULL时,就是删除第一个结点
        {
            head=cur->next;//将头结点与第二个结点链接
            free(cur);//是否cur
            cur=head;//cur往后走,也就是走到第二个结点位置
        }
       else
       {
        prev->next=cur->next;//将前面的与后面的链接
        free(cur);//删除中间的
        cur=prev->next;//cur往后走
       }
    }
}
return head;//返回头结点
}

方法2:
我们可以利用数组删除元素的思想来删除链表中的结点
在数组中我们是将不等于val的元素放入一个新的数组。
而在链表里我们也可以这样,当不等于val的结点我们就插入到一个新的链表上去,所以我们还需要创建一个新链表。

我们每次将新结点插入到新链表中时都需要找尾,非常烦人,我们可以定义一个尾指针tail,用来指向新插入结点的地址。
如果cur->val!=val那么就进行尾插,将新结点插入新链表
如果cur->val=val,那么cur 就往后走。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeElements(struct ListNode* head, int val){

struct ListNode*cur=head;
struct ListNode*tail=NULL,*newnode=NULL;//创建一个新结点
while(cur)//遍历链表
{
    if(cur->val!=val)//如果不相同,就把不相同的结点插入到新的链表中
    {
        //尾插
        if(newnode==NULL)//最初新链表肯定为空,可以直接将cur指向的不相同的结点放入到新链表中
        {
            newnode=tail=cur;
        }
        else
        {
            tail->next=cur;//cur所对应的结点插入到tail后面
            tail=tail->next;//tial要往后走,记录下一个插入结点的位置
        }
        cur=cur->next;//cur也要往后走
    }
    else
    {
        struct ListNode*next=cur->next;//如果元素相同,那先把这个结点的下一个结点地址记录下来
        free(cur);//释放这个要删除的结点
        cur=next;//cur往后走
    }
}
   if(tail!=NULL)//当tail不为空时,我们需要将tail的next置为NULL
        tail->next=NULL;
return newnode;
}

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

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

相关文章

「并发编程实战」常见的限流方案

「并发编程实战」常见的限流方案 文章目录「并发编程实战」常见的限流方案一、概述二、计数器限流方案三、时间窗口限流方案四、令牌桶限流方案五、漏桶限流方案六、高并发限流算法小结文章参考: 追忆四年前:一段关于我被外企CTO用登录注册吊打的不堪往事…

01 | n2n虚拟局域网

1 n2n简介 为了满足两个不同局域网的机器进行通信,让不同网段的机器能够进行P2P( 点对点 peer-to-peer ) 通信。2 n2n源码 https://github.com/ntop/n2n.git3 n2n名词 3.1 SuperNode 超级节点 SuperNode 相当与注册中心, 它会记录边缘节点的连接信息,…

案例09-数据类型不一致导致equals判断为false

一:背景介绍 在判断课程id和班级id的时候如果一致就像课程信息进行更新,如果不一致就插入一条新的数据。其实两个变量的值是一致的但是类型是不一致的。这就导致数据库中已经有一条这样的数据了,在判断的时候结果为false,就有插入…

【这一篇就够】mysql创建JSON数据的索引

一. 创建索引 由于json有两类不同的数据形式,即:json对象(如:{"id": 1, "name":"he"}),json数组(如:["1","2","3"]&…

nexus安装与入门

安装 nexus-3.31.1-01-unix.tar.gz 链接:https://pan.baidu.com/s/1YrJMwpGxmu8N2d7XMl6fSg 提取码:kfeh 上传到服务器,解压 tar -zvxf nexus-3.31.1-01-unix.tar.gz进入bin目录,启动 ./nexus start查看状态 ./nexus status默…

初始Linux操作系统

个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。座右铭:海不辞水,故能成其大;山不辞石,故能成其高。个人主页:小李会科技的…

Linux常用命令等

目录 1.Linux常用命令 (1)系统命令 (2)文件操作命令 2.vim编辑器 3.linux系统中,软件安装 (1) rpm 安装,RedHat Package Manager (2)yum 安装 (3)源代码编译安装 1.Linux常用命令 Linux命令是非常多的,对于像嵌入式开发工程师,运维工程师需要掌握的命令是非常多的.对于…

旋转矩形框标注--roLabelImg的使用

1. 旋转目标标注roLabelImg roLabelImg是一款开源的,可标注带旋转角度的矩形区域的标注软件。roLabelImg源码github地址: https://github.com/cgvict/roLabelImg labelme和labelimg只能标矩形框,但不能标注旋转角度。roLabelImg既能标矩形框也能标注矩形框的选择角度,因此…

复旦发布中国版 ChatGPT :MOSS

不知道这个人工智能,有没有获得完整的一生。ChatGPT 是最先进的 AI,也是最热门的应用 —— 自去年 11 月底发布以来,它的月活跃用户两个月超过一亿,轻松拿到了全球互联网史上用户增长速度的第一。它也是一种门槛很高的技术。由于 …

服务预热配置化在泛型化方法上的实践

背景 由于开发过程中,个别dubbo接口的调用会在服务发布过程中,出现P99耗时报警问题。因此我们计划通过预热服务接口,通过预热来触发JIT,构建DB资源长链接。实现服务接口上线后,耗时过长,资源等待等问题&am…

基于RWEQ模型的土壤风蚀模数估算及其变化归因分析

查看原文>>>基于RWEQ模型的土壤风蚀模数估算及其变化归因分析 土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一,土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2&#xff…

python re模块匹配字符串

python 正则模块re 要使用python3中的RE则必须引入 re模块 import re re模块的match函数 result re.match(^[A-Z]{1}[a-z], s) match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。 匹配到了,则…

AI for Science系列(二):国内首个基于AI框架的CFD工具组件!赛桨v1.0 Beta API介绍以及典型案例分享!

AI for Science被广泛认为是下一代科研范式,可以有效处理多维度、多模态、多场景下的模拟和真实数据,解决复杂推演计算问题,加速新科学问题发现[1] 。百度飞桨科学计算工具组件赛桨PaddleScience是国内首个公开且可应用于CFD(Comp…

通过工厂模式实现SpringBoot+MQTT-订阅与消费

引言 Spring Boot 是一款用于构建基于 Spring 框架的快速应用程序的开源框架。它的目标是简化 Spring 应用程序的开发和部署过程,Spring Boot 通过提供一些默认配置和自动配置,可以让开发者更快的创建一个独立的、产品级别的 Spring 应用程序。 MQTT 是…

pathon Django的关系映射

一对一 【创建】 一对一是表示现实事物间存在的一对一的对应关系。特殊字段选项 【必须】 on_delete - 级联删除 更多参考模型字段参考 | Django 文档 | Django使用oto示例: 1、先创建oto应用,然后到setting.py文件注册应用2、创建oto模型类3、创建…

【人工智能 AI】机器学习快速入门教程(Google)

目录 机器学习术语 标签 特性 示例 模型 回归与分类 深入了解机器学习:线性回归 深入了解机器学习:训练和损失 平方损失函数:一种常用的损失函数 机器学习术语 预计用时:8 分钟 什么是(监督式&#xff…

蚁群算法再优化:combine aco algorithm with Sarsa in RL

蚁群算法再优化:combine aco algorithm with Sarsa in RL蚁群算法、Sarsa介绍和TSP问题介绍TSP和Sarsaaco algorithm具体的改进和代码改进说明部分代码数值实验结论分析参考文献蚁群算法、Sarsa介绍和TSP问题介绍 在进行蚁群算法优化介绍之前,笔者先将涉…

Apache Pulsar 云原生消息中间件之王

一、简介 pulsar,消息中间件,是一个用于服务器到服务器的消息系统,具有多租户、高性能等优势。 pulsar采用发布-订阅的设计模式,producer发布消息到topic,consumer订阅这些topic处理流入的消息,并当处理完…

OIDC OAuth2.0 认证协议最佳实践系列 02 - 授权码模式(Authorization Code)接入 Authing

在上一篇文章OIDC & OAuth2.0 认证协议最佳实践系列 02 - 授权码模式(Authorization Code)接入 Authing中,我们整体介绍 OIDC / OAuth2.0 协议,本次我们将重点围绕授权码模式(Authorization Code)以及接…

RabbitMQ第一讲

目录 一、RabbitMQ-01 1.1 MQ概述 1.2 MQ的优势和劣势 1.2.1 优势 1.2.2 劣势 1.2.3 MQ应用场景 1.2.4 常用的MQ产品 1.3 RabbitMQ的基本介绍 1.3.1 AMQP介绍 1.3.2 RabbitMQ基础架构 1.3.3 RabbitMQ的6种工作模式 ​编辑 1.4 AMQP和JMS 1.4.1 AMQP 1.4.2 JMS …