【力扣面试经典150题】(链表)K 个一组翻转链表

news2025/1/10 17:05:28

题目描述

力扣原文链接

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

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

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
请添加图片描述
提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:
你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
       	// here is your code 
    }
}

解题过程

解题思路

拿到的题目以后,应该尽量根据已知条件、函数的入参和返回值抓住变与不变的量、考虑边界条件、加之常用算法手段,如递归、迭代、双指针、回溯、分治、动态规划等等,从而创造一条完整链路,再考虑时间复杂度和空间复杂度的限制,问题得解。

回到本题,函数入参是一个自定义的ListNode,以及指定的(小于ListNode长度的)正整数用以翻转子链表,最终将新链表返回。所以核心问题有两点:

  1. 函数入参指定的链表是否存在一个或者多个长度为K的子链表?
  2. 如果存在长度为K的子链表,如何实现这个不断重复的翻转子链表的工作?

接下来把代码要实现的逻辑完整地梳理一遍:

针对以上的两个问题,我们最少要进行一次O(n) 时间复杂度的链表遍历,来确定是否存在合理值K。如果不存在直接返回原链表,因为无需翻转。这是最简单的情况。如果存在合理值K,那么怎么在O(1)空间复杂度的情况下保证子链表的翻转?以及翻转后与旧链表首尾节点的组装?

用一个简单实例说明:
假设链表为 1 -> 2 -> 3 ,K = 2,那么自然会脱口而出,2 -> 1 -> 3,这样看起来是不是很简单呢?
实际上处理过程同上面分析的一样,先判断是否含有K长度子链表,链表长度为3,K为2,当然符合条件,再把K长度子链表 1 -> 2 翻转成 2 -> 1,问题得以解决。

增加虚拟节点

通常地,在解决链表相关问题的时候,习惯性地在给定的链表头加一个节点,由于与题目无关,是我们虚构用来方便计算处理边界条件的,则把它称之为“虚拟节点”。⚠️注意,后面涉及链表相关的问题会常用到虚拟节点。

为了便于理解,现在以链表 1 -> 2 ->3 为例,画图说明:
请添加图片描述
这里我们将原来 链表 1- >2- >3 加上了一个虚拟节点,变成了 链表 -1 -> 1 -> 2 -> 3

至于为什么要加这个虚拟节点,下文在遍历链表的时候大有用处,我们会详细的说,现在只需要知道虚拟节点这个概念即可。

判断是否存在长度为K的子链表

回到实际问题,仍以链表 1 -> 2 ->3 为例,下图所示,每一个节点都有两个属性

  • val 当前节点值
  • next 下个节点
    请添加图片描述上图标注的整个链表,下文统一用head表示,head是从题目中函数入参拿到的哦
public ListNode reverseKGroup(ListNode head, int k)

首先我们在链表头部加上一个虚拟节点,并声明两个指针 prev 和 last 用来限定K长度子链表的边界。

请添加图片描述

因为我们在入参的head上加了头部的虚拟节点,又加了两个指针,因此我们重新定义个新的dummy链表。

新的dummy链表 -1 -> 1 -> 2 ->3 ,并附加了两个指针 last 和 prev。

//模拟代码
//声明新的dummy链表,比之前的head链表多加了一个虚拟节点,值为-1,指向head
ListNode dummy = new ListNode(-1, head);
//在dummy链表上声明last指针,注意这里没有开辟新的空间
ListNode prev = dummy;
//注意,以上两行代码可以简写,与操作8大基本类型数据的声明是一样的道理,刚接触链表的同学可能看着有点懵,需要细心体会
ListNode dummy = new ListNode(-1, head), prev = dummy;
//在dummy链表上声明prev指针,注意这里没有开辟新的空间
ListNode last = prev;

现在通过移动last指针,移动的长度就是K,所以会有这样的写法:

//模拟代码
forint i = 0;i < k;i++{
	//循环K次,每次移动last指针到下一个节点,因为是从虚拟节点开始移动,所以第一次移动后last一定指向dummy的第一个节点。
	last = last.next;
	//移动完要判断下一个节点是否为null,如果为null说明K循环未结束,而当前节点是末尾节点了,说明不足K个节点。直接返回dummy.next即可。
    if (last == null) {
		return dummy.next;
	}
}

