【数据结构】链表OJ第一篇 —— 移除链表元素 反转链表 合并两个有序链表

news2024/11/28 18:48:39

文章目录

  • 0. 前言
  • 1. 移除链表元素
  • 2. 反转链表
  • 3. 合并两个有序链表
  • 4. 结语

0. 前言

上篇博客中,我们学习了实现了单链表。但是仅仅实现并不算掌握,所以我们需要做些题目来练习巩固。而从今天开始的几期,anduin 都会为大家带来链表OJ题,并配上详细题解和图解,来帮助大家更好的理解。今天为大家带来的是 移除链表元素、反转链表、合并两个有序链表 三道题目。话不多说,我们这就开始。

1. 移除链表元素

链接:203. 移除链表元素

示例1

img

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例2

输入:head = [], val = 1
输出:[]

示例3

输入:head = [7,7,7,7], val = 7
输出:[]

提示

  • 列表中的节点数目在范围 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

思路1(精讲)

考察的为单链表元素的删除。

遍历链表,用变量 prev 记录遍历链表时每个节点的上一个节点。变量 cur 为本次节点。

如果第一个节点的值域和 val 相等,为 头删 情况,此时记录本节点的下一个节点,将头变为本节点的下一个节点,释放本节点,迭代 cur 。

如果节点的值和 val 相等且 不为头删 情况,那么将 prev 的 next 和 cur 的 next 链接(prev->next = cur->next),然后释放 cur 节点,将cur变为 prev 的next,也就是之前当前节点的下一个节点。

如果这两个条件不满足,则将 prev 和 cur 迭代向后走。

最后返回 head 头。

image-20221020094609467

image-20221020094239694

image-20221020091123666

思路2(精讲)

尾插法。

总体思路是这样的,遍历链表,将节点的值不为 val 的节点尾插到新链表中,如果等于 val 就迭代往后走,直到链表为空。

但是尾插需要特殊考虑空链表尾插的情况,实际上写起来还是比较烦的,所以这时我们就可以用到上篇博客中提到的哨兵位(哑节点)。

先给定一个 cur 作为遍历原链表的指针。

我们先动态开辟一个哨兵位 dummy 。每次尾插时,都需要找到尾部,那么再给定一个 tail 让其等于 dummy 。这样改变 tail 的链接的时候,也就相当于改变了新链表。

接下来就是常规操作,使用 cur 遍历链表,如果 cur 对应节点的值不等于 val ,那么就对新链表进行尾插,这时由于链表一定不为空,于是直接将 tail->next 链接为 cur 即可,再使 tail 、cur 向后迭代。

如果 cur 对应节点的值等于 val ,那么就说明这为需要删除的节点,那么先拷贝 cur 的下一个节点,再释放该节点,再cur 迭代往后走。

注意:如果 tail 最后链接的节点不是原链表的最后一个节点,那么 tail->next 还链接着原链表,而一旦原链表的节点已经被释放,这就导致了野指针。所以需要断开链接,链到空(tail->next = NULL)。

image-20221105131515173

image-20221105131635708

2. 反转链表

链接:206. 反转链表

描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例1

img

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例2

img

输入:head = [1,2]
输出:[2,1]

示例3

输入:head = []
输出:[]

思路1(精讲)

迭代法。

我们想象一下,原先链表的第一个节点链接着第二个节点,那我们是否可以将第二个节点链接到第一个节点?再将其他元素逐次链接,第一个节点链接到空指针。就像这样:NULL ← ① ← ②

这当然可以,因为这就是当前思路的主要方法。

我们需要三个变量,n1 记录上一个节点, n2 记录当前节点,n3记录下一个节点。

然后通过 n2 遍历链表,将链表节点逐个反转,然后利用 n2 使 n1 迭代到当前节点的位置,利用 n3 使 n2 迭代到下一个节点,这两个步骤的次序一定不能乱。上面两个步骤完成后,如果 n3 不为 NULL ,那么让 n3 也迭代到它的下一个节点处。

最后返回 n1 就是反转后的链表。

注意点:空链表需要特判。

image-20221020103203200

