剑指offer----C语言版----第十三天

news2024/11/17 16:44:54

目录

1. 删除链表的节点

1.1 题目描述

1.2 Leetcode解题的思路一(双指针)

1.3 Leetcode解题的思路二(单指针)

1.4 剑指offer上的原题


1. 删除链表的节点

原题链接:
剑指 Offer 18. 删除链表的节点 - 力扣(LeetCode)icon-default.png?t=MBR7https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/

1.1 题目描述

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。

1.2 Leetcode解题的思路一(双指针)

首先根据题目我们知道该链表是一个单向链表,在遍历链表找到值等于val的节点后无法向前找到该节点的前一个节点,因此我们维护两个指针prev,和cur,用指针prev指向正在遍历链表的指针cur的前一个节点,当指针cur指向的val等于要删除的val值时结束遍历。然后将指针prev指向的节点的next赋值为cur的下一个节点,最后释放掉cur,cur置为空指针。

这样做显然有一个严重的问题,如果要删除的节点就是头结点,那么我们让prev->next指向cur->next就会使得空指针解引用,引起程序崩溃,对此情况我们需要单独进行处理。

时间复杂度:O(N),空间复杂度:O(1)。

struct ListNode* deleteNode(struct ListNode* head, int val){
    //如果删除的是头结点,单独处理
    if(head->val == val)
    {
        //找到头结点的下一个节点/NULL
        struct ListNode* next = head->next;
        //释放掉删除的节点
        free(head);
        //防止野指针
        head = NULL;
        //返回新的头结点
        return next;
    }
    else
    {
        //前驱指针,
        struct ListNode* prev = NULL;
        //遍历指针
        struct ListNode* cur = head;
        //找到要被删除的节点值
        while(cur->val!=val)
        {
            prev = cur;
            cur = cur->next;
        }
        //链接新的节点
        prev->next = cur->next;
        free(cur);
        cur = NULL;
        return head;
    }
    
}

1.3 Leetcode解题的思路二(单指针)

上面的解题思路需要一个前驱指针,那是不是一定需要得到被删除节点的前一个节点呢?显然没这个必要我们只需要找到要删除节点的前一个节点即可,然后再删除连接。这种思路其实跟上面的解法本质上是一样的,为了方便我们就把他当作另一种解法吧。这种方法也需要单独处理删除头结点的情况。

时间复杂度:O(N),空间复杂度:O(1)。

struct ListNode* deleteNode(struct ListNode* head, int val) 
{
    //删除头结点单独处理
    if (head->val == val)
    {
        struct ListNode* next = head->next;
        free(head);
        return next;
    }
    else
    {
        //一个指针
        struct ListNode* cur = head;
        //如果遍历到的节点的下一个节点的val等于删除的值,停止循环
        while (cur->next->val != val)
        {
            
            cur = cur->next;
        }
        //保存节点并连接
        struct ListNode* f_next = cur->next;
        struct ListNode* s_next = f_next->next;
        free(f_next);
        cur->next = s_next;
        return head;
    }
}

1.4 剑指offer上的原题

Leetcode上也注明了对原题有改动,那咱就来看看原题吧:题目大体相同,只是传入的参数是要删除节点的指针。如果是这样传参的话,我们就能做到在O(1)的时间复杂度删除节点。

如果我们把下一个节点的内容复制到需要删除的节点上覆盖原有的内容,再把下一个节点

删除,那是不是就相当于把当前需要删除的节点删除了?

我们要删除节点i,先把i的下一个节点j的内容复制到i,然后把i的指针指向节点j的下一个节点。此时再删除节点j,其效果刚好是把节点i删除了。

上述思路还有一个问题:如果要删除的节点位于链表的尾部,那么它就没有下一个节点,怎么办?我们仍然从链表的头节点开始,顺序遍历得到该节点的前序节点,并完成删除操作。

最后需要注意的是,如果链表中只有一个节点,而我们又要删除链表的头节点(也是尾节点),那么,此时我们在删除节点之后,还需要把链表的头节点设置为 nullptr。

这种思路的平均时间复杂度是O(1)。

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

