链表算法题(上)

news2024/11/28 2:38:53

在之前单链表和双链表两个专题中我们学习了链表相关的概念和性质,同时了解了单链表和双链表各自的特征,那么接下来在本篇中我们就将使用这些链表的知识来解决链表相关的算法题,在本篇中这些算法题能强化我们的算法思想,会对我们之前的编程学习有很大的益处,一加油吧!!!


 1.移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

通过以上的题目描述就可以了解到该算法题要我们实现的是将单链表中节点中为指定数据的都移除

例如以下示例

在以上链表中要将链表中节点值为6的节点移除,要实现移除的操作就要将存储值为6的节点之前的节点的next指针指向该节点的下一个节点,但这种方法在删除过程中还要将指定节点释放,这会稍嫌麻烦。其实还可以直接在创建一个新的链表之后将节点的数据不为6的节点拷贝到新链表当中,通过遍历原来的链表就可以实现新链表中无指定数据6的节点,这就实现了移除链表元素 

通过以上示例的分析接下来我们就来试着实现该算法题的代码
 

在以下代码中使用的是创建一个新链表的思想来实现原链表中元素的移除,创建两个结构体指针变量newhead和newpcur先置为NULL在此newhead是用来指向新链表的第一个节点,newpcur是用来让新节点能接入到新链表中而进行新链表内的遍历。再创建一个结构体指针变量pcur来遍历原链表,当在遍历原链表节点过程中如果节点内存储的值不为val就将该节点指针pcur拷贝newpcur ,在此之后在将newptail指向原来newpcur指向节点内的next指针指向的节点,在以上过程中由于一开始newptail指向NULL,因此要再判断在进行以上操作时newhead是否为NULL,如果是就直接将newhead和newpcur都指向pcur指向的节点也就是将newhead和newpcur都赋值为pcur

在以下代码中当遍历完原链表并将相关的节点都拷贝到新链表内时,就会出现当原链表尾节点为要移除的节点时,原链表中的尾节点回随着前一个结点拷贝到新新链表中,就例如以下示例,因此要在遍历完原链表后将指针newpcur的next置为NULL。但这时还有一个问题就是如果原链表全部结点都为要移除的节点,那么这时newpcur就为NULL,如果再将newpcur->next=NULL就会出现对空指针的解引用因此在进行以上这句代码时还要使用if语句来判断指针newpcur是否为NULL,只有当不为NULL才执行该语句

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{
     if(head==NULL)//当节点head为NULL时就说明原链表为空链表这时直接返回
     {
        return head;
     }
    ListNode* newhead,*newpcur;
    newhead=newpcur=NULL;
    ListNode* pcur=head;
    while(pcur)
    {
        if(pcur->val!=val)//当节点内的值不为val时
        {
            if(newhead==NULL)
            {
                newhead=newpcur=pcur;
            }
            else
            {
                newpcur->next=pcur;
                newpcur=newpcur->next;
            }
        }
        pcur=pcur->next;
    }
 if(newhead)
    newpcur->next=NULL;
   
    return newhead;

}

2.反转链表

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

通过以上的题目描述就可以了解该算法题要我们实现的是链表的反转也就是要使改变后链表内的节点顺序和原来倒序一样

例如以下示例

要将以上链表反转你可能想到是先创建一个数组后通过遍历链表将链表节点的值存储到数组当中 ,之后将数组反转后再通过遍历链表将反转后的数组的值依次存储到节点内,这种方法是可行的但这种方法要通过多次遍历链表 ,这就会使得时间复杂度较高,那么有什么更好的算法呢?

在这里我们来学习一种3指针的方法来解决该算法题,例如以下的链表示例

一开始定义3个指针n1,n2,n3,一开始将n1初始化置为NULL,n2指向链表的第一个节点,n3指向n2的下一个节点。之后将n2指向的的节点的next指针指向n1所指向的数据,之后将n1赋值为n2,n2赋值为n3,n3赋值为n3所指向的节点的下一节点的地址;一直重复以上的操作直到n1指向原链表最后节点时,这时原链表就变为以下形式,就完成了链表的反转

 通过以上示例的分析接下来我们就来试着实现该算法题的代码
