【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构

news2024/11/14 9:01:36

在这里插入图片描述

文章目录

  • 一、移除链表元素
    • 思路一
    • 思路二
  • 二、合并两个有序链表
    • 思路:
    • 优化:
  • 三、反转链表
    • 思路一
    • 思路二
  • 四、链表的中间节点
    • 思路一
    • 思路二
  • 五、综合应用之链表的回文结构
    • 思路一:
    • 思路二:

一、移除链表元素

题目链接:https://leetcode.cn/problems/remove-linked-list-elements/

我们先来看看题目描述和第一个示例:
在这里插入图片描述
   根据题目描述我们就可以大致明白题意,就是将一个链表中的某个值的节点删除,然后返回新链表的头结点,然后题目要我们实现的函数给了我们头结点,以及要删除的数据,我们要把相应的节点删除

思路一

   首先最简单的思路就是,我们可以通过之前实现的链表的方法用上,首先使用Find方法找到对应的值,然后使用Erase方法删除,直到Find方法返回空指针结束
   由于这个方法思路比较好实现,这里就不再赘述了,可以自己尝试一下,我们的关键是更优方法的思路二

思路二

   这个题其实跟我们在刷顺序表题的时候遇见类似的,只不过之前要删除的是数组中的元素,这道题是删除链表节点,不过本质上是相同的,上次我们使用了双指针,这次我们还是可以使用双指针,顺序表刷题参考:【初阶数据结构与算法】沉浸式刷题之顺序表练习(顺序表以及双指针两种方法)
   具体思路也很像之前的那个题,题目让返回新链表的头结点,没有说必须是原链表的头结点,所以我们可以新建一个链表,如果遍历到原链表中节点的值不是题目给定的值,也就是不是我们要删除的节点,那么我们就把它尾插到新链表
   我们要注意的是,如果遇到了要插入的节点,但是新链表的头为空,我们就要让新链表的头和尾都指向这个节点,其它情况就正常尾插
   还有一个重要的地方就是,当我们把链表移动完毕之后,新链表的尾结点可能还指向原链表的节点,我们要把它置为空,题解如下:

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{
    ListNode* newhead, *newtail;
    newhead = newtail = NULL;
    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val != val)
        {
            if(newhead == NULL)
            {
               newhead = newtail = pcur;
            }
            else
            {
                newtail->next = pcur;
                newtail = pcur;
            }
        }
        pcur = pcur->next;
    }
    if(newtail)
      newtail->next = NULL;
    return newhead;
}

在这里插入图片描述

二、合并两个有序链表

题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/

我们先来看看题目的描述和第一个示例:

在这里插入图片描述
   题目给我们两个有序链表,要求我们把这两个链表合并成一个新的有序链表,然后返回它的头结点

思路:

   这个问题是不是有点似曾相识,跟我们之前的合并有序数组是一样的,我们当时的方法就是使用双指针,只是合并有序数组时是要求我们在第一个数组中进行修改,不能新建一个数组返回
   但是这道题还要简单一些,它可以新建一个链表,我们可以通过双指针遍历要合并的链表,比较两个链表中节点的大小,谁小谁就尾插到新链表,直到有一个链表走到空就停止循环
   但是我们要注意的一点是,虽然有一个链表走到空了,也就是一个链表中的节点都插入到新链表了,但是另一个链表可能还有节点,所以我们要判断一下,如果两个链表中还有一个链表不为空,那么直接将它的所有节点尾插到新链表
   还有就是做一个特殊处理,因为两个链表中可能有空链表,上面的方法就跑不通,所以我们判断一下,如果有一个链表为空,那么直接返回另一个链表,题解如下:

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, *pcur2;
    ListNode* newhead, *newtail;
    pcur1 = list1;
    pcur2 = list2;
    newhead = newtail = NULL;
    while(pcur1 && pcur2)
    {
        if(pcur1->val < pcur2->val)
        {
            if(newhead == NULL)
            {
                newhead = newtail = pcur1;
            }
            else
            {
                newtail->next = pcur1;
                newtail = pcur1;
            }
            pcur1 = pcur1->next;
        }
        else
        {
            if(newhead == NULL)
            {
                newhead = newtail = pcur2;
            }
            else
            {
                newtail->next = pcur2;
                newtail = pcur2;
            }
            pcur2 = pcur2->next;
        }
    }
    if(pcur1)
    {
        newtail->next = pcur1;
    }
    if(pcur2)
    {
        newtail->next = pcur2;
    }
    return newhead;
}