通过K次循环last指针,判断dummy链表是否存在合理值K,直至last 为null
请添加图片描述

翻转长度为K的子链表

接着上面的实例,我们假设K=2,那么dummy链表的第一次K循环结束应该是这样的:
请添加图片描述

也就是说我们找到了符合K长度的子链表,接下来需要开始对子链表进行翻转了。

增加虚拟节点的好处
还记得前面我们买了一个伏笔吧!那就是为什么要在head原始链表头上加一个虚拟节点。看到这里我想你应该明白了,那就是

  • 翻转后的K长子链表需要与旧链表进行缝合,那么就需要知道旧链表的被切割处的节点位置,如果正好是前K个链表,那么翻转后只有尾部需要缝合,前面是没有节点的,难道要在翻转前单独判断无头有尾的特殊情况吗?而在head前面加了虚拟节点则正好解决了这个问题,无论K子链表在dummy哪里,一定都是与旧链表相连的、有头有尾的子链表。

请添加图片描述

  • 如果不存在K长子链表,则直接返回第1个节点就可以了,因为后面的节点会跟着第一个节点,这等于是对于入参head没有操作直接返回了。如果存在K长子链表,翻转后,那么从1到K已经进行了翻转,无论后面又翻转了多少个K子节点,返回的头节点就是K。所以两种情况又要单独判断了。但是加上虚拟节点,无论是否翻转,只需要返回dummy.next即可。

接下来我们看具体翻转K子链表的过程。思考为什么经过翻转后,仍只需要返回dummy.next?(不翻转可以理解,就是dummy.next)

首先要考虑翻转的子链表的起始节点和末尾节点。末尾节点就是last,起始节点应该是prev.next ,因为是动态变化的,需要新加一个指针,姑且称之为curr(意为当前节点),所以K长子链表长度应该从curr到last。

//这里curr的取值不能为 dummy.next,因为prev和curr是随着多个K长子链表动态变化的,而dummy则是一个固定的链表。
ListNode curr = prev.next;

请添加图片描述

确定了K长子链表,现在对子链表进行翻转,并将翻转后的片段拼接回dummy。
由于K长可变,试想若是存在合理值K=100,那么翻转一次吗?所以翻转的次数也是需要根据K长动态变化的。

// 比如当前假设情况,K=2 ,那么只需要循环一次即可。因为循环一次,翻转了2个节点。同理多个节点道理如此。
for (int i = 0; i < k - 1; i++) {

}

翻转实际上就是从K长度子链表的第一个节点curr与下一个节点next进行交换,直至交换到last结束。由于curr会不断在循环后刷新,所以next节点也是随curr节点动态变化的。

请添加图片描述

 ListNode next = curr.next;

现在我们要做的事情是交换curr节点与next节点,并保证交换后节点与dummy前后开口正确缝合。

  1. 首先切断curr与next的关联关系,让curr直接跳过next指向next的下一个节点。
//原来curr与next相连,现在这个操作相当于把curr的下个节点跳过了next节点,给到了next下一个节点。
curr.next = next.next;

请添加图片描述

  1. 将prev节点(K长子链表的头节点)的下一个节点也指向next的下一个节点。
//切断原来next的下一个节点的关联关系,因为上一步进行了1 -> 3 关联, 3节点无需多一个重复被指向,3节点必须是来自于curr的指向。所以把next节点重定向到prev后面。
next.next = prev.next;

请添加图片描述

  1. 将prev节点(K长子链表的头节点)重定向到next节点即可
prev.next = next;

请添加图片描述

  1. 将prev(K长子链表的头节点)指向下一轮要进行的K长翻转的头节点处

上面步骤实现了从curr到last的K长一次翻转动作,由于K长子链表需要不断在dummy中遍历寻找是否存在多个K,所以下一次循环K2的时候我们需要将K2的头节点指针重新定位。