当原链表为空时就直接返回NULL
以下使用一个循环来实现链表中每个节点next的改变,在此改变的思想就是用到上面讲解的3指针法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{
    if(head==NULL)
    {
        return NULL;
    }
    ListNode* n1,*n2,*n3;
    n1=NULL;
    n2=head;
    n3=n2->next;
    while(n2)
    {
        n2->next=n1;
        n1=n2;
        n2=n3;
        if(n3)//当节点n3为NULL时就不再进行n3=n3->next
        {
            n3=n3->next;
        }

    }
    return n1; //返回反转后的第一个节点的指针
}

3.合并两个有序链表

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

通过以上题目的描述就可以了解该算法题要我们实现的是将两个升序的链表合并为一个升序的链表 

例如以下示例

要将以上两个链表合并这时你可能会想到遍历其中一个链表将该链表中的节点插入到另一个链表中,但这种方法就需要在插入节点过程中改变多个节点内的next指针这就会比较繁琐。因此我们不会使用该方法,而是选择再创建一个链表来存储合并后的链表 ,在该过程中通过同时遍历两个原链表比较节点内值得大小来确定节点排序

  通过以上示例的分析接下来我们就来试着实现该算法题的代码

在以下代码中当指针list1为NULL时就说明list1指向的链表为空,这时就直接返回list2;当指针list2为NULL时就说明list2指向的链表为空,这时就直接返回list1

在创建新链表时,创建一个头节点也就是哨兵位节点,这样就可以不用再单独处理当新链表为空

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode; 
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1==NULL)
    {
        return list2;
    }
    if(list2==NULL)
    {
        return list1;
    }
    ListNode* pcur1;
    pcur1=list1;
    ListNode* pcur2;
    pcur2=list2;
    ListNode* newhead,*newptail;
    newhead=newptail=(ListNode*)malloc(sizeof(ListNode));
    while(pcur1&&pcur2)
    {
        if(pcur1->val<pcur2->val)
        {
            newptail->next=pcur1;
            pcur1=pcur1->next;
        }
        else
        {
            newptail->next=pcur2;
            pcur2=pcur2->next;
        }
        newptail=newptail->next;
    }
    if(pcur1)
    {
        newptail->next=pcur1;
    }
    if(pcur2)
    {
        newptail->next=pcur2;
    }
    ListNode* p=newhead->next;
    free(newhead);
    newhead=NULL;
    return p;

}

4. 链表的中间结点

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

通过以上题目的描述就可以了解到该算法题要我们实现的是找出链表的中间节点,并且返回中间节点 

例如以下示例

当链表节点个数为奇数和偶数时中间节点有什么区别呢?

来看以下链表,当链表节点个数为偶数时中间节点就为链表节点数除二后再加一得到就是中间节点的序号

那么要使用什么方法才能得到链表的中间节点呢?
在此我们来学习一种
快慢指针的方法,先定义两个指针fast和slow,一开始都指向链表的第一个节点,之后让fast指针一次走两步;slow指针一次走一步直到fast或者fast->next为空时,此时slow指向的节点就为中间节点