在这里插入图片描述

优化:

   上面的代码是一种题解,但是我们可以发现,这个代码写起来有点麻烦,有一些重复的动作,就是在每次插入之前,我们要判断链表是否为空,如果为空要让新链表的头和尾都指向要插入的节点
   那我们能不能让代码更加简洁一点呢?就是不必每次插入节点前都判断链表是否为空,这里就可以用上我们之前学过的带头的概念,我们申请一个不保存数据的哨兵位当作链表默认的头
   这样我们的新链表默认就有了一个节点,不为空了,可以直接在哨兵位后面尾插节点,不用判断链表是否为空,最后返回时就返回哨兵位的下一个节点就可以了,它就是我们新链表中保存数据的头节点
   不过由于我们的哨兵位是通过malloc来的,所以最后代码结束时不要记得把它释放掉,以免造成内存泄漏,如下:

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, *pcur2;
    pcur1 = list1, pcur2 = list2;
    ListNode* newhead, *newtail;
    newhead = newtail = (ListNode*)malloc(sizeof(ListNode));
    while(pcur1 && pcur2)
    {
        if(pcur1-> val < pcur2->val)
        {
            newtail->next = pcur1;
            newtail = pcur1;
            pcur1 = pcur1->next;
        }
        else
        {
            newtail->next = pcur2;
            newtail = pcur2;
            pcur2 = pcur2->next;
        }
    }
    if(pcur1)
    {
        newtail->next = pcur1;
    }
    if(pcur2)
    {
        newtail->next = pcur2;
    }
    ListNode* ret = newhead->next;
    free(newhead);
    newhead = NULL;
    return ret;
}

在这里插入图片描述

三、反转链表

题目链接:https://leetcode.cn/problems/reverse-linked-list/

我们来看看题目描述和第一个示例:
在这里插入图片描述
   题目要求我们将给出的链表反转,就是改变指针的指向,让原本的尾节点变成头,让原本的头结点变成尾

思路一

   思路一还是很简单,就是我们创建一个新链表,遍历原链表,拿原链表中的节点头插到新链表就可以了,如图:
在这里插入图片描述
在这里插入图片描述
   有了上图的分析,实现就很简单了,只需要一个头插方法,我们之前讲过,这里就不再赘述了,可以自己试试,我们重点介绍思路二

思路二

   思路二比较难想出来,但是确实非常快,因为它是对原链表的节点的指针指向进行修改,所以很快,并且不会消耗什么空间,思路如图:
在这里插入图片描述
在这里插入图片描述
   有了上面的思路我们就可以来写代码了,但是我们要注意一个点,就是如果题目直接给出一个空链表让我们反转,那么我们对它解引用就会出错,所以我们特殊处理一下,如果链表为空就直接返回,空链表反转还是空链表,题解如下:

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

在这里插入图片描述

四、链表的中间节点

题目链接:https://leetcode.cn/problems/middle-of-the-linked-list/

我们来看看题目描述和两个示例,如下:
在这里插入图片描述
   它的要求就是让我们返回链表的中间节点,如果是偶数个节点,那么就有两个中间节点,则返回后一个节点

思路一

   我们首先能想到的思路就是,先遍历整个链表,看看链表一共有多少个节点,然后让它除以2,最后再次循环遍历链表就可以找到中间节点,这个题很简单,我们直接给出题解,如下:

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) 
{
    int count = 0;
    ListNode* pcur = head;
    while(pcur)
    {
        count++;
        pcur = pcur->next;
    }
    count /= 2;
    pcur = head;
    while(count--)
    {
        pcur = pcur->next;
    }
    return pcur;
}

在这里插入图片描述

   虽然这个方法看起来已经很简单了,但是始终都是执行了两次循环,有没有更简单的方法呢?接下来我们来看看思路二