//打印链表
void print(struct ListNode* head)
{
    assert(head);
    struct ListNode* cur = head;
    while (cur)
    {
        printf("%d->", cur->val);
        cur = cur->next;
    }
    printf("NULL");
}

//返回值和参数都不同于Leetcode上的题目, 不要返回值,需要改变原头结点必须传入二级指针
void deleteNode(struct ListNode** pphead, struct ListNode* pToBeDeleted)
{
    assert(pphead);
    assert(pToBeDeleted);
    //要删除的节点不是尾节点
    if (pToBeDeleted->next != NULL)
    {
        struct ListNode* next = pToBeDeleted->next;
        pToBeDeleted->val = next->val;
        pToBeDeleted->next = next->next;
        free(next);
        next == NULL;
    }
    else if (*pphead == pToBeDeleted) //只有一个节点
    {
        free(pToBeDeleted);
        pToBeDeleted = NULL;
        *pphead = NULL;
    }
    else //删除尾节点
    {
        struct ListNode* cur = *pphead;
        while (cur->next != pToBeDeleted)
        {
            cur = cur->next;
        }
        cur->next = NULL;
        free(pToBeDeleted);
        pToBeDeleted = NULL;

    }
}

int main()
{
    //创建一个4,5,1,9的链表
    struct ListNode* node1 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* node2 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* node3 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* node4 = (struct ListNode*)malloc(sizeof(struct ListNode));

    node1->val = 4;
    node2->val = 5;
    node3->val = 1;
    node4->val = 9;

    node1->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = NULL;

    //删除5
    deleteNode(&node1, node2);
    print(node1);

    //删除9
    //deleteNode(&node1, node4);
    //print(node1);


    return 0;
}

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

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

相关文章

【python】类型约束(类型提示的作用)

文章目录前言一、类型系统1.动态类型2.静态类型3.鸭子类型二、变量注解1.变量注解的语法2.注解鸭子类型三、复杂(复合型)变量的注解1.引入2.难题3. Any的妙用4.类型变量5.类型Optional总结前言 python是一种解释型强类型动态语言python3.5以前是没有类型…

【Python】基于高德地图API的坐标转换函数

【Python】基于高德地图API的坐标转换函数 API申请: lbs.amap.com/api/webservice/guide/api/convert/产品介绍 坐标转换是一类简单的HTTP接口,能够将用户输入的非高德坐标(GPS坐标、mapbar坐标、baidu坐标)转换成高德坐标。 …

【学习笔记之Linux】权限之目录权限与默认权限

权限概念 一件事是否允许被谁“做”,这就是权限。权限 用户 文件属性。   在Linux上,用户分为普通用户和root。root是超级管理员 ≈ 天王老子,只能够有一个。root的命令提示符是#;普通用户通过root创建,可以有多个…

【Android安全】Protobuf原理与解析

protobuf 简介 抓包时看到header中有这个: content-type: application/x-protobuf说明包的content是以protobuf格式编码的 关于protobuf的介绍,可以参考: https://techkranti.com/what-is-protobuf-explained-for-hackers/ protobuf 的背…

C++——类和对象(一)

目录 一. 面向过程和面向对象 二. 类的引入 三. 类的定义 1.结构 2.类的作用域 3.类的两种定义方式 四. 类的访问限定符及封装 1.引入 2.访问限定符 3.封装 五. 类的实例化 六. 类对象模型 七. this指针 1.this指针的引出 2.this指针的特性 一. …

JavaScript设计模式之面向对象编程

为了深入地学习 javascript ,奔着一名标准 Web 开发人员的标准,想要深入了解一下面向对象的编程思想,提高自己模块化开发的能力,编写可维护、高效率、可拓展的代码,最近一直拜读 《JavaScript设计模式》 ,对…

【LeetBook】二叉树

参考资料:LeetBook / 二叉树 前言 差不多的解题思路就是dfs能够解决,其次就是bfs。 主要是递归的解法。 一刷就是了解了 解题的 思路 后序再补一些二叉树的题再刷一刷 目录树的介绍树的遍历前序遍历中序遍历后序遍历层序遍历(广度优先搜索)递归解决问题“…

