经典算法之链表篇(二)

news2024/11/16 9:37:36

目录

一:重排链表(LeetCode.143)

 二:删除链表的节点(LCR 136. 删除链表的节点)

三:K个一组反转链表(LeetCode.25)


 有关经典算法链表的第一篇内容,可以查看我的上一篇内容:经典算法之链表篇(一)

一:重排链表(LeetCode.143)

问题描述

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln-1 → Ln请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例

输入: head = [1,2,3,4]
输出: [1,4,2,3] 

示例2

输入: head = [1,2,3,4,5]
输出: [1,5,2,4,3] 

解题思路:

  • 找到链表中点:使用快慢指针找到链表的中点。快指针每次移动两步,慢指针每次移动一步,当快指针到达链表末尾时,慢指针指向链表中点。

  • 反转后半部分链表:从中点处将链表分为两部分,将后半部分链表进行反转。

  • 合并链表:将前半部分链表和反转后的后半部分链表依次交替合并,即可得到重新排列后的链表。

  • 处理边界情况:需要注意链表为空或只有一个节点的情况,直接返回即可
    图示:
    第一步:

    第二步:

    第三步:

    第四步:

代码实现

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) return;

        // 找到链表的中间节点
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        // 反转后半部分链表
        ListNode pre = null;
        ListNode cur = slow.next;
        slow.next = null; // 断开前后两部分链表
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }

        // 合并两部分链表
        ListNode p1 = head;
        ListNode p2 = pre;
        while (p2 != null) {
            ListNode tmp1 = p1.next;
            ListNode tmp2 = p2.next;
            p1.next = p2;
            p2.next = tmp1;
            p1 = tmp1;
            p2 = tmp2;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5};
        ListNode head = createList(nums);

        // 重排链表
        Solution solution = new Solution();
        solution.reorderList(head);

        // 输出重排后的链表
        printList(head);
    }

    // 创建链表
    public static ListNode createList(int[] nums) {
        if (nums.length == 0) return null;
        ListNode head = new ListNode(nums[0]);
        ListNode cur = head;
        for (int i = 1; i < nums.length; ++i) {
            cur.next = new ListNode(nums[i]);
            cur = cur.next;
        }
        return head;
    }

    // 输出链表
    public static void printList(ListNode head) {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}

 二:删除链表的节点(LCR 136. 删除链表的节点)

 问题描述

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

返回删除后的链表的头节点。

示例

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

解题思路

  • 判断链表是否为空,若为空直接返回 nullptr。

  • 创建一个虚拟头节点 dummy,并将其指向头节点 head,这样做是为了方便处理头节点的删除操作。

  • 初始化两个指针 precur,分别指向虚拟头节点和头节点。

  • 遍历链表,查找要删除的节点:

  • 如果当前节点的值等于要删除的值 val,则将前一个节点 prenext 指针指向当前节点的下一个节点 cur->next,即完成删除操作。

  • 否则,更新 precur 指针,继续遍历链表。

  • 完成遍历后,更新头节点 head 为虚拟头节点的下一个节点 dummy->next,即删除可能存在的头节点。

  • 释放虚拟头节点的内存,避免内存泄漏。

  • 返回更新后的头节点 head
    图示:
    第一步:

    第二步:

    第三步:遍历链表,查找要删除的节点
    第四步:

代码演示

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) return null; // 如果链表为空,直接返回 null

        // 创建一个虚拟头节点,方便处理头节点的删除
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode pre = dummy; // 前一个节点指针
        ListNode cur = head; // 当前节点指针

        while (cur != null) {
            if (cur.val == val) { // 如果当前节点的值等于要删除的值
                pre.next = cur.next; // 将前一个节点的指针指向当前节点的下一个节点
                break; // 找到并删除节点后退出循环
            }
            pre = cur; // 更新前一个节点指针
            cur = cur.next; // 更新当前节点指针
        }

        head = dummy.next; // 更新头节点

        return head; // 返回头节点
    }
}

public class Main {
    // 创建链表
    public static ListNode createList(int[] nums) {
        if (nums.length == 0) return null;
        ListNode head = new ListNode(nums[0]);
        ListNode cur = head;
        for (int i = 1; i < nums.length; ++i) {
            cur.next = new ListNode(nums[i]);
            cur = cur.next;
        }
        return head;
    }

    // 输出链表
    public static void printList(ListNode head) {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 输入链表
        int[] nums = {4, 5, 1, 9};
        ListNode head = createList(nums);
        int val = 5;

        // 删除指定值的节点
        Solution solution = new Solution();
        head = solution.deleteNode(head, val);

        // 输出删除后的链表
        printList(head);
    }
}

三:K个一组反转链表(LeetCode.25)

问题描述

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例
 

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例二

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

