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

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

  • 首先训练题的来源呢有三个,首选的是三个都出现过的高频题,以:牛客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):递归最大的栈深度,

拓展:递归和迭代的区别

递归(Recursion)和迭代(Iteration)都是两种解决问题的方法,它们在实现方式和思维方式上有一些区别。

递归
递归是一种通过将问题分解成更小的子问题来解决问题的方法。在递归中,函数会调用自身来解决子问题,直到达到基本情况(递归终止条件),然后逐层返回结果,最终得到整个问题的解。递归通常在问题的结构具有递归性质时使用,例如树形结构、链式结构等。

优点:

  • 可以处理问题的复杂逻辑,将大问题分解为小问题。
  • 在某些情况下,代码相对简洁易懂。

缺点:

  • 递归可能会导致函数调用堆栈的增长,可能导致栈溢出。
  • 递归的性能可能不如迭代。
递归算法的空间复杂度 = 每次递归的空间复杂度 * 递归深度

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

迭代
迭代是通过循环控制来重复执行一系列操作,从而逐步逼近问题的解。在迭代中,通过在循环体内更新变量的值,逐步推进问题的求解,直到满足特定的条件为止。

优点:

  • 迭代通常比递归更节省内存,因为不需要保存多层的函数调用信息。
  • 性能更高,避免了函数调用的开销。

缺点:

  • 在某些情况下,代码可能相对复杂,特别是涉及到复杂的循环控制逻辑。

在选择使用递归还是迭代时,需要考虑问题的性质、可读性、性能等因素。有些问题更适合用递归解决,而其他问题则更适合用迭代解决。有时候,递归和迭代也可以结合使用,例如一些动态规划问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MAYA粒子基础_场

重力场 牛顿场 径向场 均匀场和重力场的区别 空气场 推动物体 阻力场 推动物体 涡流场 湍流场 体积轴场

研磨设计模式day12命令模式

目录 定义 几个参数 场景描述 代码示例 参数化设置 命令模式的优点 本质 何时选用 定义 几个参数 Command&#xff1a;定义命令的接口。 ConcreteCommand:命令接口的实现对象。但不是真正实现&#xff0c;是通过接收者的功能来完成命令要执行的操作 Receiver&#x…

kafka-python 消费者消费不到消息

排除步骤1&#xff1a; 使用group_id”consumer_group_id_001“ 和 auto_offset_reset"earliest" from kafka import KafkaConsumerconsumer KafkaConsumer(bootstrap_servers["dev-kafka01.test.xxx.cloud:9092"],enable_auto_commitTrue, auto_commit…

计算机安全学习笔记(I):访问控制安全原理

访问控制原理 从广义上来讲&#xff0c;所有的计算机安全都与访问控制有关。 RFC 4949: Internet Security Glossary, Version 2 (rfc-editor.org) RFC 4949 定义的计算机安全&#xff1a;用来实现和保证计算机系统的安全服务的措施&#xff0c;特别是保证访问控制服务的措施…

十几款拿来就能用的炫酷表白代码

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 表白代码 1、坐我女朋友好吗&#xff0c;不同意就关机.vbs2、坐我女朋友好吗&…

使用 Transformer 和 Amazon OpenSearch Service 构建基于列的语义搜索引擎

在数据湖中&#xff0c;对于数据清理和注释、架构匹配、数据发现和跨多个数据来源进行分析等许多操作&#xff0c;查找相似的列有着重要的应用。如果不能从多个不同的来源准确查找和分析数据&#xff0c;就会严重拉低效率&#xff0c;不论是数据科学家、医学研究人员、学者&…

C# 实现 国密SM4/ECB/PKCS7Padding对称加密解密

C# 实现 国密SM4/ECB/PKCS7Padding对称加密解密&#xff0c;为了演示方便本问使用的是Visual Studio 2022 来构建代码的 1、新建项目&#xff0c;之后选择 项目 鼠标右键选择 管理NuGet程序包管理&#xff0c;输入 BouncyCastle 回车 添加BouncyCastle程序包 2、代码如下&am…

npm和yarn的区别?

文章目录 前言npm和yarn的作用和特点npm和yarn的安装的机制npm安装机制yarn安装机制检测包解析包获取包链接包构建包 总结后言 前言 这一期给大家讲解npm和yarn的一些区别 npm和yarn的作用和特点 包管理&#xff1a;npm 和 yarn 可以用于安装、更新和删除 JavaScript 包。它们提…

