🥪 写在前面
Hello朋友们😋,我是秋刀鱼🐟,一只活跃于Java区与算法区的新人博主~
欢迎大家加入高校算法学习社区🏰: https://bbs.csdn.net/forums/Suanfa,社区里大佬云集,大家互相交流学习!
从今天开始我将陆续更新《轻松拿捏大厂面试题》专栏文章,本专栏将挑选大厂出现频率极高的面试题做专题解读,本篇也是专栏的第一篇《反转链表篇》。
🎉🎉主页:秋刀鱼与猫🎉🎉 🎉🎉期待你的支持与关注~🎉🎉
🍥 目录
反转链表作为大厂一类高频面试题,相信可能会让很多人头疼😭,究其原因主要是对于链表结构把握不到位且没有完整梳理链表反转过程。
因此这篇博客主要分享一下我对于反转链表解题的理解,用画图的方式帮助大家理解过程,循序渐进地带领大家一步一步攻克这类难题😇
题目难度:⭐️⭐️
出现频率:38.38%
开始链表反转类问题之前,先阐述一下这类题的一般解题方法:
- 定义一个虚拟头结点,方便反转后链表头结点的返回。
- 找到需要操作的结点位置,使用修改
next的方式修改链表指向顺序达到反转的目的。话不多说就让我们开始吧😁
为了将给定的链表反转,首先需要指定当前遍历到的结点head,其次反转链表的本质是将 head 结点指向其前一个结点,因此存储 head 结点的前驱结点为 pre。既然 head 结点反转需要指前一个结点 pre ,那修改前 head 的下一个结点就存储在 next 中,保证遍历地进行。总结一下就是:
head:当前遍历的结点pre:head 结点的前一个结点,head为头结点时值为 null。next:反转 head 结点前的下一个结点下面先讲讲如何反转一个链表,假定反转前首先初始状态如下:

遍历的结点为 head ,反转的过程实际上是修改head.next为 head 的前一个元素pre,也就是执行head.next=pre。

可以看到:head 结点的 next 指向已经被修改,覆盖了原来的 next 指向 , 此时就体现出 next 指针的重要性。将 pre 指向 head, head 指向其修改前的下一个元素 next ,进入下一次修改的循环:

