链表OJ经典题目及思路总结(二)头结点

news2024/11/16 3:46:45

系列文章目录

链表OJ经典题目及思路总结(一)双指针
链表OJ经典题目及思路总结(二)头结点


文章目录

  • 系列文章目录
  • 前言
  • 1.建立新链表
    • 1.1 移除链表元素
  • 2.哨兵位的头结点
    • 2.1 链表分割
    • 2.2 合并两个有序链表
  • 3.CV工程师
  • 总结


前言

对于题目的理解是非常重要的,对于解题思路的训练也很重要,对于思维的打磨也很重要,今天我们来看经典的OJ题目,以及两大思想:创建新链表进行操作,尽量不要改动原链表;同时,哨兵位的头结点,可以减少一些麻烦,今天的题主要是无需对空链表进行考虑,也即无需单独处理第一个结点(可能为空节点)。

1.建立新链表

1.1 移除链表元素

203.移除链表元素(题目链接)
在这里插入图片描述
思路1:遍历链表,删除数据域为val的结点;
思路2:将值不为val的节点尾插到新的链表中,返回新链表的头结点(头指针)。

注意:两种思路整体都是遍历原链表,但是有几个坑!

  • 坑点1:如果尾插第一个结点,要更改新的头指针newhead;但后续插入不需要再更改头指针。
  • 坑点2:如果最后一个结点的值不是val,那么将其尾插到新链表,其next指针域为NULL;但是如果最后一个结点的值是val,其前一个结点(假设prev指向该结点)被尾插到新链表,prev->next指向的最后一个节点(数据域为val)被free,那么prev->next是野指针,所以要将其置为NULL.

比如下图中当数据域为5的结点被尾插到新链表之后,tail指向该节点,tail->next=p,但是free§之后,p就是野指针了,应将tail->next手动置为NULL.
在这里插入图片描述

  • 坑点3:第二点和链表本身为空可以合在一起考虑,如果head为NULL,那么cur为NULL,遍历原链表的while循环不会执行,tail为NULL,newhead为NULL,直接返回即可;而如果tail不为NULL,tail->next有可能为NULL,不考虑太多,直接置为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, *newhead=NULL, *tail=NULL;
    //cur用于遍历要删除的链表
    //newhead用于存放要新链表的头指针,用于返回
    //tail用于尾插,否则每次尾插都要遍历新链表找尾节点,效率不高
    while(cur)//while循环遍历要删除的链表
    {
    	//结点数据域为val与非val分开处理,val则删除,非val则尾插到新链表
        if(cur->val!=val)
        {
        	//插入第一个元素和后续元素不同的是,插入第一个元素需要更改头指针,要单独处理
        	//坑点1 
            if(tail==NULL)
                newhead=tail=cur;
            else
            {
                tail->next=cur;
                tail=tail->next;
            }
            cur=cur->next;
        }
        else
        {
            struct ListNode* next=cur->next;
            free(cur);
            cur=next;
        }
    }
    
    //坑点2,3
    if(tail)
		tail->next=NULL;
    return newhead;
}

2.哨兵位的头结点

2.1 链表分割

CM11 链表分割(题目链接)
在这里插入图片描述
或许有一些小伙伴的思路是,将大于等于x的结点删除同时尾插到原链表。这种思路的问题是:

  1. 首先不推荐在链表中间进行插入和删除,因为要记录前一个节点的位置,防止链表后续节点丢失;
  2. 其次,将大于等于x的结点不断插入到链表,要先遍历链表找到尾结点,不然会导致大于等于x的结点被循环插入,没有终止。并且还要再次遍历链表进行删除、尾插,这样就遍历了两次链表,效率下降不高;
  3. 第三,这种同时进行删除、尾插的行为对代码、逻辑思维能力要求较高,一不小心,无论是调试还是修改都容易凌乱。

因此无论是移除链表元素还是链表分割,我们都推荐将结点插入到新链表!

对于该题,遍历原链表,将值小于x的结点尾插到一个新链表,值大于等于x的结点尾插到另一个新链表,最后将两个链表链接,因为尾插不改变原来结点的相对顺序

这里引入一个哨兵位的头结点,因为尾插要判断链表是否为空,如果为空,就要单独处理。哨兵位的头结点一般都是动态申请,用完进行free(牛客链接可能测不出内存泄漏问题)。

并且两个新的用于插入的链表都引入头结点,这样,即使传入的链表为空也无需单独讨论;即使链表所有结点数据域的值都大于等于或都小于x,也无需进行单独处理。

