当前位置: 首页 > news >正文

C++ 面试高频题 链表 模拟 力扣 143. 重排链表 题解 每日一题

文章目录

  • 题目描述
  • 为什么这道题值得我们花些时间看完?
  • 题目分析
  • 算法原理
  • 代码实现思路
    • 整体代码
    • 直接从中间节点进行逆序的思路与代码
    • 总结
    • 下题预告

在这里插入图片描述
在这里插入图片描述

题目描述

题目链接:力扣 143. 重排链表
题目描述:给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:
在这里插入图片描述
输入:head = [1,2,3,4]
输出:[1,4,2,3]

示例 2:
在这里插入图片描述
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

提示:
链表的长度范围为 [1, 5 * 104]
1 <= node.val <= 1000

为什么这道题值得我们花些时间看完?

这道题是面试中链表考察的“黄金综合题”,看似只是“重排节点”,实则把链表三大核心操作(找中点、反转、合并)无缝整合,能一次性检验你的链表功底是否扎实。它的价值远不止“做对一道题”,而是能帮你吃透面试高频的关键能力,性价比极高。

1. 一次性攻克链表三大核心操作,效率拉满
这道题没有新知识点,却要求你熟练串联“找中点”“反转链表”“合并链表”三个基础操作。单独做这三个基础题不难,但要在一道题里连续调用、衔接无误,特别考验对每个操作的细节把控——比如找中点时快慢指针的起始位置、反转链表时的指针交接、合并时的节点顺序对齐。搞定它,相当于一次完成三次专项训练,后续遇到单独考察这三个操作的题目,都能直接复用思路。

2. 暴露指针操作的“隐形短板”,精准补漏
链表题的核心难点永远是“指针控制”,这道题把这点放大到极致。从找中点时的快慢指针同步移动,到反转后的链表与原链表节点的交叉合并,每一步都需要精准控制多个指针的指向变更,稍微疏忽就会出现空指针或节点丢失。很多人觉得自己懂链表,但做这道题时容易在“衔接处”翻车,而这些翻车点正是面试中的高频扣分点,提前踩坑、复盘,能帮我们避免面试或笔试上犯同类错误

3. 贴合面试“综合应用”的考察趋势,实用性强
现在面试早已不满足于考察单一操作,更倾向于“多操作组合”的综合题——这道题完美贴合这个趋势。它常以原题或变种题形式出现在大厂面试中,面试官不仅看我们能否做对,还会追问“如何优化找中点效率”“合并时能否不占用额外空间”等问题,考察我们的逻辑严谨性和优化思维。花时间吃透它,相当于提前适配面试的考察模式,遇到同类综合题能快速反应。

4. 强化“复杂问题拆解”的核心思维
这道题的解题关键是“拆解问题”:把“重排链表”拆成“找中点→分割→反转后半段→合并”四个小步骤,再逐个突破。这种“大问题拆小、逐个解决”的思维,是算法设计的核心,不仅适用于链表题,更能迁移到数组、二叉树等各类复杂问题中。通过这道题刻意练习拆解思维,能帮我们养成“化繁为简”的解题习惯,应对更难的面试题时更有底气。

题目分析

给定一个单链表 L 的头节点 head ,单链表 L 表示为:L0 → L1 → … → Ln - 1 → Ln,请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …

简单来说就是将已知链表一前一后的重新排列:
1->2->3->4->5 为例:

  • 原节点顺序:L0=1、L1=2、L2=3、L3=4、L4=5
  • 重排后顺序:1(L0)→5(L4)→2(L1)→4(L3)→3(L2)

如下图👇:
在这里插入图片描述
并且核心要求是不能仅修改节点值,必须通过调整节点的指针指向实现实际的节点交换,且要保证操作后链表无环、无节点丢失,同时需适配链表长度为奇数或偶数的情况。解题的关键在于把“重排”这个复杂问题,拆解成多个可执行的基础链表操作,再逐步落地。

算法原理

这道题不难想算法原理,就是模拟整个过程,但是一前一后的来回访问节点对于顺序表来说轻轻松松,但是对于链表来说每次的访问时间复杂度是O(n),这明显不是明智之举,所以我们找下其中规律。

其实我们可以发现合并时候的操作很像两个链表的合并,只不过这道题是一个链表,那么我们将其分成两个链表再合并可不可以呢?其实完全可以,还是拿示例二(输入:1->2->3->4->5,输出:1->5->2->4->3)来举例:

