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

题目描述

力扣原文链接

给你链表的头节点 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 。

  • last用来限定K长度子链表的边界。
  • prev用来作为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代码地址

https://github.com/harrypottry/leetcode

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

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

相关文章

技术分享 | 如何写好测试用例?

对于软件测试工程师来说&#xff0c;设计测试用例和提交缺陷报告是最基本的职业技能。是非常重要的部分。一个好的测试用例能够指示测试人员如何对软件进行测试。在这篇文章中&#xff0c;我们将介绍测试用例设计常用的几种方法&#xff0c;以及如何编写高效的测试用例。 ## 一…

vue和uni-app的递归组件排坑

有这样一个数组数据&#xff0c;实际可能有很多级。 tree: [{id: 1,name: 1,children: [{ id: 2, name: 1-1, children: [{id: 7, name: 1-1-1,children: []}]},{ id: 3, name: 1-2 }]},{id: 4,name: 2,children: [{ id: 5, name: 2-1 },{ id: 6, name: 2-2 }]} ]要渲染为下面…

春秋云境靶场CVE-2022-30887漏洞复现(任意文件上传漏洞)

文章目录 前言一、CVE-2022-30887描述和介绍二、CVE-2021-41402漏洞复现1、信息收集2、找可能可以进行任意php代码执行的地方3、漏洞利用找flag 总结 前言 此文章只用于学习和反思巩固渗透测试知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随…

C语言中的指针(上)

目录 一、基本概念 1.变量的存储空间 2.定义指针 3.引用与解引用 二、指针的算术运算、类型以及通用指针 1.指针的算数运算 2.指针类型以及通用型指针 三、指向指针的指针&#xff08;pointers to pointers&#xff09; 四、函数传值以及传引用 1.局部变量 2.从存储地…

gitlab环境准备

1.准备环境 gitlab只支持linux系统&#xff0c;本人在虚拟机下使用Ubuntu作为操作系统&#xff0c;gitlab镜像要使用和操作系统版本对应的版本&#xff0c;(ubuntu18.04,gitlab-ce_13.2.3-ce.0_amd64 .deb) book100ask:/$ lsb_release -a No LSB modules are available. Dist…

机器学习二元分类 二元交叉熵 二元分类例子

二元交叉熵损失函数 深度学习中的二元分类损失函数通常采用二元交叉熵&#xff08;Binary Cross-Entropy&#xff09;作为损失函数。 二元交叉熵损失函数的基本公式是&#xff1a; L(y, y_pred) -y * log(y_pred) - (1 - y) * log(1 - y_pred)其中&#xff0c;y是真实标签&…

中贝通信-603220 三季报分析(20231120)

中贝通信-603220 基本情况 公司名称&#xff1a;中贝通信集团股份有限公司 A股简称&#xff1a;中贝通信 成立日期&#xff1a;1999-12-29 上市日期&#xff1a;2018-11-15 所属行业&#xff1a;软件和信息技术服务业 周期性&#xff1a;1 主营业务&#xff1a;通信网络技术服务…

通信网络安全防护定级备案流程介绍(附流程图)

通信网络安全防护定级备案是拥有增值电信业务经营许可证并且有开展电信业务的企业要做的一件事情。刚接触这块的家人们在填报操作的时候可能对具体通信网络安全防护定级备案流程还不是很清楚&#xff0c;所以就给大家画张具体的流程图吧&#xff0c;可以更加直观的了解。 通信…

go语言学习-go环境安装

1、安装Go 1.1 下载安装 go官网 找对应电脑的版本进行安装即可。 点击安装包&#xff0c;直接下一步下一步即可&#xff0c;安装目录可以自行设置一下。 1.2 验证 windows通过cmd验证。 linux或者mac可以通过自带终端执行测试。 2、配置环境变量 2.1 windows 找到系统…

HarmonyOS开发(四):UIAbility组件