代码示例如下

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <asm-generic/errno.h>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode* cur=pHead;
        struct ListNode* greatGuard,*greatTail,*shortGuard,*shortTail;
		//动态申请头结点
        greatGuard=greatTail=(struct ListNode*)malloc(sizeof(struct ListNode));
        shortGuard=shortTail=(struct ListNode*)malloc(sizeof(struct ListNode));
        greatGuard->next=shortGuard->next=NULL;
        while(cur)
        {
            if(cur->val<x)
            {
                //尾插
                shortTail->next=cur;
                shortTail=shortTail->next;
            }
            else 
            {
                greatTail->next=cur;
                greatTail=greatTail->next;
            }
            cur=cur->next;
        }
        
        //链接
        shortTail->next=greatGuard->next;

		//这个地方greatTail是原来的cur赋值得来的,所以greatTail可能还存储着原链表的下一个节点的地址,具体如下图,所以要将greatTail->next置为NULL.
        greatTail->next=NULL;
        pHead=shortGuard->next;
        free(greatGuard);
        free(shortGuard);   
        return pHead;
    }
};

下图示例中,两个链表链接后数据域为2的结点指向数据域为4的结点,数据域为7的结点next指针域存储数据域为1的结点的地址,最后会循环遍历1 3 2 4 6 7 1 3 2 4 6 7…链表成了循环单链表,与题目不符。
在这里插入图片描述

2.2 合并两个有序链表

21.合并两个有序链表(题目链接)
在这里插入图片描述
思路:
注意建立新链表,而不是在原链表进行操作以及哨兵位的头结点可以减少很多麻烦;
双指针分别遍历两个链表,并比较结点的值,将值较小的链表尾插到新链表;
哨兵位的头结点可以避免尾插检查链表是否为空的问题。
代码实现如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* cur1=list1,*cur2=list2,*guard=NULL,*tail=NULL;
    guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));
    tail->next=NULL;

    while(cur1 && cur2)
    {
        if(cur1->val<cur2->val)
        {
            tail->next=cur1;
            tail=tail->next;
            cur1=cur1->next;
        }
        else
        {
            tail->next=cur2;
            tail=tail->next;
            cur2=cur2->next;
        }
    }
    if(cur1)
        tail->next=cur1;
    if(cur2)
        tail->next=cur2;
    struct ListNode* head=guard->next;
    free(guard);
    return head;
}

如果没有头结点,代码如下,每次头插都要考虑链表是否为NULL.

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1==NULL && list2==NULL)
        return false;
    struct ListNode* cur1=list1,*cur2=list2,*newhead=NULL,*tail=NULL;
    while(cur1 && cur2)
    {
        if(cur1->val<cur2->val)
        {
            if(newhead==NULL)
                newhead=tail=cur1;
            else
            {
                tail->next=cur1;
                tail=tail->next;
            }
            cur1=cur1->next;
        }
        else
        {
            if(newhead==NULL)
                newhead=tail=cur2;
            else
            {
                tail->next=cur2;
                tail=tail->next;
            }
            cur2=cur2->next;
        }
    }  
    if(cur1)
    {
        if(newhead==NULL)
            newhead=cur1;
        else
            tail->next=cur1;
    }  
    if(cur2)
    {
        if(newhead==NULL)
            newhead=cur2;
        else
            tail->next=cur2;
    }
    return newhead;
}

以上两个代码均通过测试,我们看到,不带头结点代码要考虑得多,且较为繁琐。


3.CV工程师

OR36 链表的回文结构(题目链接)
在这里插入图片描述
思路:一共有两种可能情况,第一种,一共偶数个元素,将中间结点及其后面的结点反转后与前半部分剩余的结点完全一致;
第二种,一共奇数个元素,将中间结点及其后面的结点翻转后比前半部分剩余的结点多一个,但是,我们可以看到下图中的指向关系,前半部分的2结点指向3结点,反转后指向不变,遍历head链表用于比较。

反转后的头指针为rhead,将rhead与head所指的链表进行比较,当rhead为NULL时停止。

在这里插入图片描述
所以要找中间结点,并且反转链表,找到我们之前写的反转链表和找链表中间结点的代码,Ctrl+C,Ctrl+V即可,做CV工程师~