原链表要实现 L0→Ln→L1→Ln-1→… 的顺序,本质是需要一个“按原序取前半段节点”的链表,和一个“按逆序取后半段节点”的链表。
示例二中原链表的前半段是 1->2->3(包含中间节点),后半段是 4->5。如果直接用这两个链表合并,只能得到 1->4->2->5->3,不符合要求——因为我们需要后半段的“最后一个节点先衔接”,也就是 5(Ln)接 1(L0)、4(Ln-1)接 2(L1)。
所以拆分的关键是:将原链表按中点分成“前半段(原序)”和“后半段(需逆序)”,这样两个链表就能满足“交叉合并”的需求。

  • 前半段 A:1->2->3(原序,保留 L0、L1、L2 的顺序);
  • 后半段 B:4->5(逆序后变为 5->4,即 L4、L3 的顺序);
    此时将 A 和 逆序后的 B 交叉合并,正好得到 1(A1)→5(B1)→2(A2)→4(B2)→3(A3),完全匹配目标顺序。

到这里算法原理就已经结束了,希望大家可以自己动手画画,独立完成代码部分

代码实现思路

那么我们就按照这个思路一步一步来进行代码实现:

1.找到链表中间节点
我们采用快慢双指针法高效定位中间节点,具体思路是:

  • 快指针每次前进2步,慢指针每次前进1步
  • 当快指针到达链表末尾时,慢指针恰好指向中间节点

奇数长度链表
在这里插入图片描述
偶数长度链表
在这里插入图片描述
代码如下:

ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr)
{fast = fast->next->next;slow = slow->next;
}

此时,slow 即为中间节点,前半段为 head→...→slow,后半段为 slow->next→...→尾节点

2.逆序后半段链表
逆序有两种策略:
1)逆序的链表头节点是slow->next
当第一种策略的时候我们还会细分成两种情况,如下图👇:
在这里插入图片描述
问题就在偶数的时候不带着 slow 一起逆序结果会对吗? 在这道题里面并不影响最终结果,我们通过示例一观察:
在这里插入图片描述
我们能发现已知链表在中间的两个数,在重排后依然相连,所以并不会对结果造成影响,大家也可以自己画下图来体会下。

2)逆序的链表的头节点是slow
虽然这个方法其实也可以做出来,但是会我们没有办法完全将两个链表断开,因为单链表没法找前一个结点,而上面的方法我们直接 slow->next = nullptr 即可,这种情况还会有细节问题,所以我们放到后面说,如果遇到只能用这种方法的时候其实也很好解决,我们在原来链表前面加一个虚拟头节点即可返回到第一种情况中。

如何逆序,我们用头插法进行,如下图👇:
在这里插入图片描述

代码实现:

ListNode* newhead = new ListNode();
ListNode* cur = slow->next;
slow->next = nullptr;while(cur != nullptr)
{ListNode* next = cur->next;cur->next = newhead->next;newhead->next = cur;cur = next;
}

3.合并两个链表
最后一步是将前半段(head 起始)与逆序后的后半段(newhead->next 起始)交替合并,形成最终结果。

代码实现:

ListNode* ret = new ListNode();
ListNode* tmp = ret;
newhead = newhead->next;
while(head != nullptr || newhead != nullptr)
{if(head)  {tmp->next = head;tmp = tmp->next; head = head->next;}if(newhead){tmp->next = newhead;tmp = tmp->next;newhead = newhead->next;}  
} 

4.释放节点
毕竟是面试题,不释放怕面试官红温,给他个面子释放下(内存管理是面试的加分项,需要及时释放动态分配的节点)。

delete newhead;
delete ret;

整体代码

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     ListNode *next;*     ListNode() : val(0), next(nullptr) {}*     ListNode(int x) : val(x), next(nullptr) {}*     ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:void reorderList(ListNode* head) {ListNode* fast = head;ListNode* slow = head;while(fast != nullptr && fast->next != nullptr){fast = fast->next->next;slow = slow->next;}ListNode* newhead = new ListNode();ListNode* cur = slow->next;slow->next = nullptr;while(cur != nullptr){ListNode* next = cur->next;cur->next = newhead->next;newhead->next = cur;cur = next;}ListNode* ret = new ListNode();ListNode* tmp = ret;newhead = newhead->next;while(head != nullptr || newhead != nullptr){if(head)  {tmp->next = head;tmp = tmp->next; head = head->next;}if(newhead){tmp->next = newhead;tmp = tmp->next;newhead = newhead->next;}  } delete newhead;delete ret;}
};

直接从中间节点进行逆序的思路与代码

若我们选择从中间节点 slow 开始逆序,核心问题是无法直接断开前后半段。
那么解决思路是:用一个虚拟头节点指向 newhead,合并的时候特殊判断边界。
在这里插入图片描述
所以细节点就在,我们在合并链表的时候为了能够找到尾,有一个链表要提前判断自己的下一个是否为空,防止排序混乱,合并流程如下图👇(水印去不掉/(ㄒoㄒ)/~~):
在这里插入图片描述

