【算法训练-链表】反转链表、区间反转链表、K个一组反转链表

news2024/11/15 21:56:09

从今天开始进行高频算法的训练,一方面训练自己的逻辑思维,一方面保持自己的竞争力。训练过程有这么两个基准原则:

  • 首先训练题的来源呢有三个,首选的是三个都出现过的高频题,以:牛客101为基准分类,然后在CodeTop中找近一年内出现频率最多的牛客中该分类的题目,还有就是LeetCode热题100,这个按照最低参考度考虑。
  • 其次同一篇Blog里记录相关性较强的题,可以理解为普通-升级-再升级。例如当前这篇:反转链表-区间反转链表-K个一组反转链表

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!首先,链表对应的数据结构在这篇Blog中:【基本数据结构 一】线性数据结构:链表,基于对基础知识的理解来进行题目解答。
在这里插入图片描述

反转链表

首先来个最简单的反转链表

题干

在这里插入图片描述

输入输出示例:

输入:
{1,2,3}

返回值:
{3,2,1}
输入:
{}
复制

{}

说明:空链表则输出空      

解题思路

迭代方式,比较简单,就是用双指针一直向后滑动,将引用方向反转,特别需要注意的是,由于下一个节点会因为在反转过程中断掉,所以需要用临时节点记录下一个节点的位置。
在这里插入图片描述

解题代码

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        // 1 如果为空列表,返回null
        if (head == null) {
            return null;
        }

        // 2 定义双指针
        ListNode cur = head;
        ListNode pre = null;

        while (cur != null) {
            // 存储下一个节点
            ListNode  pNext = cur.next;
            // 当前节点指向上一个[局部反转]
            cur.next = pre;
            // 双指针向后移动
            pre = cur;
            cur= pNext;
        }
        return pre;
    }
}

时间复杂度O(N):遍历了一遍链表; 空间复杂度O(1):常数级空间

链表内指定区间反转

升级版,链表内指定区间进行反转

题干

在这里插入图片描述

输入:
{1,2,3,4,5},2,4

返回值:
{1,4,3,2,5}
输入:
{5},1,1

返回值:
{5}

解题思路

头插法迭代方式,增加如下几个节点或节点指针:

  • dummy 原始链表头节点的虚假头节点,为了避免当head节点位置改变且无明确位置的指针节点不知道该如何返回的情况
  • pre指向反转区间前一个节点
  • cur指向遍历到的位置(虽然他的位置在移动,但是其实一直指向的是同一个节点,即原始链表m处的节点)
  • pNext 指向cur下一个节点,反转过程就是将这个节点插入到pre的下一个位置

步骤主要分为两大步骤

  1. pre和cur同时向后走m步到达要反转的列表区间,pre指向反转区间前一个节点,cur为当前反转子区间的头节点
  2. 进行n-m次的节点反转,每次反转目标都是将pNext移动到pre的后边,分三步

这三个步骤是:

  1. cur.next = pNext.next;: cur指向下一个待转节点
  2. pNext.next=pre.next: pNext节点指向反转子区间的尾节点,成为反转子区间新的尾节点
  3. pre.next=pNext: pre指向反转子区间新的尾节点pNext

整体示意图如下:
在这里插入图片描述

解题代码

Java版本实现

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类
     * @param m int整型
     * @param n int整型
     * @return ListNode类
     */
    public ListNode reverseBetween (ListNode head, int m, int n) {
        if (head == null) {
            return null;
        }
        // 1 设置虚拟头节点[避免当head节点位置改变且无明确位置的指针节点不知道该如何返回的情况]
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        // 2 双指针向后移动m步
        ListNode pre = dummy;
        ListNode cur = head;
        for (int i = 1; i < m; i++) {
            pre = cur;
            cur = cur.next;
        }
        // 3 m到n的区间链表要进行反转[下一个节点和已反转的子区间整体进行反转]
        for (int i = m; i < n; i++) {
            // 1 pNext 指向cur下一个节点,反转过程就是将这个节点插入到pre的下一个位置
            ListNode pNext = cur.next;
            // 2 cur指向下一个待转节点
            cur.next = pNext.next;
            // 3 pNext节点指向反转子区间的尾节点,成为反转子区间新的尾节点
            pNext.next = pre.next;
            // 4 pre指向反转子区间新的尾节点pNext
            pre.next = pNext;
        }

        return dummy.next;
    }
}

时间复杂度O(N):遍历了一遍链表; 空间复杂度O(1):常数级空间

链表中的节点每k个一组翻转

ok,接下来难度再升级

题干

在这里插入图片描述

输入:
{1,2,3,4,5},2

返回值:
{2,1,4,3,5}
输入:
{},1

返回值:
{}

解题思路

