🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳
如果你觉得这个【重启人生计划】对你也有一定的帮助,加入本专栏,开启新的训练计划,漫长成长路,千锤百炼,终飞升巅峰!无水文,不废话,唯有日以继日,终踏顶峰! ✨✨欢迎订阅本专栏✨✨
❤️❤️❤️ 最后,希望我的这篇文章能对你的有所帮助! 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ❤️❤️❤️
🔥【重启人生计划】第零章序·大梦初醒🔥
🔥【重启人生计划】第壹章序·明确目标🔥
🔥【重启人生计划】第贰章序·勇敢者先行🔥
🔥【重启人生计划】第叁章序·拒绝内耗🔥
🔥【重启人生计划】第四章序·积蓄星火🔥
序言
大家好,我是最爱吃鱼罐头,距离离职已经过去一个月了,目前进度为4,打算重新找工作倒计时26天,当然这其中也会去投递面试。
没有信仰的人群川流不息,繁华的城市充斥着愚昧,因为生命存在着且有其独特之处。
今日回顾
今天早上大概回顾了下MySQL的面试题,后面就刷day5的算法题,难度适中,是需要理解链表的数据结构。
然后今天下午去了医院检查了下皮肤,也做一点手术换肤术、粉刺去除术,这一套下来脸部是又痒用疼的,尤其这个粉刺去除术,脸部基本都被扎烂了。。。闭着眼,偷偷扎你一下,还要硬挤你痘痘出来,这个挤是真疼,都已经疼出泪花了。。。
然后今天也没背诵,没有看视频,我决定明天补一下,然后再看一下视频。
算法回顾
反转链表 II
反转链表 II 📍
这道题和昨天的反转链表 📍类似,但又有些不同,不同在于反转的是局部,不是对整个链表进行操作,那这道题的关键在于**先找到局部区域的左结点,在左结点和右结点之间进行链表反转的操作即可。**并且这道题的另一个关键在于创建一个虚拟头结点,这样才能更好反转头结点。
主要步骤
- 创建一个虚拟头结点并指向head结点;
- 找到left位置的前一个结点preNode;
- 反转指定局部区间的链表;
- 在需要反转的区间里,每遍历到一个结点,让这个新结点来到反转部分的起始位置,一直反转到right位置;
preNode
:永远指向待反转区域的第一个结点 left 的前一个结点;curNode
:指向待反转区域的第一个结点 left;nextNode
:指向curNode的next结点;- 需要做的操作:将curNode的下一个结点指向
nextNode
的下一个结点;把pre
的下一个结点指向next
;最后把nextNode
的下一个结点指向pre
的下一个结点;
- 最后返回虚拟头结点的下一个结点。
图解:
代码实现:
package com.ygt.day5;
import com.ygt.day4.ListNode;
/**
* 92. 反转链表 II
* https://leetcode.cn/problems/reverse-linked-list-ii/description/
* 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。
* 请你反转从位置 left 到位置 right 的链表结点,返回 反转后的链表 。
* 输入:head = [1,2,3,4,5], left = 2, right = 4
* 输出:[1,4,3,2,5]
* @author ygt
* @since 2024/8/15
*/
public class ReverseBetween {
public static void main(String[] args) {
ListNode node5 = new ListNode(5);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node = new ListNode(1, node2);
// 打印查看当前效果
ListNode.print(node);
ListNode listNode = new ReverseBetween().reverseBetween(node, 2, 4);
System.out.println();
// 打印查看当前效果
ListNode.print(listNode);
}
public ListNode reverseBetween(ListNode head, int left, int right) {
// 1. 创建一个虚拟头结点并指向head结点;
// 为避免出现left出现在第一个结点
ListNode dummyNode = new ListNode(-1, head);
// 2. 找到left位置的前一个结点preNode;
ListNode preNode = dummyNode;
for (int i = 0; i < left - 1; i++) {
preNode = preNode.next;
}
// 3. 反转指定局部区间的链表;
// 在找到left的前结点后,可以在left和right区间的链表进行反转了
// 1 --> 2 --> 3 --> 4 --> 5 待反转区域为 2 --> 3 --> 4
// 定义当前结点
ListNode curNode = preNode.next;
// 4. 在需要反转的区间里,每遍历到一个结点,让这个新结点来到反转部分的起始位置,一直反转到right位置;
// 开始遍历反转
for (int i = left; i < right; i++) {
// 当前结点的next结点
ListNode nextNode = curNode.next;
// 将当前结点的next指向了 nextNode的next结点 即 2 --> 4
curNode.next = nextNode.next;
// 将nextNode的next指向了 preNode的next结点 即 3 --> 2
nextNode.next = preNode.next;
// 将preNode的next指向了 nextNode 即 1 --> 3
preNode.next = nextNode;
}
// 5. 最后返回虚拟头结点的下一个结点。
return dummyNode.next;
}
}
最后注意虚拟头结点:
链表的一大问题就是操作当前结点必须要找前一个结点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个结点了。
每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。而且很多链表的题目中,都大多数需要用到虚拟头结点。
删除排序链表中的重复元素 II
删除排序链表中的重复元素 II📍
这道题和昨天的删除排序链表中的重复元素📍类似,但又有些不同,不同在于删除元素上,这道题删除的是全部重复元素,不能有保留,比昨天额外需要做的一步是,循环一直跳过当前结点,直到不同为止,并且为了避免第一个元素就是重复的情况下,所以也得创建一个虚拟头结点。
代码实现:
package com.ygt.day5;
import com.ygt.day4.ListNode;
/**
* 82. 删除排序链表中的重复元素 II
* https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/description/
* 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的结点,只留下不同的数字 。返回 已排序的链表 。
* 输入:head = [1,2,3,3,4,4,5]
* 输出:[1,2,5]
* @author ygt
* @since 2024/8/15
*/
public class DeleteDuplicates {
public static void main(String[] args) {
ListNode node7 = new ListNode(5);
ListNode node6 = new ListNode(4, node7);
ListNode node5 = new ListNode(4, node6);
ListNode node4 = new ListNode(3, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node = new ListNode(1, node2);
// 打印查看当前效果
ListNode.print(node);
ListNode listNode = new DeleteDuplicates().deleteDuplicates(node);
System.out.println();
// 打印查看当前效果
ListNode.print(listNode);
}
public ListNode deleteDuplicates(ListNode head) {
// 1. 创建一个虚拟头结点并指向head结点;
// 为避免出现第一个结点就重复的情况
ListNode dummyNode = new ListNode(-1, head);
// 前一个结点
ListNode preNode = dummyNode;
// 从虚拟头结点出发,为啥不直接 ListNode preNode = dummyNode.next;
// 因为第一个结点有可能重复呀。
while (preNode.next != null && preNode.next.next != null) {
// 当前值
int curVal = preNode.next.val;
// next值
int nextVal = preNode.next.next.val;
// 两种情况,
if(curVal != nextVal){
// 当前值与next值不同
// curNode.next可以正常指向,无需改变,也就是后移一位。
preNode = preNode.next;
}else{
// 相同
// 就得循环找到不同的值为止
do {
// 跳过当前结点
preNode.next = preNode.next.next;
}while (preNode.next != null && preNode.next.val == curVal);
}
}
return dummyNode.next;
}
}
删除链表的倒数第 N 个结点
删除链表的倒数第 N 个结点 📍
这道题有两个关键点:
- 必须要有虚拟头结点,避免删除结点刚好是第一个的情况;
- 如何确定链表的倒数第 N 个结点呢?首先要确定删除某个结点话,一般是找到待删除结点的前一个结点,所以思路如下:
- 让一个指针fast先走N步,然后创建一个指针slow和fast一起走,直到fast遇到了null,这时slow就是倒数第 N 个结点的前一个结点;
- 接着进行删除操作即可。
代码实现:
package com.ygt.day5;
import com.ygt.day4.ListNode;
/**
* 19. 删除链表的倒数第 N 个结点
* https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/
* 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
* 输入:head = [1,2,3,4,5], n = 2
* 输出:[1,2,3,5]
* @author ygt
* @since 2024/8/15
*/
public class RemoveNthFromEnd {
public static void main(String[] args) {
// 测试反转链表
ListNode node5 = new ListNode(5);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node = new ListNode(1, node2);
// 打印查看当前效果
ListNode.print(node);
ListNode list = new RemoveNthFromEnd().removeNthFromEnd(node, 2);
System.out.println();
// 打印查看当前效果
ListNode.print(list);
}
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) {
return null;
}
// 1. 创建一个虚拟头结点并指向head结点;
// 为避免出现 删除结点刚好是第一个的情况
ListNode dummyNode = new ListNode(-1, head);
// 主要思路:让一个指针fast先走N步,然后创建一个指针slow和fast一起走,直到fast遇到了null,
// 这时slow就是倒数第 N 个结点的前一个结点;
ListNode fastNode = dummyNode;
// 在移动过程中,必须额外判断是否有为空的情况。
for (int i = 0; i < n && fastNode.next != null; i++) {
fastNode = fastNode.next;
}
ListNode slowNode = dummyNode;
while (fastNode.next != null) {
// 一起移动,直到fast遇到null
slowNode = slowNode.next;
fastNode = fastNode.next;
}
// 删除
slowNode.next = slowNode.next.next;
return dummyNode.next;
}
}
移除链表元素
移除链表元素 📍
最后这道题,就很简单了吧,主要关键点:
- 必须要有虚拟头结点,避免删除结点刚好是第一个的情况;
- 遍历过程遇到与题目要求的值相同时,直接删除。
代码实现:
package com.ygt.day5;
import com.ygt.day4.ListNode;
/**
* 203. 移除链表元素
* https://leetcode.cn/problems/remove-linked-list-elements/description/
* 给你一个链表的头结点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的结点,并返回 新的头结点 。
* 输入:head = [1,2,6,3,4,5,6], val = 6
* 输出:[1,2,3,4,5]
* @author ygt
* @since 2024/8/15
*/
public class RemoveElements {
public static void main(String[] args) {
ListNode node6 = new ListNode(6);
ListNode node5 = new ListNode(5, node6);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node22 = new ListNode(6, node3);
ListNode node2 = new ListNode(2, node22);
ListNode node = new ListNode(1, node2);
// 打印查看当前效果
ListNode.print(node);
ListNode list = new RemoveElements().removeElements(node, 6);
System.out.println();
// 打印查看当前效果
ListNode.print(list);
}
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return null;
}
// 1. 创建一个虚拟头结点并指向head结点;
// 为避免出现 删除结点刚好是第一个的情况
ListNode dummyNode = new ListNode(-1, head);
// 前一个结点
ListNode preNode = dummyNode;
while (preNode.next != null) {
// 开始判断
if(preNode.next.val == val) {
// 相同
preNode.next = preNode.next.next;
}else {
// 不同
preNode = preNode.next;
}
}
return dummyNode.next;
}
}
不是,这它写的跟我有什么不同吗。。。。。我1ms,它0ms。这不区别对待?
小结算法
今天的算法还是相对比较简单,很好刷,认真思考下,就可以完成的,但是前提得要有链表的基础。
明日内容
算法
在有链表的基础上进行链表的算法题,可以事半功倍。
需要有链表以及双指针的基础。
- 链表的中间结点 📍
- 环形链表 📍
- 环形链表 II 📍
- 相交链表📍
🌸 完结
最后,相关算法的代码也上传到gitee或者github上了。
乘风破浪会有时 直挂云帆济沧海
希望从明天开始,一起加油努力吧,成就更好的自己。
🥂 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!✨✨✨
💟 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! 🌼🌼🌼
💖 学到这里,今天的世界打烊了,晚安!🌙🌙🌙