代码实现(这里是唯一与第一种方法不同的部分):

ListNode* ret = new ListNode();
ListNode* tmp = ret;
newhead = newhead->next;
while(head->next != nullptr || newhead != nullptr)
{if(head->next)  {tmp->next = head;tmp = tmp->next; head = head->next;}if(newhead){tmp->next = newhead;tmp = tmp->next;newhead = newhead->next;}  
} 

总结

这道题的核心价值在于“拆解与整合”:

  1. 拆解思维:将复杂的重排需求,拆分为“找中点、逆序、合并”三个基础操作,化繁为简。
  2. 细节把控:快慢指针的起始位置、逆序时的指针交接、合并时的顺序对齐,都是面试高频考点。
  3. 内存管理:动态分配节点后及时释放,是体现代码严谨性的加分项。

通过这道题,不仅能熟练掌握链表三大核心操作,更能强化“复杂问题拆解”的算法思维,为后续应对更难的综合题打下基础。

下题预告

接下来,我们将继续攻坚链表操作中的——力扣 23. 合并 K 个升序链表。
这道题是对“合并两个有序链表”的升级拓展,更是考验贪心思想、分治策略与优先级队列应用的经典场景:既要解决多个有序链表的高效合并问题,又要在时间复杂度上实现优化,避免暴力合并的低效陷阱。我们会拆解“分而治之合并”“优先级队列辅助合并”两种核心思路,对比不同方法的优劣,带你吃透多链表合并的底层逻辑。

坚持跟着梳理的朋友,相信对复杂链表问题的拆解与优化能力会大幅提升!如果之前的解析对你有帮助,欢迎点赞收藏,方便随时回顾~ 关注博主,下一篇我们一起破解合并 K 个升序链表的关键难点,让每一次学习都有实打实的收获!

Doro 带着小花🌸来啦!🌸奖励🌸看到这里的你!如果这篇内容帮你理清了复杂链表问题的解题方向,或是让你对算法优化有了更深入的思考,别忘了点赞支持呀!把它收藏起来,以后遇到多链表合并问题时翻出来,就能快速找回解题思路~关注这个博主,他会持续更新算法系列内容,有什么疑问或者好的建议发到评论区,他看到会及时认真回复哒!对了大家有什么好的mp4转gif的免费网站或是软件能分享给这个博主吗?Doro也不喜欢看带水印的动图,可是Doro只会吃橘子🍊,Doro会帮大家转述给博主,Doro谢谢大家了(●’◡’●)
在这里插入图片描述

http://www.dtcms.com/a/585137.html

相关文章:

  • 快速定位bug,编写测试用例
  • 力扣第 474 场周赛
  • Node与Npm国内最新镜像配置(淘宝镜像/清华大学镜像)
  • 超越时空网上书城网站建设方案网站人员队伍建设落后
  • 海外云手机是指什么
  • react native 手搓数字键盘
  • 算法复杂度解析:时间与空间的衡量
  • 开源鸿蒙SIG-Qt技术沙龙成都站成功举办,产品方案展示
  • 2025年渗透测试面试题总结-235(题目+回答)
  • C语言进阶:深入理解函数
  • 计算机图形学·11 变换(Transformations)
  • Rust编程学习 - 如何利用代数类型系统做错误处理的另外一大好处是可组合性(composability)
  • LocalAI:一个免费开源的AI替代方案,让创意更自由!
  • 深入理解Ext2:Linux文件系统的基石与它的设计哲学
  • 泉州网站的建设html网页制作我的家乡
  • PHP 魔术常量
  • 【iOS】音频与视频播放
  • php通过身份证号码计算年龄
  • 基于PHP+Vue+小程序快递比价寄件系统
  • Next.js、NestJS、Nuxt.js 是 **Node.js 生态中针对不同场景的框架**
  • 牛客周赛 Round 114 Java题解
  • PostgreSQL 中数据库、用户、对象关系、表、连接及管理概述
  • 樟树市城乡规划建设局网站爱站攻略
  • Gitblit 迁移指南
  • Git分支管理核心:git fetch与git checkout创建分支完全指南
  • LRU 缓存的设计与实现
  • Linux -- 线程互斥
  • 2.2 Transformer 架构详解:从理论到实践
  • 《Docker+New Relic+Jenkins:开发全链路的工具赋能指南》
  • 2025最新修复的豪门足球风云-修复验证问题-修复注册问题实现地注册-架设教程【豪门足球本地验证】