【FPGA】中值滤波处理BMP图片

文章目录一、中值滤波二、BMP图片格式三、功能实现1.代码设计思路2.shift IP核3.代码实现四、结果测试参考博客一、中值滤波 中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。 中值滤波是基于排序统计理论…

【GO】K8s 管理系统项目[API部分--Ingress]

K8s 管理系统项目[API部分–Ingress] 1. 接口实现 service/dataselector.go import ("sort""strings""time"appsv1 "k8s.io/api/apps/v1"corev1 "k8s.io/api/core/v1"nwv1 "k8s.io/api/networking/v1" )// Ing…

JavaScript client screen offset scroll

文章目录JavaScript client screen offset scrollclientX和clientY、offsetX和offsetY、screenX和screenY、pageX和pageYclientWidth、offsetWidth、scrollWidthwindow.outerWidth、window.innerWidth、document.documentElement.clientWidthJavaScript client screen offset s…

【开发工具】Gradle的安装 与 配置环境变量

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ Gradle安装配置教程一、安装Gradle二、配置环境…

C++: Essential C++ 读书笔记:面向过程编程:调用函数。

1:传值和传址区别 按值传递: 在调用函数中将原函数的值拷贝一份过去给被调用的函数,在被调用函数中对该值的修改,不会影响原函数的值。值传递,变量赋值,修改变量的值------修改的是新地址(复制地…

智能家居加速落地,景联文科技提供数据采集标注服务

“以AI驱动智能家居,智能家庭助手和智能家居安防同向发展的智能物联网是目前主流趋势。高质量的标注数据能够高效训练算法,加速应用落地。景联文科技为相关企业提供、智能语音助手、人脸识别、指纹识别门禁系统、非法闯入检测、扫地机器人智能终端控制等…

临床资料研究中的风险因素评估相关指标

前言 写这篇文章是因为最涉及的医学相关的项目比较多,有些常常遇到的概念容易混淆,在这里着重区分一下。(感谢广大学霸的分享) 1. Ratio 与Rate 的区别 Ratio:表示相对比,简单理解为一个数值相对于另一个…

【1】高危漏洞利用工具 (2023.1.6更新)-- Apt_t00ls

0x01 工具介绍增加CNVD-2023-00895包括:泛微、蓝凌、用友、万户、致远、通达、中间件、安全设备等多个高位漏洞。泛微: e-cology workrelate_uploadOperation.jsp-RCE (默认写入冰蝎4.0.3aes) e-cology page_uploadOperation.jsp-RCE (暂未找到案例 仅供检测poc) e-…

Vivado综合属性之ASYNC_REG

本文验证了综合属性ASYNC_REG对寄存器位置的影响。 ASYNC_REG用于单bit信号采用双(或多)触发器实现异步跨时钟域的场合,此时所有用于同步的触发器都要标记ASYNC_REG。标记方式为: (* ASYNC_REG "TRUE" *) reg sync_0…

想在2023 年成为前端 Web 开发人员的分步指南

当我开始成为一名前端开发人员时,这是我希望拥有的路线图我想出了这个路线图,它有助于实现成为全能开发人员的目标。让我们开始吧。谁是前端开发人员?好的,现在谁是后端开发人员?那么如何成为一名前端开发人员呢&#…

Java 集合练习题

SourceURL:file:///home/windstorm/Documents/JAVA/JavaCoursePractise/Java 集合练习题.docx 答案: import java.lang.reflect.Array; import java.security.cert.CollectionCertStoreParameters; import java.util.*; public class Main { public static voi…

promise和async用法及区别(详解)

一、promisepromise的概念Promise 是异步编程的一种解决方案,是一个构造函数,自身有all、reject、resolve方法,原型上有then、catch等方法。特点:对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态&…

【django】模型类中数据的增删改查操作总结

文章目录一、数据库数据操作二、创建对象三、批量创建对象方法一:for循环迭代方法二:bulk_create()四、更新对象save()默认更新所有的字段指定要更新的字段一次性更新多个对象五、查询对象1、管理器2、QuerySet3、检索全部对象a、要注意:4、过…