解题思路

  • 创建一个虚拟头节点 dummy,将其 next 指向原链表的头节点 head,方便处理头部的特殊情况。

  • 使用 pre 指针来记录每个需要翻转的子链表的前一个节点。初始时,pre 指向虚拟头节点。

  • 在循环中,先找到需要翻转的子链表的起始节点 start 和结束节点 end。如果剩余节点不足 k 个,则结束循环。

  • 将当前子链表与下一个子链表断开,即将 end->next 置为 nullptr

  • 调用 reverse 函数翻转当前子链表,并将翻转后的子链表连接到前一个子链表的末尾,即将 pre->next 指向翻转后的子链表的头节点。

  • 将翻转后的子链表的末尾与下一个子链表的开头连接,即将 start->next 指向下一个需要翻转的子链表的第一个节点。

  • 更新 pre 指向下一个需要翻转的子链表的前一个节点。

  • 循环直到所有子链表都被翻转。
    图示:
    第一步:

    第二步:

    第三步:

    第四步:

    第五步:

    第六步:

    第七步:

 代码演示

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0); // 创建一个虚拟头节点,方便处理头部的特殊情况
        dummy.next = head;
        ListNode prev = dummy; // prev 指向每个需要翻转的子链表的前一个节点

        while (true) {
            ListNode start = prev.next; // start 指向当前需要翻转的子链表的第一个节点
            ListNode end = prev; // end 指向当前需要翻转的子链表的最后一个节点
            for (int i = 0; i < k && end != null; ++i) {
                end = end.next; // 找到当前需要翻转的子链表的最后一个节点
            }
            if (end == null) {
                break; // 如果剩余节点不足 k 个,结束循环
            }

            ListNode nextGroup = end.next; // nextGroup 指向下一个需要翻转的子链表的第一个节点
            end.next = null; // 将当前子链表与下一个子链表断开

            prev.next = reverse(start); // 翻转当前子链表,并将翻转后的子链表连接到前一个子链表的末尾
            start.next = nextGroup; // 将翻转后的子链表的末尾与下一个子链表的开头连接

            prev = start; // 更新 prev 指向下一个需要翻转的子链表的前一个节点
        }

        return dummy.next; // 返回虚拟头节点的下一个节点作为翻转后的链表的头节点
    }

    private ListNode reverse(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}

public class Main {
    public static void main(String[] args) {
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);

        Solution solution = new Solution();
        ListNode newHead = solution.reverseKGroup(head, 2);

        printList(newHead); // 输出:2 1 4 3 5
    }

    private static void printList(ListNode head) {
        ListNode curr = head;
        while (curr != null) {
            System.out.print(curr.val + " ");
            curr = curr.next;
        }
        System.out.println();
    }
}

总结

这三道链表题相比较于上篇的三道题难度有些增加,因此要多加注重理解。作者在写算法题的时候也借鉴了许多技术大佬的相关博客知识和力扣官方的解题思路,后续还会再写有关链表的经典算法题,大家可以持续关注!!

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

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

相关文章

在线考试系统源码功能分析

在线考试系统源码的功能分析涵盖了多个关键方面&#xff0c;以确保系统能够满足教育机构和个人的需求。以下是一些常见的功能分析&#xff1a; 权限控制&#xff1a;系统通常支持多个角色&#xff0c;如教师、管理员和学生&#xff0c;并使用JWT等技术进行用户身份的合法性校验…

Leetcode JAVA刷刷站(101)对称二叉树

一、题目概述 二、思路方向 在Java中&#xff0c;要检查一个二叉树是否是轴对称的&#xff08;也称为镜像对称的&#xff09;&#xff0c;你可以通过递归地比较树的左子树和右子树是否镜像对称来实现。轴对称的二叉树意味着树的左子树和右子树关于根节点对称&#xff0c;即左子…

微信小程序:手机联调同一个网段无法找到本地接口

我们在开发微信小程序的时候&#xff0c;一般会启动本地服务器进行API连调&#xff0c;不过模拟器上面往往一些问题及细节发现不了&#xff0c;需要真机调试&#xff0c;结果调试的时候发现&#xff0c;不能访问到 localhost或者本机IP&#xff0c;也就访问不到本地接口&#x…

【HarmonyOS NEXT开发】鸿蒙开发环境准备,ArkTS基础语法入门

文章目录 鸿蒙开发环境准备&#xff0c;ArkTS基础语法入门大纲简介DevEco Studio简介运行环境要求 安装与配置开发工具下载Harmony OS 和 OpenHarmony 的区别Previewer汉化插件的配置 ArkTS基础快速入门1. 解释说明2. 变量与常量3. 变量命名规则4. 数组5. 函数定义函数调用函数…

Mini型LoRa DTU远距离无线传输“小体积大作为”

Mini型LoRa DTU&#xff08;数据传输单元&#xff09;CL61M凭借其小巧的体积、低功耗、远距离通信和高可靠性等特点&#xff0c;在远距离无线传输领域展现出了巨大的应用潜力。使RS485/232串口终端设备能够轻松实现十公里的远距离无线通信&#xff0c;适用于多种复杂环境&#…

(三)Kafka离线安装 - ZooKeeper开机自启