反转链表和找链表中间结点的代码在系列文章中,下面为链接,有兴趣的友友可以看一下。
链表OJ经典题目及思路总结(一)双指针

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
  public:
    struct ListNode* middleNode(struct ListNode* head) {
        struct ListNode* fast = head, *slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    struct ListNode* reverseList(struct ListNode* head) {
        struct ListNode* cur = head, *newhead = NULL;
        while (cur) {
            struct ListNode* next = cur->next;

            cur->next = newhead;
            newhead = cur;

            cur = next;
        }
        return newhead;
    }
    bool chkPalindrome(ListNode* head) {
        // write code here
    struct ListNode* mid= middleNode(head);
    struct ListNode* rhead = reverseList(mid);
    while(rhead)
    {
        if(rhead->val != head->val)
            return false;
        rhead=rhead->next;
        head=head->next;
    }
    return true;
    }
};

总结

两大思想:
一、尽量不要在原链表插入、删除,特别是表中,创建新链表较为方便;二、哨兵位头结点比较方便,避免尾插时对空链表的讨论。以及必要时做CV工程师呀~

分享今天心得,老师提到,要自主写代码,有问题就调试、画图、思考,不要对照老师写的代码去写代码、修改代码,因为面试的时候参考谁的呢?工作参考谁的呢?

昨天代码照着老师写、修改的,今天自己写代码、修改,通过测试后,和老师代码对照,查漏补缺。感觉确实不一样,自己写的代码,而且有一个代码老师没讲,也写过了,就是2.2不带头结点的方式,当时老师没讲,但是听课真的有点迷糊了~

也有的同学问,这些代码的思路都是怎么想出来的呢?老师说呀,多画画图思考,如果还是不可以,就多练!感觉自己写代码的时候,要去考虑很多,比如链表为空怎么处理,链表结点的指向,画图清晰很多!

希望屏幕面前的你有所收获,要么天赋异禀,要么天道酬勤,加油,年轻人!

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

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

相关文章

Python笔记 - 利用装饰器设计注解体系

认识注解 注解&#xff08;Annotation&#xff09;是一种用于为代码添加元数据的机制。这些元数据可以在运行时被访问&#xff0c;用于为代码元素&#xff08;如类、方法、字段等&#xff09;提供额外的信息或指示。 由于Python中装饰器只能装饰类和方法&#xff0c;因此也只…

C动态内存管理

前言&#xff1a;不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。 1 为什么要有动态内存分配 在C语言中&#xff0c;使用int&#xff0c;float&#xff0c;double&#xff0c;short等数据内置类型以及数组不是也可以开辟内存空间吗&…

《算法岗面试宝典》重磅发布!

大家好&#xff0c;历时半年完善&#xff0c;《算法岗面试宝典》 终于可以跟大家见面了。 最近 ChatGPT 爆火&#xff0c;推动了技术圈对大模型算法场景落地的热情&#xff0c;就业市场招聘人数越来越多&#xff0c;算法岗一跃成为竞争难度第一的岗位。 岗位方向 从细分方向…

李宏毅深度学习-梯度下降和Normalization归一化

Gradient Descent梯度下降 ▽ -> 梯度gradient -> vector向量 -> 下图中的红色箭头&#xff08;loss等高线的法线方向&#xff09; Tip1: Tuning your learning rates Adaptive Learning Rates自适应 通常lr会越来越小 Adaptive Learning Rates中每个参数都给它不同…