1、UIAbility概述 UIAbility 一种包含用户界面的应用组件用于与用户进行交互系统调度的单元为应用提供窗口在其中绘制界同 注&#xff1a;每一个UIAbility实例&#xff0c;都对应一个最近任务列表中的任务。 一个应用可以有一个UIAbility也可以有多个UIAbility。 如一般的…

深度学习YOLO安检管制物品识别与检测 - python opencv 计算机竞赛

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov55 模型训练6 实现效果7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习YOLO安检管制误判识别与检测 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&…

【论文阅读】SPARK:针对视觉跟踪的空间感知在线增量攻击

SPARK: Spatial-Aware Online Incremental Attack Against Visual Tracking introduction 在本文中&#xff0c;我们确定了视觉跟踪对抗性攻击的一个新任务&#xff1a;在线生成难以察觉的扰动&#xff0c;误导跟踪器沿着不正确的&#xff08;无目标攻击&#xff0c;UA&#x…

设计模式--模板方法外观模式

模板方法模式 场景&#xff1a;需使用代码方式实现&#xff0c;考完试后&#xff0c;将各个学生的试卷及答案誊抄一份。 假如有两个学生的试卷誊抄完毕. // 学生A public class TestPaperA {// 试题1public void testQuestion1() {System.out.println("问题一:XXXXXXXX…

《opencv实用探索·一》QT+opencv实现图片拼接和Mat转QImage

本文利用opencv实现了几个好用的功能&#xff0c;包含两个文件&#xff0c;如下&#xff1a; 源码放在文章末尾 imageProcessing类包含三个功能&#xff1a; 1、图像拼接 cv::Mat imageMosaic(cv::Mat mat1, cv::Mat mat2, MosaicMode mosaicMode);mat1和mat2为两个待拼接的…

Matplotlib实现Label及Title都在下方的最佳姿势

Matplotlib实现Label及Title都在下方的最佳姿势 1. 问题背景2. 基本思想&#xff08;可以不看&#xff09;3. 方法封装4. 调用实例5. 总结6. 起飞 1. 问题背景 用python绘制下面这种图的时候&#xff0c;一般用xlable作为子图的标题&#xff0c;这是因为plt.title()方法绘制的…

Android studio run 手机或者模拟器安装失败,但是生成了debug.apk

错误信息如下&#xff1a;Error Installation did not succeed. The application could not be installed&#xff1a;List of apks 出现中文乱码&#xff1b; 我首先尝试了打包&#xff0c;能正常安装&#xff0c;再次尝试了debug的安装包&#xff0c;也正常安装&#xff1…

再谈谷歌GMS认证之Android 13

写在前面的话 2023年来到一个新的公司&#xff0c;传说中的做互联网金融即将上市的高大上公司。 入职后才发现就是做pos机设备的一个小厂 哎&#xff0c;什么命啊&#xff01; 工作和手机开发的工作重合度可以达到95%以上&#xff0c;我不想做手机&#xff0c;偏偏又干上…

计算机基础知识54

ORM的介绍 # ORM是什么&#xff1f; 我们在使用Django框架开发web应用的过程中&#xff0c;不可避免地会涉及到数据的管理操作&#xff08;增、删、改、查&#xff09;&#xff0c;而一旦谈到数据的管理操作&#xff0c;就需要用到数据库管理软件&#xff0c;例如mysql、oracle…

Ubuntu20.0中安装Gradle

下载Gradle到temp文件夹 wget https://services.gradle.org/distributions/gradle-8.3-bin.zip -P /tmp 然后解压文件到/opt/gradle目录 sudo unzip -d /opt/gradle /tmp/gradle-8.3.zip 配置Gradle环境变量 接下来我们会创建一个gradle.sh文件来保存Gradle的环境变量 sudo…

ubuntu20.04蓝牙连接airpods

ubuntu20.04蓝牙连接airpods 解禁蓝牙安装blueman设置模式连接上没有声音的问题 解禁蓝牙 sudo rmmod btusb sleep 1 sudo modprobe btusb sudo /etc/init.d/bluetooth restart安装blueman sudo apt install blueman sudo apt-get install pulseaudio-module-bluetooth sudo …