当 head 指向为 NULL 时,循环结束,此时的真正的头结点是 head 的前一个元素 pre ,因此将 pre 作为结果返回。
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
// 指向 head 的前一个元素
ListNode pre = null;
// 循环结束的条件为 head == null
while (head != null) {
// 用 next 保存修改前的下一个元素位置
ListNode next = head.next;
// 修改
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
题目传送门🌌
题目难度:⭐️⭐️⭐️
出现频率:37.36%
题目要求:只能反转一部分链表,剩余一部分链表顺序保持不变。在尝试了半个小时的挣扎后看着臃肿的代码与不知所名的变量,秋刀鱼不禁陷入了沉思:有什么好的方法能够简单、高效地解决这一类问题呢?
片刻后灵光一现!突然想到上一题中反转链表的代码与本题都是处理链表的反转,那上题的代码或许能派上用场!
反转链表代码中传入参数是一个链表的头结点,返回的是反转后的头结点。那只需要找到反转部分的头部结点作为参数传入后,返回的不就是反转后链表的头部结点了嘛!
但如果直接传入反转头结点进入函数中,那之后所有的结点都会被反转!因此将反转部分尾与其之后结点断开是一个不错的处理方式。
因此只需要做到下面几点就能够优雅地解决这道题目:
hpreHtafterT反转可能出现在头结点上,这种情况下 preH 的处理会相当的困难。可以使用一个链表中常见的方法:添加一个虚拟头结点preHead的方式,使得虚拟头结点做为原来头结点head的前驱结点,那么preHead.next就是需要返回的结果。
一图胜千言,下面这张图更好地阐述了上面的构思:

但仔细观察:反转之后 preH.next 与 h.next 指向明显出现错误,因此重新调整链表是不可缺少的一环。
根据上图不难发现:调整过程只需要将 preH.next 指向反转后的头结点 t ,h.next 指向 afterT 就能够完成调整!

当调整完成后,返回 preHead.next 即是新的头结点也是题目的答案。
class Solution {
// 上一题翻转链表的代码
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode pre = null;
while (head != null) {
ListNode next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
// 题目函数
public ListNode reverseBetween(ListNode head, int left, int right) {
// 虚拟头结点 preHead
ListNode preHead = new ListNode();
preHead.next = head;
ListNode preH, h, t, tmp, afterT;
// 初始化
t = afterT = preH = tmp = h = preHead;
// 位置参数,从0开始
int idx = 0;
while (tmp != null) {
// tmp 指向 preH
if (idx == left - 1) {
h = tmp.next;
preH = tmp;
}
// tmp 指向 t
if (idx == right) {
t = tmp;
afterT = tmp.next;
// 断开不需要翻转的部分
tmp.next = null;
break;
}
tmp = tmp.next;
++idx;
}
// 等价于 preH.next = reverseList(h);
reverseList(h);
preH.next = t;
//修改h.next的指向完成尾部的拼接
h.next = afterT;
return preHead.next;
}
}
题目传送门🌌
题目难度:⭐️⭐️⭐️⭐️
出现频率:39.76%
我相信大多数的朋友第一次遇见这题都是蒙圈的状态。题目要求每 K 组进行一次链表反转,长度不够 K 组的部分保留原来顺序,这怎么办处理才好呢还真是伤脑筋。
![]()
此时我们可以换换思路,用之前两道题的解题思路解决这道题目:每 K 组进行一次链表反转,是不是对应着反转 [ 1 , k ] , [ k + 1 , 2 k ] , [ 2 k + 1 , 3 k ] . . . [1,k],[k+1,2k],[2k+1,3k]... [1,k],[k+1,2k],[2k+1,3k]... 的链表元素呢,而上一题 反转链表 II 中处理的正好就是这种某段链表反转的问题!因此只需要借用上一题的代码就能够优雅地拿捏这道题。
为了方便处理首先定义一个头结点的前驱结点preHead,而每次传入 反转链表 II 参数是头结点,即传入preHead.next。定义一个计数器 idx 同时定义一个遍历链表的指针 tmp,tmp 开始时指向头结点 head ,例如下图所示。
idx 记录遍历次数tmp 遍历链表next
当计数器 idx%k==0时,tmp指向需要一组的最后一个结点,此时进行 K 个一组的反转操作,反转
[
i
d
x
−
k
+
1
,
k
]
[idx-k+1,k]
[idx−k+1,k] 链表结点。

第一次转置会导致preHead.next指向出错,因此反转后将新的头结点赋值给 preHead.next。当 tmp 指向为空时循环结束,最终返回preHead.next即是结果。

class Solution {
// 反转链表 代码
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode pre = null;
while (head != null) {
ListNode next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
// 反转链表II 代码
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode preHead = new ListNode();
preHead.next = head;
ListNode preH, h, t, tmp, afterT;
t = afterT = preH = tmp = h = preHead;
int idx = 0;
while (tmp != null) {
if (idx == left - 1) {
h = tmp.next;
preH = tmp;
}
if (idx == right) {
t = tmp;
afterT = tmp.next;
tmp.next = null;
break;
}
tmp = tmp.next;
++idx;
}
reverseList(h);
preH.next = t;
h.next = afterT;
return preHead.next;
}
// 题目函数
public ListNode reverseKGroup(ListNode head, int k) {
// preHead 声明为头结点的前驱结点
ListNode preHead = new ListNode();
preHead.next = head;
// tmp 遍历链表
ListNode tmp = head;
int idx = 0;
while (tmp != null) {
++idx;
ListNode next = tmp.next;
if (idx % k == 0) {
preHead.next = reverseBetween(preHead.next, idx - k + 1, idx);
}
tmp = next;
}
return preHead.next;
}
}
反转链表的高频题讲解到此就结束了,其核心还是抓住推导反转的过程,一步一步完成链表的反转操作,但是细节慢慢一定要小心谨慎(秋刀鱼本人就掉过很多次坑😥),因此一定要小心。
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested
我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我们有一个字符串:“”这个正则表达式://i如何从当前字符串中获取所有匹配项? 最佳答案 "".scan(//)参见scan在ruby-docs上 关于ruby-如何遍历Ruby中所有正则表达式匹配的字符串?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/6857852/
是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述
我们如何捕获或/和处理ruby中所有未处理的异常?例如,这样做的动机可能是将某种异常记录到不同的文件或发送电子邮件给系统管理。在Java中我们会做Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandlerex);在Node.js中process.on('uncaughtException',function(error){/*code*/});在PHP中register_shutdown_function('errorHandler');functionerrorHandler(){$error=error_
我有一个随机大小的散列,它可能有类似"100"的值,我想将其转换为整数。我知道我可以使用value.to_iifvalue.to_i.to_s==value来做到这一点,但我不确定我将如何在我的散列中递归地做到这一点,考虑到一个值可以是一个字符串,或一个数组(哈希或字符串),或另一个哈希。 最佳答案 这是一个非常简单的递归实现(尽管必须同时处理数组和散列会增加一些技巧)。deffixnumifyobjifobj.respond_to?:to_i#IfwecancastittoaFixnum,doit.obj.to_ielsifobj
我希望访问我机器上的所有HTTP流量(我的Windows机器-不是服务器)。据我了解,拥有一个本地代理是所有流量路线的必经之路。我一直在谷歌搜索但未能找到任何资源(关于Ruby)来帮助我。非常感谢任何提示或链接。 最佳答案 WEBrick中有一个HTTP代理(Rubystdlib的一部分)和here's一个实现示例。如果你喜欢生活在边缘,还有em-proxy伊利亚·格里戈里克。这postIlya暗示它似乎确实需要一些调整来解决您的问题。 关于ruby-如何捕获所有HTTP流量(本地代理)
我喜欢RVM。我意识到它的主要用例是让不同的用户在不同版本的Ruby之间切换。但是假设我正在将Rails应用程序部署到服务器,并且我只想运行单个版本的Ruby。特别是,我想要1.9.2,用RVM安装它很容易,但没有它就很痛苦。有没有一种方法可以让我说“我希望这是所有用户的规范Ruby安装”(连同它的所有gem),而不必手动创建一堆符号链接(symboliclink)并在每次更新到更新时更改它们Ruby版本? 最佳答案 以root身份安装RVM并执行sudorvmuse1.9.2--default。任何采购/usr/local/rvm