重启人生计划-浮舟沧海

🥳🥳🥳 茫茫人海千千万万,感谢这一刻你看到了我的文章,感谢观赏,大家好呀,我是最爱吃鱼罐头,大家可以叫鱼罐头呦~🥳🥳🥳

如果你觉得这个【重启人生计划】对你也有一定的帮助,加入本专栏,开启新的训练计划,漫长成长路,千锤百炼,终飞升巅峰!无水文,不废话,唯有日以继日,终踏顶峰! ✨✨欢迎订阅本专栏✨✨

❤️❤️❤️ 最后,希望我的这篇文章能对你的有所帮助! 愿自己还有你在未来的日子,保持学习,保持进步,保持热爱,奔赴山海! ❤️❤️❤️

🔥【重启人生计划】第零章序·大梦初醒🔥

🔥【重启人生计划】第壹章序·明确目标🔥

🔥【重启人生计划】第贰章序·勇敢者先行🔥

🔥【重启人生计划】第叁章序·拒绝内耗🔥

🔥【重启人生计划】第四章序·积蓄星火🔥

序言

大家好,我是最爱吃鱼罐头,距离离职已经过去一个月了,目前进度为4,打算重新找工作倒计时26天,当然这其中也会去投递面试。

没有信仰的人群川流不息,繁华的城市充斥着愚昧,因为生命存在着且有其独特之处。

今日回顾

今天早上大概回顾了下MySQL的面试题,后面就刷day5的算法题,难度适中,是需要理解链表的数据结构。

然后今天下午去了医院检查了下皮肤,也做一点手术换肤术、粉刺去除术,这一套下来脸部是又痒用疼的,尤其这个粉刺去除术,脸部基本都被扎烂了。。。闭着眼,偷偷扎你一下,还要硬挤你痘痘出来,这个挤是真疼,都已经疼出泪花了。。。

然后今天也没背诵,没有看视频,我决定明天补一下,然后再看一下视频。

算法回顾

反转链表 II

反转链表 II 📍

这道题和昨天的反转链表 📍类似,但又有些不同,不同在于反转的是局部,不是对整个链表进行操作,那这道题的关键在于**先找到局部区域的左结点,在左结点和右结点之间进行链表反转的操作即可。**并且这道题的另一个关键在于创建一个虚拟头结点,这样才能更好反转头结点。

主要步骤

  1. 创建一个虚拟头结点并指向head结点;
  2. 找到left位置的前一个结点preNode;
  3. 反转指定局部区间的链表;
  4. 在需要反转的区间里,每遍历到一个结点,让这个新结点来到反转部分的起始位置,一直反转到right位置;
    • preNode:永远指向待反转区域的第一个结点 left 的前一个结点;
    • curNode:指向待反转区域的第一个结点 left;
    • nextNode:指向curNode的next结点;
    • 需要做的操作:将curNode的下一个结点指向 nextNode 的下一个结点;把 pre 的下一个结点指向 next;最后把 nextNode 的下一个结点指向 pre的下一个结点;
  5. 最后返回虚拟头结点的下一个结点。

图解:

代码实现:

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 个结点 📍

这道题有两个关键点:

  1. 必须要有虚拟头结点,避免删除结点刚好是第一个的情况;
  2. 如何确定链表的倒数第 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;
    }
}

移除链表元素

移除链表元素 📍

最后这道题,就很简单了吧,主要关键点:

  1. 必须要有虚拟头结点,避免删除结点刚好是第一个的情况;
  2. 遍历过程遇到与题目要求的值相同时,直接删除。

代码实现:

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。这不区别对待?

小结算法

今天的算法还是相对比较简单,很好刷,认真思考下,就可以完成的,但是前提得要有链表的基础。

明日内容

算法

在有链表的基础上进行链表的算法题,可以事半功倍。

需要有链表以及双指针的基础。

🌸 完结

最后,相关算法的代码也上传到gitee或者github上了。

乘风破浪会有时 直挂云帆济沧海

希望从明天开始,一起加油努力吧,成就更好的自己。

🥂 虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!✨✨✨

💟 感谢各位看到这里!愿你韶华不负,青春无悔!让我们一起加油吧! 🌼🌼🌼

💖 学到这里,今天的世界打烊了,晚安!🌙🌙🌙

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

最爱吃鱼罐头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值