//curr一定是K长子链表最末尾的一个节点,所以将prev指针移动到curr节点。
prev = curr;

第二次K循环由于last指针需要移动两次,但是节点3的next为空,所以直接返回prev.next了。

代码梳理

完整代码实现

 	public static ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(-1, head), prev = dummy;
        while (true) {
            // 检查剩余节点是否有k个,不足则返回
            ListNode last = prev;
            for (int i = 0; i < k; i++) {
                last = last.next;
                if (last == null) {
                    return dummy.next;
                }
            }
            // 翻转k个节点
            ListNode curr = prev.next, next;
            for (int i = 0; i < k - 1; i++) {
                next = curr.next;
                curr.next = next.next;
                next.next = prev.next;
                prev.next = next;
            }
            prev = curr;
        }
    }

    public static void main(String[] args) {
        ListNode list1 = new ListNode(1,new ListNode(2,new ListNode(3)));
        ListNode listNode = reverseKGroup(list1, 2);
        //链表遍历
        while (listNode != null) {
            System.out.println(listNode.val);
            listNode = listNode.next;
        }
    }

断点步骤拆解

GitHub代码地址

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

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

相关文章

“释放视频潜力,批量放大视频尺寸,高效提升视频质量“

在视频制作和编辑的过程中&#xff0c;我们经常需要调整视频的尺寸。然而&#xff0c;一个一个地手动调整不仅耗时&#xff0c;还容易出错。为了解决这个问题&#xff0c;现在有一款全新的视频批量剪辑工具&#xff0c;可以帮助你批量将视频尺寸放大&#xff0c;提升工作效率。…

Argo Rollouts结合Service进行Blue-Green部署

删除03 部署04 rootk8s-master01:~/learning-jenkins-cicd/09-argocd-and-rollout/rollout-demos# kubectl delete -f 03-rollouts-with-prometheus-analysis.yaml rootk8s-master01:~/learning-jenkins-cicd/09-argocd-and-rollout/rollout-demos# kubectl apply -f 04-rol…

C++多线程编程(3):接收线程处理函数的返回值

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 处理带返回值的函数asyncpackaged_taskpromise 处理带返回值的函数 有三种方法&#xff1a; asyncpackaged_taskpromise async 第一种方法是使用 async 函数。 步骤&#xff1a; 使用 async 创建线程处理函…

pom.xml格式化快捷键

在软件开发和编程领域&#xff0c;"格式化"通常指的是将代码按照一定的规范和风格进行排列&#xff0c;以提高代码的可读性和维护性。格式化代码有助于使代码结构清晰、统一&#xff0c;并符合特定的编码规范。 格式化可以包括以下方面&#xff1a; 缩进&#xff1a…

HR应用在线人才测评,给企业招聘带来的好处

一、什么是人才测评&#xff1f; 人才测评是指运用一系列的科学方法&#xff0c;对人的基本素质&#xff0c;专业能力&#xff0c;心理健康&#xff0c;性格进行选拔&#xff0c;评价及发展人才的一种科学方法。近十多年&#xff0c;它被广泛运用于国有大型企业的人才招聘和人…

策略模式在数据接收和发送场景的应用(升级版)

1.背景 在数据接收和发送场景打算使用了 if else 进行判断&#xff1a; if("A".equals(system)){ASystem.sync("向A同步数据"); } if("B".equals(system)){BSystem.sync("向B同步数据"); } ... 非常麻烦&#xff0c;需求多了很臃肿&…

AI对开发者职业的影响,保持领先的7 个行动指南

在不断发展的技术领域&#xff0c;人工智能(AI)已经成为一股变革性的力量&#xff0c;重塑了行业&#xff0c;重新定义了我们解决问题的方式。对于开发人员来说&#xff0c;学习AI的决定不仅仅是为了保持相关性&#xff0c;而是在他们的职业生涯中开启一个新的可能性维度。 1.…

Scalable Exact Inference in Multi-Output Gaussian Processes