通过以上示例的分析接下来我们就来试着实现该算法题的代码
 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) 
{
    if(head==NULL)
    {
        return head;
    }
    ListNode* fast,*slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

5.链表分割 

面试题 02.04. 分割链表 - 力扣(LeetCode)

链表分割_牛客题霸_牛客网 (nowcoder.com)

通过以上的题目描述就可以了解到该算法题要我们实现的是将链表中比特定值x小的节点都放在比特定值x大的节点之前,在此调整完的链表不一定要为升序

接下来就来分析如何实现链表的分割
例如以下示例

要将以上链表中比3大的节点都排在比3小的节点之前要如何实现呢?

在此我们学习一种大小链表的算法来解决链表的问题,就比如以上链表先创建两个新链表一开始里面无节点,之后通过遍历链表先将比3小的节点保存在第一个新链表中;将大于等于3的节点保存在第二个节点中。在此将第一个链表称为小链表,第二个链表称为大链表

之后再将小链表的尾节点和大链表的第一个节点连接就得到分割完的链表

 分析完解决链表分割的方法后接下来就试着实现链表分割的代码
在实现链表分割的代码中在大小链表的初始化时都让其有哨兵位的节点,这样做的是为了之后在大链表或者小链表插入节点时不用再单独去处理大链表或者小链表为空这种情况

在将原链表内的节点都分到大小链表后,greattail->next=NULL这句代码是将大链表最后一个节点内的next指针置为NULL,这是避免大链表最后一个节点还连着其他节点

注意:在代码的最后要将大小链表的哨兵位节点的内存空间释放,避免形成内存泄漏

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
    ListNode* smallhead,*smalltail;
    ListNode* greathead,*greattail;
    smallhead=smalltail=(ListNode*)malloc(sizeof(ListNode));
    greathead=greattail=(ListNode*)malloc(sizeof(ListNode));
    ListNode* pcur=head;
    while(pcur)
    {
        if(pcur->val<x)
        {
            smalltail->next=pcur;
            smalltail=smalltail->next;
            pcur=pcur->next;
        }
        else
        {
            greattail->next=pcur;
            greattail=greattail->next;
            pcur=pcur->next;
        }
    }
    
    greattail->next=NULL;
    smalltail->next=greathead->next;//将大小链表连接
    ListNode* ret=smallhead->next;
    free(smallhead);
    free(greathead);
    smallhead=greattail=NULL;

    return ret;
}

6.链表的回文结构 

链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

在通过以上的题目描述可以了解到该算法题要我们实现的是链表回文结构的判断,在此链表为回文结构简单来说就是链表是对称的 

在此在实现该算法题的代码之前先要来分析使用说明算法来解决

首先来看以下的回文结构链表示例

在以上的链表当中我们可以看出当一个链表为回文结构时,链表相对应的节点内的值是相同的,了解了这个特性后你可能就会有一个想法就是可以先通过遍历得到链表的节点数,之后根据得到的节点数来创建一个元素个数和节点数相同的数组,之后再遍历一次链表将链表内的值一一都保存到数组当中,在此之后定义两个变量一个为数组首元素的下标;另一个为数组最后一个元素的下标,之后让这两个下标一个从左到右;一个从右向左遍历数组,在此比较相对应下标元素的值是否相同,如果当两个变量都相同时都为出现不相同的情况就说明原链表为回文结构

以上的这种算法思想的确是可以来解决该算法题的,不过使用这种方法时会多次的遍历原链表这就会使得算法的效率不高因此在此我们不使用这种算法

那么如果不用以上这种算法还有什么更好的解法呢? 

对于是否是回文结构的链表如果我们能先找到原链表的中间节点后再将中间节点之后的链表反转,之后两个指针一个指向链表第一个节点,另一个指向链表尾节点,再从两段来遍历链表比较相对应节点内的值不就可以判断原链表是否是回文结构了吗?

例如以上示例按照这种方法过程图如下所示:

对回文结构的链表判断进行分析后接下来就来实现该算法题吧 

以下在实现链表找中间节点的函数就用到之前链表的中间结点算法题的解决方法,在实现将链表中间之后的链表反转的函数就用到之前反转链表算法题的解决方法

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/

#include <valarray>
ListNode*SLTFindMid(ListNode* head)
{
    ListNode* slow,*fast;
    slow=fast=head;
    while(fast &&fast->next)
    {
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
}


ListNode* SLTReverse(ListNode* head)
{
    ListNode*n1,*n2,*n3;
    n1=NULL,n2=head,n3=n2->next;
    while(n2)
    {
        n2->next=n1;
        n1=n2;
        n2=n3;
        if(n3)
        {
            n3=n3->next;
        }
    }
    return n1;
}

class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) 
    {
        //找出链表的中间节点
        ListNode*mid=SLTFindMid(A);
        //反转中间节点之后的链表
        ListNode*right=SLTReverse(mid);
        //判断前后两段链表是否相同
        ListNode* left=A;
        while(right)
        {
            if(left->val!=right->val)
            {
                return false;
            }
            left=left->next;
            right=right->next;
        }
        return true;     
    }
};