递归方式,我们这时候可以用到自上而下再自下而上的递归或者说栈。如果这个链表有n个分组可以反转,我们首先对第一个分组反转,那么接下来将剩余n-1个分组,n−1个分组反转后的结果接在第一组后面就行了,那这剩余的n−1组就是一个子问题。使用递归的三段式模版:

  • 终止条件: 当进行到最后一个分组,即不足k次遍历到链表尾(0次也算),就将剩余的部分直接返回。
  • 返回值: 每一级要返回的就是翻转后的这一分组的头,以及连接好它后面所有翻转好的分组链表。
  • 本级任务: 对于每个子问题,先遍历k次,找到该组结尾在哪里,然后从这一组开头遍历到结尾,依次翻转,结尾就可以作为下一个分组的开头,而先前指向开头的元素已经跑到了这一分组的最后,可以用它来连接它后面的子问题,即后面分组的头。

具体做法:

  • 步骤一:每次从进入函数的头节点优先遍历链表k次,分出一组,若是后续不足k个节点,不用反转直接返回头。
  • 步骤二:从进入函数的头节点开始,依次反转接下来的一组链表,反转过程直接使用链表反转的算法。
  • 步骤三:这一组经过反转后,原来的头变成了尾,后面接下一组的反转结果,下一组采用上述递归继续。

整个过程如图所示:
在这里插入图片描述

解题代码

Java版本实现

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param head ListNode类
     * @param k int整型
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        // 1 定义分组尾节点
        ListNode tail = head;
        // 2 划分本级处理的区间反转子任务
        for (int i = 0; i < k; i++) {
            if (tail == null) {
                // 2-1 任务停止条件是区间不足K个节点,此时直接返回这些节点即可
                return head;
            }
            tail = tail.next;
        }
         // 3 处理本级区间反转任务
        ListNode newHead = reverse(head, tail);
        //  4 剩余区间继续递归反转,直到全部反转
        head.next = reverseKGroup(tail, k);
        return newHead;
    }

    private ListNode reverse(ListNode head, ListNode tail) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != tail) {
            ListNode pNext = cur.next;
            cur.next = pre;
            pre = cur;
            cur = pNext;
        }
        return pre;
    }
}

时间复杂度O(N):遍历了一遍链表; 空间复杂度O(N/K):递归最大的栈深度,拓展了解下:

递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度

为什么要求递归的深度呢?因为每次递归所需的空间都被压到调用栈里(这是内存管理里面的数据结构,和算法里的栈原理是一样的),一次递归结束,这个栈就是就是把本次递归的数据弹出去。所以这个栈最大的长度就是递归的深度。

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

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

相关文章

渗透测试工具ZAP入门教程(2)-HUD教程

平视显示器 HUD是一种全新的与ZAP进行交互的方式。 它将安全信息叠加到你正在测试的应用程序上&#xff0c;并允许你访问关键的ZAP功能。 对于刚接触安全的人来说&#xff0c;它更易于理解&#xff0c;但同时也允许经验丰富的渗透测试人员将重点放在他们正在测试的应用程序上。…

计算机网络(速率、宽带、吞吐量、时延、发送时延)

速率&#xff1a; 最重要的一个性能指标。 指的是数据的传送速率&#xff0c;也称为数据率 (data rate) 或比特率 (bit rate)。 单位&#xff1a;bit/s&#xff0c;或 kbit/s、Mbit/s、 Gbit/s 等。 例如 4 1010 bit/s 的数据率就记为 40 Gbit/s。 速率往往是指额定速率或…

图床项目进度(二)——动态酷炫首页

前言&#xff1a; 前面的文章我不是说我简单copy了站友的一个登录页吗&#xff0c;我感觉还是太单调了&#xff0c;想加一个好看的背景。 但是我前端的水平哪里够啊&#xff0c;于是在网上找了找制作动态背景的插件。 效果如下图。 如何使用 这个插件是particles.js 安装…

vue2 自定义指令,插槽

一、学习目标 1.自定义指令 基本语法&#xff08;全局、局部注册&#xff09;指令的值v-loading的指令封装 2.插槽 默认插槽具名插槽作用域插槽 二、自定义指令 1.指令介绍 内置指令&#xff1a;v-html、v-if、v-bind、v-on… 这都是Vue给咱们内置的一些指令&#xff0c;…

代码随想录第31天|认识贪心算法,455.分发饼干,376. 摆动序列,53.最大子数组和

贪心的介绍 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。 例如&#xff0c;有一堆钞票&#xff0c;你可以拿走十张&#xff0c;如果想达到最大的金额&#xff0c;你要怎么拿&#xff1f; 指定每次拿最大的&#xff0c;最终结果就是拿走最大数额的钱。…

Ansible 生成硬件报告

生成硬件报告 创建一个名为 /home/greg/ansible/hwreport.yml 的 playbook &#xff0c;它将在所有受管节点上生成含有以下信息的输出文件 /root/hwreport.txt &#xff1a; 清单主机名称 以 MB 表示的总内存大小 BIOS 版本 磁盘设备 vda 的大小 磁盘设备 vdb 的大小 输出文件中…

c语言练习题34:打印整数二进制的奇数位和偶数位