识别图片中的文字

前言 PearOCR 是一款免费无限制网页版文字识别工具。 优点如下&#xff1a; 免费&#xff1a;完全免费&#xff0c;没有任何次数、大小限制&#xff0c;可以无限使用&#xff1b; 安全&#xff1a;全部数据本地运算&#xff0c;所有图片均不会被上传&#xff1b; 智能&#xf…

市场的新宠:4G智能手表

现在人们提到智能手表&#xff0c;健康监测、运动记录、接打电话等定是他不可或缺的功能&#xff0c;而其中通讯功能在绝大数多的智能手表上都是通过蓝牙实现的&#xff0c;需要让手表通过蓝牙连接到手机端来进行。在没有手机的情况下&#xff0c;配置再高的蓝牙智能手表也是“…

Kubernetes入门 十、HPA 自动扩/缩容

目录 概述安装metrics-server使用HPA 概述 我们已经可以通过手动执行 kubectl scale 命令实现Pod的扩缩容&#xff0c;但是这显然不符合 Kubernetes 的定位目标–自动化和智能化。Kubernetes 期望可以通过监测Pod的使用情况&#xff0c;实现 Pod 数量的自动调整&#xff0c;于…

成都瀚网科技:抖店如何经营?

作为热门的短视频分享平台&#xff0c;抖音不仅是一种娱乐工具&#xff0c;更是一个蕴藏着无限商机的电商平台。开店、抖音下单成为很多人的选择。那么&#xff0c;抖音如何开店、下单呢&#xff1f; 1、如何在抖音上开店和下单&#xff1f; 注册账号&#xff1a;首先&#xff…

5.网络原理之初识

文章目录 1.网络发展史1.1独立模式1.2网络互连1.3局域网LAN1.3.1基于网线直连1.3.2基于集线器组建1.3.3基于交换机组建1.3.4基于交换机和路由器组建1.3.4.1路由器和交换机区别 1.4广域网WAN 2.网络通信基础2.1IP地址2.2端口号2.3认识协议2.4五元组2.5 协议分层2.5.1 分层的作用…

SpringMVC-2-Spring MVC拦截器详解:从入门到精通

SpringMVC-2-Spring MVC拦截器详解&#xff1a;从入门到精通 今日目标 能够编写拦截器并配置拦截器 1.拦截器【理解】 1 拦截器介绍 1.1 拦截器概念和作用 拦截器&#xff08;Interceptor&#xff09;是一种动态拦截方法调用的机制&#xff0c;在SpringMVC中动态拦截控制器方…

上海亚商投顾:创业板指反弹大涨1.26% 核污染概念股午后全线走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日集体反弹&#xff0c;沪指午后冲高回落&#xff0c;创业板指盘中涨超2%&#xff0c;尾盘涨幅也有所收…

Electron学习3 使用serialport操作串口

Electron学习3 使用serialport操作串口 一、准备工作二、 SerialPort 介绍1. 核心软件包(1) serialport(2) serialport/stream(3) serialport/bindings-cpp(4) serialport/binding-mock(5) serialport/bindings-interface 2. 解析器包3. 命令行工具 三、创建一个demo程序1. 创建…

元类(metaclass)

目录 一、引言 二、什么是元类 三、为什么用元类 四、内置函数exec(储备) 五、class创建类 5.1 type实现 六、自定义元类控制类的创建 6.1 应用 七、__call__(储备) 八、__new__(储备) 九、自定义元类控制类的实例化 一十、自定义元类后类的继承顺序 十一、练习 p…

Go1.19 排序算法设计实践 经典排序算法对比

详解经典排序算法 01 为什么要学习数据结构与算法 抖音直播排行榜功能 案例 规则&#xff1a;某个时间段内&#xff0c;直播间礼物数TOP10房间获得奖励&#xff0c;需要在每个房间展示排行榜解决方案 •礼物数量存储在Redis-zset中&#xff0c;使用skiplist使得元素整体有序 •…

ImportError: cannot import name ‘SQLDatabaseChain‘ from ‘langchain‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

第三方检测检验实验室信息化建设

检测公司配置先进的信息化管理系统&#xff0c;信息化管理系统采用适宜的、先进的架构&#xff0c;具备开放性、扩展性、前瞻性、安全性等。先期建设按照实验室的规格及整体配套设施&#xff0c;整个实验室信息化系统的结构化数据考虑本地存储&#xff0c;且应考虑高速存储应用…