注:在以上的代码语言环境选择的是C++,这时因为该算法题牛客没有提供C语言的代码环境,但C++其实是兼容C语言的,所以我们可以在此使用C语言来实现代码,在此ListNode其实在C++使用struct就成为一个类,所以不用使用typedef也之后可以直接使用LIstNode。如果你想了解更多原因可以观看C++篇章的内容

以上就是链表算法题(上)的全部内容了,在链表算法题(下)中我们将继续来解决链表的算法题,未完待续……

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

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

相关文章

HTML5好看的花店商城源码2

文章目录 1.设计来源1.1 主界面1.2 界面效果11.3 界面效果21.4 界面效果31.5 界面效果41.6 界面效果51.7 界面效果61.8 界面效果71.9 界面效果8 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#…

postgresql数据库创建表分区和分区分区查询失效问题

postgresql数据库创建表分区和分区失效场景 一、前言二、分区表创建1、范围分区&#xff08;Range Partitioning&#xff09;2、列表分区&#xff08;List Partitioning&#xff09;3、hash分区&#xff08;hash Partitioning&#xff09; 三、表分区查询失效问题 一、前言 在…

AcWing算法基础课-785快速排序-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》785 题——快速排序。这篇文章介绍了使用快速排序算法对整数数列进行排序的方法&#xff0c;包括选择基准元素、分区操作和递归排序子数组。通过详细的步骤和示例&#xff0c;解释了快速排序的…

Axure打造科技感数据可视化大屏原型

在数字化浪潮的推动下&#xff0c;数据已成为企业决策不可或缺的核心驱动力。面对海量且复杂的数据集&#xff0c;如何高效解读并转化为洞见&#xff0c;是企业面临的重大挑战。数据可视化&#xff0c;尤其是科技感十足的大屏展示&#xff0c;不仅为企业提供了直观的数据洞察&a…

9,sql 约束