打印整数二进制的奇数位和偶数位 获取一个整数二进制序列中所有的偶数位和奇数位&#xff0c;分别打印出二进制序列 思路&#xff1a; 1. 提取所有的奇数位&#xff0c;如果该位是1&#xff0c;输出1&#xff0c;是0则输出0 2. 以同样的方式提取偶数位置检测num中某一位是0还…

【Java基础】Java注解与反射

文章目录 ⭐️写在前面的话⭐️1、什么是注解&#xff1f;注解的分类常用的Java注解 2、元注解TargetRetentionDocumentedInherited 3、自定义注解Override注解的基本格式 4、什么是反射&#xff1f;什么时候需要用到反射&#xff1f;反射的应用场合 5、反射的原理6、反射机制的…

大数据-玩转数据-Flink窗口函数

一、Flink窗口函数 前面指定了窗口的分配器, 接着我们需要来指定如何计算, 这事由window function来负责. 一旦窗口关闭, window function 去计算处理窗口中的每个元素. window function 可以是ReduceFunction,AggregateFunction,or ProcessWindowFunction中的任意一种. Reduc…

Kafka为什么这么快?

Kafka 是一个基于发布-订阅模式的消息系统&#xff0c;它可以在多个生产者和消费者之间传递大量的数据。Kafka 的一个显著特点是它的高吞吐率&#xff0c;即每秒可以处理百万级别的消息。那么 Kafka 是如何实现这样高得性能呢&#xff1f;本文将从七个方面来分析 Kafka 的速度优…

【ARMv8 SIMD和浮点指令编程】NEON 乘法指令——乘法知多少?

NEON 乘法指令包括向量乘法、向量乘加和向量乘减,还有和饱和相关的指令。总之,乘法指令是必修课,在我们的实际开发中会经常遇到。 1 MUL (by element) 乘(向量,按元素)。该指令将第一个源 SIMD&FP 寄存器中的向量元素乘以第二个源 SIMD&FP 寄存器中的指定值,将…

机器学习策略——优化深度学习系统

正交化&#xff08;Orthogonalization&#xff09; 老式电视机&#xff0c;有很多旋钮可以用来调整图像的各种性质&#xff0c;对于这些旧式电视&#xff0c;可能有一个旋钮用来调图像垂直方向的高度&#xff0c;另外有一个旋钮用来调图像宽度&#xff0c;也许还有一个旋钮用来…

基于SpringBoot实现MySQL与Redis的数据最终一致性

问题场景 在并发场景下&#xff0c;MySQL和Redis之间的数据不一致性可能成为一个突出问题。这种不一致性可能由网络延迟、并发写入冲突以及异常情况处理等因素引起&#xff0c;导致MySQL和Redis中的数据在某些时间点不同步或出现不一致的情况。数据一致性问题的级别可以分为三…

《深入理解Java虚拟机》读书笔记:方法调用

方法调用并不等同于方法执行&#xff0c;方法调用阶段唯一的任务就是确定被调用方法的版本&#xff08;即调用哪一个方法&#xff09;&#xff0c;暂时还不涉及方法内部的具体运行过程。在程序运行时&#xff0c;进行方法调用是最普遍、最频繁的操作&#xff0c;但前面已经讲过…

Nginx详解 一:编译安装Nginx和Nginx模块

文章目录 1.HTTP 和 Nginx1.1 Socket套接字1.2 HTTP工作机制1.2.1一次http事务1.2.2 资源类型1.2.3提高HTTP连接性能 2. I/O模型2.1 I/O模型相关概念2.2 网络I/O模型2.2.1 **阻塞型** **I/O** 模型&#xff08;blocking IO&#xff09;2.2.2 **非阻塞型** **I/O** **模型** **(…

在React项目是如何捕获错误的?

文章目录 react中的错误介绍解决方案后言 react中的错误介绍 错误在我们日常编写代码是非常常见的 举个例子&#xff0c;在react项目中去编写组件内JavaScript代码错误会导致 React 的内部状态被破坏&#xff0c;导致整个应用崩溃&#xff0c;这是不应该出现的现象 作为一个框架…

Java基础 数据结构一【栈、队列】

什么是数据结构 数据结构是计算机科学中的一个重要概念&#xff0c;用于组织和存储数据以便有效地进行访问、操作和管理。它涉及了如何在计算机内存中组织数据&#xff0c;以便于在不同操作中进行查找、插入、删除等操作 数据结构可以看作是一种数据的组织方式&#xff0c;不…

[maven]关于pom文件中的<relativePath>标签

关于pom文件中的<relativePath>标签 为什么子工程要使用relativePath准确的找到父工程pom.xml.因为本质继承就是pom的继承。父工程pom文件被子工程复用了标签。&#xff08;可以说只要我在父工程定义了标签&#xff0c;子工程就可以没有&#xff0c;因为他继承过来了&…

Kotlin数据结构

数据结构基础 什么是数据结构 在计算机科学中&#xff0c;数据结构&#xff08;Data Structure&#xff09;是计算机中存储、组织数据的方式。数据结构是各种编程语言的基础。 一些使用场景 不同的数据结构适用于不同的应用场景。比如HashMap与ConcurrentHashMap&#xff0…