思路二

   思路二的方法很绝妙,简单又快捷,就是使用快慢指针的算法,快慢指针默认都指向头结点,慢指针一次走一步,快指针一次走两步,那么等快指针走到空的时候,慢指针指向的节点就是中间节点
   为什么呢?因为快指针每次走的距离都是慢指针的2倍,最后统计一共走的距离时,快指针走的总距离也是慢指针的2倍,而快指针走到了空,也就说明走到了链表尾,那么此时慢指针就是它的一半,刚好指向中间节点,题解如下:

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) 
{
    ListNode* slow, *fast;
    slow = fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

在这里插入图片描述

五、综合应用之链表的回文结构

题目链接:https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

我们先来看看题目描述和它的第一个示例:
在这里插入图片描述
   题目要求我们判断给出的链表是否是一个回文结构,也就是是否前后对称,这道题就可以用上我们之前做题写出的代码,具体后面再说,我们先解决一个事情
   就是这个题目没有提供C语言的选项,那我们就选择C++来做,C++是兼容C语言的,主要是我们要知道在哪里写代码,如图:
在这里插入图片描述

   这是C++中的类,但是不影响我们做题,我们只需要知道我们把代码写在哪里,在题目中也有提示,把代码写在紫色大括号内即可,其它的可以不管,还有一个就是,C++对结构体做了优化,可以在使用结构体时不必加上struct

思路一:

   虽然判断链表是否是回文结构很难,但是我们可以把链表中的数据存放到数组中,判断数组是否是回文结构,这个就比较简单了
   由于链表两边的数据是对称的,所以我们定义一个left和right分别指向数组的头和尾,然后对比它们的值,如果不同直接返回假,否则的话就一直让它们往中间走,直到left不再小于right
   在循环过程中,一旦left所在位置的值和right所在位置的值不相同,就说明并不对称,也就不是回文结构,返回假,一旦循环结束,说明左右对称,就是回文结构,直接返回真
   并且我们注意到,虽然题目要求空间复杂度为O(1),但是同时又给出了链表的最大节点个数不超过900,那定义一个901个元素大小的数组时间复杂度还是O(1),因为它始终还是常数个空间,如下:

class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) 
    {
        int arr[901] = { 0 };
        ListNode* pcur = A;
        int i = 0;
        while(pcur)
        {
            arr[i++] = pcur->val;
            pcur = pcur->next;
        }
        int left = 0, right = i-1;
        while(left < right)
        {
            if(arr[left] != arr[right])
            {
                return false;
            }
            left++, right--;
        }
        return true;
    }
};

在这里插入图片描述
   最后就是,这个方法虽然很简单,但是只适合给出了链表节点大小的题目,如果遇到没有给出链表节点大小的题目就会导致时间复杂度变成O(N),导致不符合要求,所以我们再介绍一个方法

思路二:

   这个思路可以帮我们复习上面做过的题,让我们能够灵活运用知识,具体思路就是,我们首先通过找中间节点的函数找到链表中间节点,然后从中间节点开始,将后面的节点反转,形成一个新链表,然后再和原链表进行比较即可,如图:
在这里插入图片描述
   找中间节点的函数和反转链表的函数可以从我们之前做过的题里面拿过来用,当然也可以自己根据这个逻辑把中间的代码实现,这里我就直接把之前写过的函数直接拿过来用,如下:

class PalindromeList {
  public:
    struct ListNode* reverseList(struct ListNode* head) {
        if (head == NULL) {
            return head;
        }
        ListNode* n1, *n2, *n3;
        n1 = NULL;
        n2 = head;
        n3 = head->next;
        while (n2) {
            n2->next = n1;
            n1 = n2;
            n2 = n3;
            if (n3)
                n3 = n3->next;
        }
        return n1;
    }