image-20221020103245288


思路2(精讲)

头插法。

给定 cur 用来遍历链表,newhead为链表的新头。

遍历链表,记录 cur 的下一个节点(cur->next),然后将这个节点头插到 newhead前,将 newhead 赋值为 cur,到最后newhead 就是之前链表的最后一个节点,返回即可。

image-20221020110821736

image-20221020103503192

3. 合并两个有序链表

链接:21. 合并两个有序链表

描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例1

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例2

输入:l1 = [], l2 = []
输出:[]

示例3

输入:l1 = [], l2 = [0]
输出:[0]

提示

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1l2 均按 非递减顺序 排列

思路(精讲)

我们之前做过一道题目,叫做合并两个有序数组。其中有一种方法是创建一个新数组,然后遍历两个数组,将两个数组的较小的元素放置到新数组中。一个数组遍历完,将没有放置的元素放置到新数组中,然后拷贝回原数组。

那么我们这道题能否借鉴它的思路?

我们这道题为合并两个有序链表,链表和数组不同,数组形式的一种方法需要我们创建一个新数组。但是对于链表而言我们可以通过指针改变链接关系,所以我们不需要创建新链表,只需要修改即可。

有序数组的做法是将较小元素逐个尾插新数组中,那么我们也可以将较小元素尾插链表中呀。

但是尾插就有两个问题,当尾插的时候,我们需要找链表的尾,而且当链表为空时,需要特殊处理。

为了避免每次找链表的尾,那么我们就给定一个 tail,这样只要将 tail 迭代就可以。

那么链表为空如何处理?难道在循环中特判,然后每次迭代的时候都判断一次,那也太麻烦了,而且也会代码冗余。

那么这时就又要用到哨兵位(头结点)了,我们给一个哨兵位 head,它也不存储数据,那么不就可以了?但是注意有效数据从 head->next 开始。

注意:哨兵位需要释放,否则会造成内存泄漏。

image-20221020140742086

image-20221020132239703

代码

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* head = NULL, *tail = NULL;
    if (list1 == NULL)
    {
        return list2;
    } 
    if (list2 == NULL)
    {
        return list1;
    }
    // 哨兵位
    // 这里 tail 也需要动态开辟一下
    // 因为不在迭代时进行第一次插入的处理
    // tail 一开始为空指针,会报错
    head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));   
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            tail->next = list1; // 当前结点链表的尾链接到 list1
            tail = list1; // 链表的尾变成 list1
            list1 = list1->next; // list1 并没有改变,list1 迭代到list1的下一个节点
        }
        else
        {
            tail->next = list2;
            tail = list2;
            list2 = list2->next;
        }
    }
    // 未放置完的元素
    // 这里和数组的完全不一样
    // 链表是串联的,所以只需要把当前节点给到tail->next
    // 就可以全部串联
    if (list1)
    {
        tail->next = list1;
    }
    if (list2)
    {
        tail->next = list2;
    }
    // 释放哨兵位
    struct ListNode* ans = head->next;
    free(head); 
    return ans;
}

这道题目不使用哨兵位也能写,但是使用这种方法时,需要处理一下链表第一次合并时尾插的情况,大体思路和带哨兵位差不多,只是需要注意一下细节,这里我就不多赘述了,大家可以自己试试,下面贴下截图和代码:

image-20221105203907712

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


struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;
    struct ListNode *head, *tail;
    head = tail = NULL;
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            // 第一次合并
            if (tail == NULL)
            {
                head = tail = list1;
            }
            else
            {
                tail->next = list1;
                tail = tail->next;
            }
            list1 = list1->next;
        }
        else
        {
            // 第一次合并
            if (tail == NULL)
            {
                head = tail = list2;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
            }
            list2 = list2->next;
        }
    }
    if (list1)
        tail->next = list1;
    if (list2)
        tail->next = list2;
    return head;
}

4. 结语

到这里,本篇博客就到此结束了,建议大家在看完博客后,再下去写一写。从 理解 → 独立想出思路 → 画图 → 完整实现代码。会写一道题目不是在看完题解后,因为记住了代码,默写下来;而是能自己想出思路,能画出图,并自我实现,这样才算掌握。多写多练多画图,才能学好数据结构。