Orthogonal Instantaneous Linear Mixing Model TY are m-dimensional summaries&#xff0c;ILMM means ‘Instantaneous Linear Mixing Model’&#xff0c;OILMM means ‘Orthogonal Instantaneous Linear Mixing Model’ 辅助信息 作者未提供代码

【接口测试】最细Fiddle抓包辅助接口实战,抓包全过程总结...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、为什么需要抓包…

Python开源项目周排行 2023年第38周

#2023年第38周2023年11月19日1easybc用于解析分组加密算法的输入 [EasyDC] 程序&#xff0c;然后根据差分密码分析对分组加密算法进行安全分析。它支持以下功能&#xff1a; EasyBC 的解释器。 基于 SMT 的方法&#xff0c;用于确定各种密码操作的分支数。 S-box 中的差分传播建…

C++多线程编程(1):线程的创建方式

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 进行与线程C中如何实现多线程创建线程的多种方式无参函数lambda表达式常成员函数not常成员引用函数智能指针仿函数类的普通成员函数综合测试 进行与线程 多线程是指多个线程并发执行的过程。 进程与线程的关系&…

WIFI版本云音响设置教程腾讯云平台版本

文章目录 WIFI版本云音响设置教程腾讯云平台版本一、申请设备三元素1.腾讯云物联网平台2.创建产品3.设置产品参数4.添加设备5.获取三元素 二、设置设备三元素1.打开MQTTConfigTools2.计算MQTT参数3.使用windows电脑的WIFI连接到设备热点4.设置参数 三、腾讯云物联网套件协议使用…

Linux基础全整理 从入门到放弃,一些想说的话

阅读目录 断更后一些想说的话用户useraddpasswdpasswd文件详解 chageusermoduserdelshadow 文件格式切换用户 用户组groupaddgroup文件格式groupmodgroupdel登陆远程机器 磁盘RAIDraid0&#xff08;安装系统&#xff09;raid1&#xff08;存放数据&#xff09;raid 5&#xff0…

盘点54个Python实用工具源码Python爱好者不容错过

盘点54个Python实用工具源码Python爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1OXyEh-Yy3JI90jvn6d6wRw?pwd8888 提取码&#xff1a;8888 项目名称 7z辅助破解工…

基于冠状病毒群体免疫算法优化概率神经网络PNN的分类预测 - 附代码

基于冠状病毒群体免疫算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于冠状病毒群体免疫算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于冠状病毒群体免疫优化的PNN网络5.测试结果6.参考文献7.Matlab代码 …

漂亮的pyqt6皮肤 PyOneDark_Qt_Widgets_Modern_GUIPublic

大家先看看界面图&#xff0c;真的很漂亮&#xff1a; github地址&#xff1a;GitHub - Wanderson-Magalhaes/PyOneDark_Qt_Widgets_Modern_GUI 作者还录了教程&#xff1a; TUTORIALS: Tutorial 01: https://youtu.be/QQGlTGYCMg0 Tutorial 02: https://youtu.be/LwKre2proDk…

Halcon (4):如何开始自学

文章目录 文章专栏前言Halcon文档Halcon基础案例文档英语阅读建议 结论 文章专栏 Halcon开发 前言 在我完成上一篇代码&#xff0c;halcon基础窗口事件写完了之后&#xff0c;我已经基本掌握了如何写一个简单的halcon程序。后面我学习新的知识的时候感觉遇到了瓶颈。因为网上没…

【C++】:模板进阶

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关C模板进阶的知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

『亚马逊云科技产品测评』活动征文|借助AWS EC2搭建服务器群组运维系统Zabbix+spug

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 Developer Centre, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道。 本文基于以下软硬件工具&#xff1a; aws ec2 frp-0.52.3 zabbix 6…

Web之JavaScript(jQuery)笔记

Web之HTML、CSS、JavaScript 三、JavaScriptJS调试变量自定义函数数据类型及转换运算符优先级内置函数数组事件DOM(Document Object Model 文档对象模型)jQuery Web之HTML笔记 Web之CSS笔记 三、JavaScript JavaScript&#xff08;简称“JS”&#xff09;是一种轻量级的面向对…