轻松搞定链表反转
最近迫于生活,又去面试咯。好在魔都的就业环境还可以,面试机会总是不缺。今天闲下来了,来谈一谈我最近面试遇到的一道题,是跟反转链表相关的。
题目很简洁:给定一个链表的head跟数字k,反转从head开始的交替间隔的大小为k的子列表。
也就是说,我反转k个节点之后,跳过k个节点,再反转k个节点,以此类推。反转一个链表不复杂,但是通常我们遇到的题目会附加额外条件,比如:不允许开辟额外的内存空间。
我们先来看看反转一个链表我们会怎么做,我们的思路很直接,从头开始依次取节点,让它指向前面一个节点,到最后我们的链表就反转完成了。代码跟口述过程一样简单,我们直接来上代码:
public static ListNode reverse(ListNode head) {
ListNode current = head; // 记录当前要被处理的节点
ListNode previous = null; // 记录之前被处理的节点
ListNode next = null; // 临时存储下一个节点
while (current != null) {
next = current.next;
current.next = previous; // 当前节点指向前一个结点
previous = current; // 更新previous的位置
current = next; // 向后移动当前节点
}
// 循环结束后,previous指向原链表的最后一个节点,此时它已经成为反转后链表的head
return previous;
}
我们只要保存好下一个要处理的节点的索引,保证可以按顺序迭代,之后按照正常思路反转引用就好。
好了,有了这道题的基础,我们来增加一些难度,现在给定一个数字k,反转链表中每个长度为k的子列表。
也就是说,当k=2时,给我们一个链表1->2->3->4,反转后变成2->1->4->3。现在看起来无从下手,其实跟上题区别不大,我们只要关注每过k个节点重新开始一轮反转,其它的流程照旧即可。
public static ListNode reverse(ListNode head, int k) {
if (k <= 1 || head == null)
return head;
ListNode current = head, previous = null;
while (true) {
ListNode lastNodeOfPreviousPart = previous;
// 反转结束之后,current就是子列表的最后一个节点
ListNode lastNodeOfSubList = current;
ListNode next = null; // 用来临时存储下一个节点
//反转k个节点
for (int i = 0; current != null && i < k; i++) {
next = current.next;
current.next = previous;
previous = current;
current = next;
}
//跟前面的部分链接
if (lastNodeOfPreviousPart != null)
lastNodeOfPreviousPart.next = previous; // previous现在是子列表的第一个节点
else // 这意味着我们在处理第一个子列表
head = previous;
//跟下一部分链接
lastNodeOfSubList.next = current;
if (current == null) //到达最后,结束循环
break;
// 为下一个子列表做准备
previous = lastNodeOfSubList;
}
return head;
}
其实不管是要求我们反转一个子列表还是多个子列表,都没问题。反转多个子列表的时候,反转过程是一样的,需要注意的点无非是在各个子列表反转后的前后链接,我们只要记住每个子列表开头跟结束的节点,在反转完成的时候进行链接就好了。
现在我们可以回到开头的问题了,这道题跟上面那道区别也很小哇,好巧啊!(手动狗头?,我才不说我是故意的)这边唯一的区别在于我们得跳过K个节点。管它呢,我们还可以遵循上面那个流程,只不过在每次迭代后,跳过k节点,这不就行了嘛!
public static ListNode reverse(ListNode head, int k) {
if (k <= 1 || head == null)
return head;
ListNode current = head, previous = null;
while (true) {
ListNode lastNodeOfPreviousPart = previous;
//反转结束后,它就是子列表的最后一个节点
ListNode lastNodeOfSubList = current;
ListNode next = null; // 用来临时存储下一个节点
// 反转k个节点
for (int i = 0; current != null && i < k; i++) {
next = current.next;
current.next = previous;
previous = current;
current = next;
}
// 跟前面的部分链接
if (lastNodeOfPreviousPart != null)
lastNodeOfPreviousPart.next = previous;
// 'previous'现在是子列表的第一个节点
else //
head = previous;
// 与后面的部分链接
lastNodeOfSubList.next = current;
// 跳过k个节点
for (int i = 0; current != null && i < k; ++i) {
previous = current;
current = current.next;
}
if (current == null) // 到最后了,结束循环
break;
}
return head;
}
只要在最后加上跳过的逻辑,齐活儿!整个过程中,我们只迭代一次链表,时间复杂度为O(n),而因为我们没有开辟额外的内存空间,空间复杂度为O(1)。
到这里可以发现,反转链表的问题一点也不难,很直接很暴力,也没有像其他问题所说的什么奇技淫巧,按照自己常规思路来就好咯。变来变去无非是变着花样反转子列表,只要大家想明白了它就变不出啥幺蛾子了。
- GreenPlum 简单性能测试与分析(续)
- 最终版 Reflector v1.0 (+简单的反流程混淆)
- 性能&分布式&NewLife.XCode对无限数据的支持
- ASP.NET MVC下的异步Action的定义和执行原理
- 包学会之浅入浅出Vue.js:结业篇
- 迈克尔•戴尔:人工智能杀手?技术反乌托邦?不存在的
- 你知道吗?多个类多线程环境下静态构造函数的执行顺序
- 云端架构师养成之三:微信也在用的消息队列服务
- 现在 tensorflow和mxnet 很火,是否还有必要学习 scikit-learn 等框架?
- ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
- 改进版CodeTimer及XCode性能测试
- 常见测试术语解析
- 秦俊:开放 DevOps 敏捷开发套件,助力开发者驰骋云端
- 开源组件NanUI一周年-使用HTML/CSS/JS来构建.Net Winform应用程序界面
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- java+testNG测试框架搭建(接口测试或者ui测试)
- 解决Selenium testNG执行测试时,每个测试方法都打开一个浏览器窗口的问题
- selenium元素定位中css或者xpath不选择某一类元素
- TRTC横竖屏切换
- Swift 元祖
- Flutter - 解决混合开发iOS脚本打包遇到的问题
- Shader 特效 —— Film Burn (炫光光晕)效果【GLSL】
- java selenium chromedriver浏览器驱动放在哪里?【两种位置】
- 56. Vue原生js的组件拆分结构设计
- 一步一步教你把 Redux Saga 添加到 React&Redux 程序中
- Octave的基本语句及函数的使用入门—ML Note 31
- JAVA的Lock锁接口实现
- 抽象语法树为什么抽象
- burpsuite IP伪造插件
- 用阻塞队列,再系一次鞋带