下篇博客我会继续更新链表的OJ题,我们敬请期待~

如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!

我是anduin,一名C语言初学者,我们下期见!

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

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

相关文章

在Linux环境下VScode中配置ROS、PCL和OpenCV开发环境记录

一.安装必要的插件 打开VScode&#xff0c;在开展中安装CMake、CMake Tools&#xff0c;ROS和catkin-tools插件&#xff0c;截图如下&#xff0c;安装后重新打开VScode插件生效。 二.创建ROS工作空间 在选择的路径下&#xff0c;打开终端创建工作空间&#xff0c;具体命令如下…

【概率论笔记】正态分布专题

文章目录一维正态分布多维正态分布n维正态分布二维正态分布一维正态分布 设X~N(μ,σ2)X\text{\large\textasciitilde}N(\mu,\sigma^2)X~N(μ,σ2)&#xff0c;则XXX的概率密度为f(x)12πσe−(x−μ)22σ2f(x)\frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}}f(…

WXML模板语法

文章目录1、数据绑定1.1 数据绑定的基本原则1.2在data中定义页面的数据1.3 Mustache语法的格式1.4 Mustache语法的应用场景1.5 算数运算2、事件绑定2.1 小程序常用的事件2.2事件对象的属性列表2.3 target和currentTarget的区别2.4 <font colorred>bindtap的语法格式2.5 在…

狗厂员工来面试本想难为一下,结果被虐得连console.log也不敢写了

这次说到的面试题是关于node服务端内存溢出的问题&#xff0c;狗厂员工来面试本想难为一下&#xff0c;现在我连console.log也不敢写了 关于这道node内存溢出的问题&#xff0c;大哥从以下几个方面讲的&#xff0c;讲完我觉得自己得到了升华&#xff0c;现在搞得连代码也快不敢…

2.24 OrCAD Cadence16.6怎么更改原理图中做好的库文件?

笔者电子信息专业硕士毕业&#xff0c;获得过多次电子设计大赛、大学生智能车、数学建模国奖&#xff0c;现就职于南京某半导体芯片公司&#xff0c;从事硬件研发&#xff0c;电路设计研究。对于学电子的小伙伴&#xff0c;深知入门的不易&#xff0c;特开次博客交流分享经验&a…

FPGA代码设计规范一些探讨

代码设计规范的重要性 经过一段的工作积累已经慢慢进入了提高和进阶的阶段&#xff0c;在这篇博客里多聊一聊在现实工作中的话题&#xff0c;比如代码规范以及如何尽快接手前人代码&#xff0c;快速定位项目问题。 显然每个FPGA工程师的设计理念和代码风格很多情况下有一些差别…

python 基于PHP+MySQL的学生成绩管理系统

学生成绩管理是每一个学校都会面临的一个问题,传统的管理模式已经明显到和时代不同步。通过我对当前学校成绩管理的需求和自己的实习经验整理出了一个能够满足大多数学校的学生成绩管理系统。本系统分为管理人员,教师和学生三种用户,每种用户各负责其一部分功能然后通过他们的整…

Web前端开发基础教程二

注释和特殊字符&#xff1a; 如果需要在html文档添加一些便于阅读和理解但又不需要显示在页面中的注释文字&#xff0c;就需要使用注释标签。 html中的注释以“<!--”开头&#xff0c;以“-->”结束或者快捷键&#xff1a;Ctrl/。 举例&#xff1a; <!-- 我想放假 …

【实战】Mysql 千万级数据表结构变更 、含脚本

一、实测结果 业务无感知&#xff0c;无死锁平滑 线上800万数据以下 直接使用 alter 新增字段 300ms左右 2000万数据&#xff0c;强制使用主键索引&#xff0c;每次查询50万数据 并插入新表 &#xff0c;耗时 20s &#xff0c;cpu 占45% 二、整体步骤 创建新表 biz_table_ne…

Vue-脚手架的创建