手动启动方式 一般通过指令手动来启动zookeeper的方法是&#xff0c;先进入到zookeeper的安装目录下的bin目录&#xff0c;然后执行启动指令。 cd /usr/local/zookeeper/zookeeper-3.8.4/bin/zkServer.sh start 停止指令 zkServer.sh stop 查看状态 zkServer.sh status 上…

如何在知行之桥上通过业务单号查找原始报文?

在知行之桥中接收或发送的数据通常是EDI原始报文&#xff0c;知行之桥会对EDI原始报文进行格式转换&#xff0c;以方便用户后端系统的处理。因此&#xff0c;一般情况下&#xff0c;用户看到的都是转换后的数据结构&#xff0c;例如Json、XML或Excel等&#xff0c;无需直接查看…

window上部署kafka3.6.1,并配置sasl认证

1 安装kafka 第一步安装kafka,并能成功启动&#xff0c;可参考文章Windows下安装Kafka3-CSDN博客 2 修改kafka的配置文件 server.properties是kafka的主要配置文件&#xff0c;里面有很多参数可以调整。 主要修改如下 listenersSASL_PLAINTEXT://127.0.0.1:9092 sasl.enable…

基于tkinter实现学生管理系统(四)

学生信息管理系统-修改学生 代码实现 在上一节中的class StudentManagerApp中添加如下方法&#xff1a; # 修改学生信息def modify_student(self):selection self.tree.selection()if not selection:messagebox.showwarning("警告", "请选择要修改的学员"…

04:创建PADS Logic软件逻辑库

1. 打开自带的库文件 2.保留common库&#xff0c;移除其他库文件 3.新建库 5点击封装工具栏 6选择2D线 7添加端点 8点击保存 9打开查看

SQLi-LABS靶场46-50通过攻略

less-46 1.判断注入点 ?sort1 页面出现报错 2.判断闭合方式 ?sort1 -- 3.查询数据库 因为页面有报错 所以使用报错注入 ?sort1 and updatexml(1,concat(1,database()),1)-- 4.查询数据库的所有表 ?sort1 and updatexml(1,concat(1,(select group_concat(table_name)…

【功能自动化】使用HTMLTestRunner生成测试报告

配置环境&#xff1a; 1.部署webtours网站 2.user.txt 3.HTMLTestRunner.py """ A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance.The simplest way to use this is to invoke it…

【Go高性能】测试(单元测试、基准测试)

Go测试 一、分类1. 单元测试2. 基准测试 二、基准测试1. 介绍2. 基准测试基本原则3. 使用testing包构建基准测试3.1 执行基准测试3.2 基准测试工作原理3.3 改进基准测试的准确性3.3.1 -benchtime3.3.2 -count3.3.3 -cpu 4. 使用benchstat工具比较基准测试(可跳过&#xff09;4.…

Leetcode 第 408 场周赛题解

Leetcode 第 408 场周赛题解 Leetcode 第 408 场周赛题解题目1&#xff1a;3232. 判断是否可以赢得数字游戏思路代码复杂度分析 题目2&#xff1a;3233. 统计不是特殊数字的数字数量思路代码复杂度分析 题目3&#xff1a;3234. 统计 1 显著的字符串的数量思路代码复杂度分析 题…

Pycharm can‘t open file ‘D:\\Program‘: [Errno 2] No such file or directory

问题描述 Pycharm 使用Python 3.11.9 版本调试代码报错&#xff1a; 解决方案 1、WindowsR&#xff0c;调起CMD&#xff08;PowerShell不行&#xff09;&#xff0c;执行以下指令&#xff1a; mklink /J "D:\PyCharm" "D:\Program Files\JetBrains\PyCharm 2…

react学习之useState和useEffect

useState useState 可以使函数组件像类组件一样拥有 state&#xff0c;函数组件通过 useState 可以让组件重新渲染&#xff0c;更新视图。 实际使用 setstate()中回调函数的返回值将会成为新的state值回调函数执行时&#xff0c; React会将最新的state值作为参数传递 const A…

Vulkan进阶系列1 - Raytracing 光线查询

一:概述 为了提高效率,光线追踪需要将几何体组织成加速结构(AS, 即Acceleration Structure),以减少渲染过程中光线与三角形的相交测试次数。这种层次结构通常在硬件中实现,但只有两个层级对用户可见:一个顶层加速结构(TLAS),它引用任意数量的底层加速结构(BLAS)。通…

Linux进程间的通信(一)exec函数族,getenv获取系统环境变量,system和popen的区别,文件和记录锁定通信

目录 几个系统关键api exec函数族 getenv() system() 文件和记录锁定通信 在Linux/Unix系统中&#xff0c;进程间通信方式&#xff08;Inter-Process Comunication&#xff09;通常有如下若干中方式&#xff1a; 1、文件和记录锁定 2、管道 3、信号 4、system-V 5、PO…

贪心算法三道经典题(买卖股票,分发饼干)

贪心算法 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 步骤&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 分发饼干 LeetCode原题 找满足孩子数量的最大值———最优解问题 什么…