创建表时添加非空约束 create table sys3(name int,age int not null) 建表后修改 alter table sys3 modify name int null 删除 创建表时添加默认值约束 关键字 default 默认值的意思 后面跟着默认的值create table sys_4(列名1 数据类型 default 默认值,列名2 数据类型 no…

网络安全售前入门09安全服务——安全加固服务

目录 1.服务概述 2.流程及工具 2.1服务流程 2.2服务工具 3.服务内容 ​​​​​​​4.服务方式 ​​​​​​​5.风险规避措施 ​​​​​​​6.服务输出 1.服务概述 安全加固服务是参照风险评估、等保测评、安全检查等工作的结果,基于科学的安全思维方式、长期的安全…

Apache CloudStack Official Document 翻译节选(十三)

快速部署一朵 Apache CloudStack 云 &#xff08;二&#xff09; 部署一朵pache CloudStack 云 安装Apache CloudStack的云内管理服务组件 本部分我们将安装Apache CloudStack的云内管理服务组件及周边工具。 关于数据库的安装与配置&#xff1a; 我们会安装和配置MySQL并配…

【机器学习入门】一文读懂非线性支持向量机SVM

前面已经分别介绍了基于硬间隔最大化的线性可分支持向量机、基于软间隔最大化的线性支持向量机&#xff0c;这次来总结下使用核函数来解决非线性可分问题的非线性支持向量机。 【机器学习入门】一文读懂线性可分支持向量机【机器学习入门】一文读懂线性支持向量机SVM 一 非线…

GCViT实战:使用GCViT实现图像分类任务(一)

摘要 GC ViT&#xff08;全局上下文视觉转换器&#xff09;是一种创新的深度学习架构&#xff0c;旨在提升计算机视觉任务中的参数和计算效率。它通过将全局上下文自注意力模块与标准的局部自注意力相结合&#xff0c;有效地建模长程和短程空间交互&#xff0c;同时避免了传统…

IJCAI-信也科技杯全球AI大赛-华东师范大学亚军队伍分享

作者&#xff1a;彭欣怡(找不到工作版) 华东师范大学; 马千里(搬砖版) 虾皮; 指导&#xff1a;闫怡搏(科研版) 华东师范大学 比赛链接&#xff1a;https://ai.ppdai.com/mirror/goToMirrorDetailSix?mirrorId34 前言 这是我们首次参加语音领域的比赛&#xff0c;最初只是抱着…

深度学习之 OpenCV 图像边缘检测算法解析及代码演示

引言 在计算机视觉领域&#xff0c;边缘检测是一种重要的图像预处理技术&#xff0c;用于识别图像中对象的边界。边缘检测有助于提取图像的关键特征&#xff0c;这对于后续的图像分析&#xff08;如物体识别、运动检测等&#xff09;至关重要。OpenCV 是一个强大的计算机视觉库…

Zabbix 企业级高级应用(Zabbix Enterprise Advanced Application)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

备忘录1【java环境变量手动更改】

B站视屏链接&#xff1a;https://www.bilibili.com/video/BV1cV8BeCESA?vd_source3e4562475feb55327a8adde111050d4e 1.电脑设置 2.系统-------系统信息 3.高级系统设置 4.环境变量 5.系统变量中&#xff0c;更改之前创建好的JAVA_HOME 版本可以将1.8改成本机安装的其他版本…

Java进阶13讲__第九讲

Stream流 1. 案例初体验 package cn.hdc.oop9.stream.using;import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream;public class t1 {public static void main(String[] args) {LinkedList<String&g…

认知杂谈30

今天分享 有人说的一段争议性的话 I I 《豁然开朗&#xff1a;男性成长的关键转折》 在男人的人生旅程当中啊&#xff0c;最金贵的可不是那些让人眼馋的钱财啥的&#xff0c;也不是啥惊天动地的事儿&#xff0c;更不是偶尔碰到的贵人帮忙。真正无价的宝贝呢&#xff0c;是在…

培训第四十一天(docker-compose一键部署项目,haproxy容器代理多个web或java容器)

# 创建脚本&#xff0c;可以在java环境中运行任何的jar包或者war包#!/bin/bash/usr/local/jdk/bin/java -jar /java/src/*.?ar 一、思路分析&#xff1a; &#xff08;1&#xff09;nginx 1、下载镜像&#xff0c;将本地的dist项目的目录挂载在容器的/usr/share/nginx/html…

论文解读:Visual Prompt Tuning

Comment: ECCV2022 摘要 当前调整预训练模型的方法需要更新backbone的全部参数&#xff0c;即完全微调。本文提出了一种高效的方法VPT&#xff0c;成为视觉中大规模transformer模型完全微调的替代方案。从微调大规模语言模型中吸取经验&#xff0c;VPT仅在输入空间中引入少数…

【C语言】通讯录的实现(详解)

通讯录的实现 主要用到的知识&#xff1a; 1.结构体相关知识 2.枚举 3.多文件 在学习完结构体和枚举后就可以利用学过的知识去实现一个通讯录了 1.通讯录要求 用C语言所学知识实现下面这样一个通讯录 1.通讯录100个联系人 2.姓名 性别 年龄 电话 地址 3.添加联系人 4.删除指…

SQL server数据库实现远程跨服务器定时同步传输数据

项目背景 公司新建项目&#xff0c;需要访问生产数据&#xff0c;但是规定不能直接访问生产数据库服务器&#xff0c;所以得考虑通过中间库的形式实现。经过评估项目需求 &#xff0c;以及当前拥有的环境。 需求&#xff1a; 1.用户不需要实时获取生产数据 2.用户只需要指定的…

Python青少年简明教程:函数

Python青少年简明教程&#xff1a;函数 在Python中&#xff0c;函数是一段可重用的代码块&#xff0c;用于执行特定的任务。函数能够接受输入参数&#xff0c;执行特定的操作&#xff0c;并返回结果。 Python提供了许多内置函数&#xff0c;如print()、len()、eval()等&#x…