本篇vue3的脚手架主要是使用vue-cli进行创建&#xff0c;有网的情况下才能创建成功 文章目录一、下载node.js二、全局安装vue/cli三、使用vue-cli创建项目3.1 使用vscode打开终端3.2 创建项目3.3 创建成功四、注意事项一、下载node.js 1、打开node的官网 node官网 2、点击下方图…

Oxygen XML Editor 25.0.X Crack

XML 编辑器 Oxygen XML Editor 是完整的 XML 编辑解决方案&#xff0c;适用于 XML 开发人员和内容作者。它提供了必备的 XML 编辑工具&#xff0c;涵盖了大多数 XML 标准和技术。Oxygen XML Editor 包括 Oxygen XML Developer 和 Author 的所有功能。 特点与技术 单一来源出版 …

【树莓派不吃灰】使用frp内网穿透,实现远程访问树莓派

目录1. 前言2. frp内网穿透2.1 概述2.2 实现原理3. 开源frp项目4. 公网服务器选型5. 下载frp软件5.1 公网服务器下载frp服务器5.1.1 github选择适合服务端的版本5.1.2 公网服务器进行下载解压5.2 树莓派下载frp客户端5.2.1 github选择适合客户端的版本5.2.2 树莓派进行下载解压…

流形上的预积分(上)

预积分和流形 论文&#xff1a;IMU Preintegration on Manifold for Effificient Visual-Inertial Maximum-a-Posteriori Estimation 引言 Recent results in monocular visual-inertial navigation (VIN) have shown that optimization-based approaches outperform filteri…

免费体验CSDN云IDE使用指南

云IDE产品介绍 云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间啦~ 官方活动入口 文章目录1.免费体验CSDN云IDE使用指南1.1云IDE优点2.自己的代码在云IDE上跑起来看看如何操作2.1 克隆开源仓库2.2 创建一个空工作空间2.3 使用默认模板代…

Thread类中run和start的区别

一.认识 Thread类 中的 start() 和 run() 首先来看一张jdk-api中对于start 和 run 的描述. 二.方法的区别 下面我们简单看一下代码: // 继承Thread类 重写run() class MyThread extends Thread {Overridepublic void run() {while (true) {System.out.println("thread&q…

如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)

以下只是做简单的演示、大致实现的效果。页面效果需要进一步优化。目的是提供思路 视频效果&#xff1a; 首页商品跳转1、需求 1、首页的的商品来自接口数据2、在首页点击某一个商品跳转到更加详细的商品介绍页面&#xff08;可以购买、加入购物车、查看商品评价信息等&#x…

频频出现的DevOps到底是什么呢?浅浅了解下什么是DevOps吧

文章目录 &#x1f4d1;前言 &#x1f9e9;DevOps的概念和起源 &#x1f4f0;DevOps的概念 &#x1f4f0;DevOps的起源与发展 &#x1f4f0;总结 &#x1f9e9;DevOps的发展 &#x1f4f0;发展过程速览 &#x1f4f0;发展现状 &#x1f4f0;未来发展 &#x1f4c4;以…

【前端】JavaScript数据类型

目录 一、数据类型简介 二、简单数据类型 数字型Number 1.数字型进制 2.数字型范围 3.数字型三个特殊值 4.isNaN() 字符串型String 1.字符串引号嵌套 2.字符串转义符 3.字符串长度 4.字符串拼接 布尔型Boolean Undefined和Null 三、获取变量数据类型 四、数据类型…

res.add(new ArrayList<>(path))和res.add(path)的区别

一.问题描述 在链表path里面添加值&#xff0c;之后把path链表添加到res链表中&#xff0c;自己做的时候使用res.add(path)&#xff0c;结果发现出现解答错误。 题目链接&#xff1a;113. 路径总和 II - 力扣&#xff08;LeetCode&#xff09; 代码如下&#xff1a; class …

网络初始之网络协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 目录 文章目录 前言 1.局域网&#xff1a; 概念&#xff1a; 构建方式 2.广域网&#xff1a; 3.IP地址&#xff1a; 4. 端口号&#xff1a; 常考点&#xff1a; 一、…