    struct ListNode* middleNode(struct ListNode* head) {
        ListNode* slow, *fast;
        slow = fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    bool chkPalindrome(ListNode* A) 
    {
        if(A == NULL)
        {
            return true;
        }
        ListNode* mid = middleNode(A);
        ListNode* newlist = reverseList(mid);
        ListNode* pcur1, *pcur2;
        pcur1 = A, pcur2 = newlist;
        while(pcur2)
        {
            if(pcur1->val != pcur2->val)
            {
                return false;
            }
            pcur1 = pcur1->next;
            pcur2 = pcur2->next;
        }
        return true;
    }
};

在这里插入图片描述

   那么今天的链表刷题训练就到这里结束啦,有什么不懂欢迎提出,我们下一篇文章还是刷题,之后我们会讲栈和队列的实现,敬请期待
   bye~

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

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

相关文章

三维测量与建模笔记 - 特征提取与匹配 - 4.1 梯度信息提取

上面说的“可关联性”&#xff0c;举一个例子&#xff0c;比如我们拍摄一个凹凸不平的金属表面&#xff0c;在某个角度拍的时候&#xff0c;从图像中可以看到这些凹凸不平的地方&#xff0c;但是换个角度&#xff08;或者光照发生变化&#xff09;拍就看不到了。这样的特征点就…

显示微服务间feign调用的日志

第一步 package com.niuniu.common.config;import com.niuniu.common.CommonConstant; import com.niuniu.common.utils.UserContext; import feign.Logger; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.…

读取文件内容、修改文件内容、识别文件夹目录(Web操作系统文件/文件夹详解)

前言 因 Unicode IDE 编辑器导入文件、文件夹需要&#xff0c;研究了下导入文件/文件夹的功能实现&#xff0c;发现目前相关文章有点少&#xff0c;故而记录下过程&#xff0c;如果有误&#xff0c;还望指正。(API的兼容性及相关属性、接口定义&#xff0c;请自行查看文件系统 …

「Mac玩转仓颉内测版2」入门篇2 - 编写第一个Cangjie程序

本篇详细介绍在Mac系统上创建首个Cangjie项目并编写、运行第一个Cangjie程序的全过程。内容涵盖项目创建、代码编写、程序运行与调试&#xff0c;以及代码修改后的重新运行。通过本篇&#xff0c;掌握Cangjie项目的基本操作&#xff0c;进一步巩固开发环境的配置&#xff0c;迈…

微信小程序_小程序视图与逻辑_day3

一、目标 A. 能够知道如何实现页面之间的导航跳转 B. 能够知道如何实现下拉刷新效果 C. 能够知道如何实现上拉加载更多效果 D. 能够知道小程序中常用的生命周期 二、目录 A. 页面导航 B. 页面事件 C. 生命周期 D. WXS脚本 E. 案例-本地生活&#xff08;列表页面&#xff09;…

Threejs 材质贴图、光照和投影详解

1. 材质和贴图 材质&#xff08;Material&#xff09;定义了物体表面的外观&#xff0c;包括颜色、光泽度、透明度等。贴图&#xff08;Textures&#xff09;是应用于材质的图像&#xff0c;它们可以增加物体表面的细节和真实感。 1.1材质类型 MeshBasicMaterial&#xff1a…

论文概览 |《Sustainable Cities and Society》2024.11 Vol.115(下)

本次给大家整理的是《Sustainable Cities and Society》杂志2024年11月第115期的论文的题目和摘要&#xff0c;一共包括76篇SCI论文&#xff01;由于论文过多&#xff0c;我们将通过两篇文章进行介绍&#xff0c;本篇文章介绍第31--第76篇论文! 论文31 The effect of urban fo…

【问卷调研】HarmonyOS SDK开发者社区用户需求有奖调研

问卷请点击&#xff1a;HarmonyOS SDK开发者社区用户需求有奖调研

【3D Slicer】的小白入门使用指南五

心脏CT多组织半自动分割(解说版) 模块勾选的是converters下的crop volume 右侧移动滑块+移动边框,移动位置找到自己关注的区域即可,然后点击apply(其他参数也要设置的和图片一致) 模块勾选segment editor,并创建心脏多个不同分割位置(默认下不同位置自动分配了不同的颜…

丹摩征文活动|FLUX.1 和 ComfyUI:从部署到上手,轻松驾驭!

FLUX.1 和 ComfyUI&#xff1a;从部署到上手&#xff0c;轻松驾驭&#xff01; FLUX.1历史曲线 黑森林实验室推出了一款名为FLUX.1的先进图像生成模型&#xff0c;根据不同用户需求&#xff0c;提供了三种独特的版本。 FLUX.1-pro&#xff1a;作为专为企业打造的强大闭源版本…

自动驾驶合集(更新中)

文章目录 车辆模型控制路径规划 车辆模型 车辆模型基础合集 控制 控制合集 路径规划 规划合集

string------1

文章目录 一. STL1.概念2.版本 二. string类2.1 为什么学习string类2. 标准库中的string类2.2.1 构造&#xff08;7个&#xff09;2.2.2 对string类对象进行“访问和修改”&#xff08;1&#xff09;operator[]&#xff08;2&#xff09;迭代器1.迭代器的使用2.迭代器的价值&am…

开源 2 + 1 链动模式、AI 智能名片、S2B2C 商城小程序在用户留存与品牌发展中的应用研究

摘要&#xff1a;本文以企业和个人品牌发展中至关重要的用户留存问题为切入点&#xff0c;结合管理大师彼得德鲁克对于企业兴旺发达的观点&#xff0c;阐述了用户留存对品牌营收的关键意义。在此基础上&#xff0c;深入分析开源 2 1 链动模式、AI 智能名片、S2B2C 商城小程序在…

蓝桥杯每日真题 - 第8天

题目&#xff1a;&#xff08;子2023&#xff09; 题目描述&#xff08;14届 C&C B组A题&#xff09; 解题思路&#xff1a; 该代码通过动态计算包含数字 "2023" 的子序列出现次数。主要思路是&#xff1a; 拼接序列&#xff1a;将1到2023的所有数字按顺序拆分…

Mac Nginx 前端打包部署

安装homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 安装Nginx brew install nginx nginx相关命令 nginx启动命令&#xff1a;nginx nginx -s reload #重新加载配置 nginx -s reopen #重启 nginx -s stop #…

小试银河麒麟系统OCR软件

0 前言 今天在国产电脑上办公&#xff0c;需要从一些PDF文件中复制文字内容&#xff0c;但是这些PDF文件是图片转换生成的&#xff0c;不支持文字选择和复制&#xff0c;除了手工输入&#xff0c;我们还可以使用OCR。 1 什么是OCR OCR &#xff08;Optical Character Recogni…

选择主动孤独,亦可以成长和放松

有的人热衷于喧嚣&#xff0c;享受来自社交场合的灯火辉煌&#xff0c;将欢声笑语作为心灵的慰藉。有的人则偏爱那份宁静&#xff0c;更愿意在青灯古巷间徘徊&#xff0c;在山川湖海间独行&#xff0c;以一杯茶、一卷书、一段旅程与自我对话。每个人以不同的方式诠释活着的意义…

Django 外键引用另一个表中的多个字段

在 Django 中&#xff0c;外键&#xff08;ForeignKey&#xff09;通常只引用另一张表的一个字段&#xff0c;比如一个主键或一个唯一标识字段。然而&#xff0c;如果我们需要让一个外键引用另一张表中的多个字段&#xff0c;通常有以下几种方法来实现这种关系。 1、问题背景 …

MyBatis从入门到进阶

目录 MyBatis入门1、创建项目、数据准备2、数据库配置3、编写持久层代码单元测试打印日志 基本操作查询数据插入数据删除数据更新数据 MyBatis - xml插入数据更新数据删除数据查询数据#{}与${}SQL注入排序like查询 MyBatis进阶if标签trim标签where标签set标签foreach标签sql标签…

【JavaWeb】JavaWeb入门之XML详解

目录 1.XML介绍 1.1.XML概述 1.1.1.什么是XML 1.1.2.XML的作用 1.1.3.XML与HTML的比较 1.1.4.XML和properties&#xff08;属性文件&#xff09;比较 1.1.5.W3C组织 1.2.XML语法概述 1.2.1.XML文档展示 1.2.2.XML文档的组成部分 1.3.XML文档声明 1.3.1.什么是XML文…