110.WEB渗透测试-信息收集-ARL(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;109.WEB渗透测试-信息收集-FOFA语法&#xff08;9&#xff09; 信息收集自动化工具-灯塔…

黑马头条day6-kafka及异步通知文章上下架

今天任务比较水 主要是kafka入门和 文章上下架 以及异步通知article同步到app的前端数据 需要重新看一下&#xff08;使用步骤并不是很复杂 kafka主要解决高并发&#xff09; 1 kafka的入门 和 使用异步 需要重新看一下了流程和 详细信息 2 bug 打开app页面的时候出现503 服…

从0到1深入浅出构建Nest.Js项目

Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#xff09;&#xff0c;并结合了 OOP &#xff08;面向对…

动手学运动规划: 2.2.c 3次样条曲线代码解析

学习? 学个P!☺ — 亮剑 李云龙 &#x1f3f0;代码及环境配置&#xff1a;请参考 环境配置和代码运行! 本节提供了3次样条曲线的代码测试 python3 tests/curves/cubic_spline.py2.2.c.1 3次样条曲线代码实现 CubicSpline1D实现了1维的3次样条曲线, 需要输入一组离散点. Cub…

现在别买理想L7/L8,问界M8要来暴揍友商了

文 | AUTO芯球 作者 | 雷慢 问界又一重磅炸弹要来了&#xff0c; 它就是问界M8&#xff0c; 来看&#xff0c;M8刚又曝光了大量谍照。 现在我打听的消息是这样的&#xff0c; 11月广州车展亮相预售&#xff0c; 12月底正式上市&#xff0c;25年春节前后开始交付&#xff…

计算机网络:计算机网络体系结构 —— 专用术语总结

文章目录 专用术语实体协议服务服务访问点 SAP 服务原语 SP 协议数据单元 PDU服务数据单元 SDU 专用术语 实体 实体是指任何可以发送或接收信息的硬件或软件进程 对等实体是指通信双方处于相同层次中的实体&#xff0c;如通信双方应用层的浏览器进程和 Web 服务器进程。 协…

Java组件化开发:jar包

我在java基础&#xff1a;原始数据类型&#xff0c;包的创建与导入-CSDN博客一文中记录了包的使用&#xff0c;此文就详细讲解一下IDEA中如何进行组件化开发。 介绍 现在的软件系统功能越来越复杂&#xff0c;规模也越来越大&#xff0c;为了应对这种挑战&#xff0c;人们将“…

深入解析Python错误消息及解决方法

深入解析Python错误消息及解决方法 Python是开发者广泛使用的语言&#xff0c;因其简洁的语法和强大的标准库而深受欢迎。然而&#xff0c;Python程序在运行过程中&#xff0c;错误不可避免。理解Python的错误消息并正确处理这些错误&#xff0c;是提升代码质量和调试效率的重…

3.点位管理改造-列表查询——帝可得管理系统

目录 前言一、与页面原型差距1.现在&#xff1a;2.目标&#xff1a;3. 存在问题&#xff1a;所在区域和合作商ID展示的都是ID&#xff0c;而不是名称&#xff1b;同时合作商ID应改为合作商 二、修改1.重新设计SQL语句2.修改mapper层&#xff0c;使用Mybatis中的嵌套查询3.修改s…

AI人工智能人像修饰中文面板PS插件 Retouch Pro 3.2.0 中文汉化版

AI人工智能人像修饰PS扩展插件 Retouch Pro 3.2.0 中文汉化版 支持软件&#xff1a;PS 2018 – PS 2025或更高版本 系统要求&#xff1a;Windows系统 或 MacOS系统 出处&#xff1a;https://www.aeown.com/thread-3061-1-1.html Retouch Pro Panel 有一个非常强大和先进的人工…

Python Tips6 基于数据库和钉钉机器人的通知

说明 起因是我第一版quant程序的短信通知失效了。最初认为短信是比较即时且比较醒目的通知方式&#xff0c;现在看来完全不行。 列举三个主要问题&#xff1a; 1 延时。在早先还能收到消息的时候&#xff0c;迟滞就很严重&#xff0c;几分钟都算短的。2 完全丢失。我手机没有…

ACP科普:SoSM和CPO

在Scrum of Scrums&#xff08;SoS&#xff09;框架中&#xff0c;SoSM&#xff08;Scrum of Scrums Master&#xff09;和CPO&#xff08;Chief Product Owner&#xff09;是两个关键角色&#xff0c;负责协调多个Scrum团队的工作&#xff0c;确保项目的顺利进行。以下是对这两…

Android AMS介绍

注&#xff1a;本文为作者学习笔记&#xff0c;如有误&#xff0c;请各位大佬指点 系统进程运行环境的初始化 Context是一个抽象类&#xff0c;它可以访问application环境的全局信息和各种资源信息和类 context功能&#xff1a; 对Activity、Service生命周期的管理通过Intent发…

c++进阶之多态讲解

这篇文章和大家一起学习一下c中的多态 多态的概念 多态的概念&#xff1a;通俗来讲&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 什么是静态多态 前⾯讲的函数重载和函数模板&#xff0c;它们传不同类型的参数就可以调用不同的函数&…

深入理解 C 语言中的内存操作函数:memcpy、memmove、memset 和 memcmp

目录&#xff1a; 前言一、 memcpy 函数二、 memmove 函数三、 memset 函数四、 memcmp 函数总结 前言 在 C 语言中&#xff0c;内存操作函数是非常重要的工具&#xff0c;它们允许我们对内存进行直接操作&#xff0c;从而实现高效的数据处理。本文将深入探讨四个常用的内存操…

zabbix7.0web页面删除主机操作实现过程

前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 被监控端配置 链接: zabbix7.0监控linux主机案例详解 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) zabbix-client(被监控端已配置…