当前位置: 首页 > news >正文

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

系列文章目录

链表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,3if(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 herestruct 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;elsetail->next=cur1;}  if(cur2){if(newhead==NULL)newhead=cur2;elsetail->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 herestruct 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.mrgr.cn/news/41370.html

相关文章:

  • 【动态规划】0-1背包问题(滚动数组篇)
  • V3D——从单一图像生成 3D 物体
  • OpenAI 推理模型 O1 研发历程:团队访谈背后的故事
  • 代码随想录Day24
  • 基于元神操作系统实现NTFS文件操作(二)
  • Linux 进程优先级
  • 【Python】CSVKit:强大的命令行CSV工具套件
  • 基于ssm的学生社团管理系统 社团分配系统 社团活动调度平台 学生社团管理 信息化社团管理开发项目 社团活动管理 社团预约系统(源码+文档+定制)
  • 图解C#高级教程(四):协变、逆变
  • Spring注解系列 - @Autowired注解
  • express,生成用户登录后的 token
  • Golang 服务器虚拟化应用案例
  • 什么是 LDAC、SBC 和 AAC 音频编码技术
  • 【不看会后悔系列】排序之——文件归并【史上最全详解】~
  • 【在Linux世界中追寻伟大的One Piece】System V共享内存
  • FreeRTOS篇4:任务调度
  • python numpy np.fromstring方法介绍
  • C++七种异常处理
  • 练习题 - DRF 3.x Validators 验证使用示例和配置方法
  • 命令按钮QLink