目录
0. 前言
abandon, abandon?这是以前的事了:算法:leetcode_206_反转链表-优快云博客
你难道真要背完abandon就放弃了?
no, 今天我们要做的题目是:persist[坚持!]
这道92. 反转链表 II将会是206. 反转链表的升华!
做完这道题目, 你将进一步理解链表!
1. 题目介绍
和算法:leetcode_206_反转链表最大的区别就是, 现在我们要菊部, no, 局部反转!
只是反转从left到right中间的局部链表, 注意left和right是第几个, 而非索引。

2. 思路
2.1 更清晰的示例
题目这样看, 是不是看着迷迷糊糊的?反转整个链表就是简单, 怎么反转局部就感觉有想法但是不知道怎么写了?
我来给你重新画个图:

你看是不是清晰多了!
先反转局部链表:现在局部链表的头节点和尾节点换位了, 攻守易行!
将局部链表的原来的首节点2的前面一个节点1, 链接上局部链表原来的最后一个节点4.
然后又将局部链表原来的首节点2[如今的局部链表的尾节点]链接上, 原来的局部链表的尾节点的再下一个节点!
是不是看着绕绕的, 我来给你加点符号!
2.3.1 原来的链表

2.3.2 反转后的链表

那我们开始吧!follow me!
2.2 设置虚拟头节点
首先, 为了防止分类讨论, 头节点是否为空, 做链表题, 我们可以无脑用虚拟头节点,
并将虚拟头节点链接上真正的头节点:
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
2.3 提前记录好这4个关键节点
2.3.1 pre节点
由于pre节点是在leftNode的前面一个位置, 比如left = 2, 那么从虚拟头节点dummyNode开始, 移动(left - 1)次就到达了:
ListNode pre = dummyNode;
for(int i = 0; i < left - 1; i++){
pre = pre.next;
}
2.3.2 leftNode节点
无需多说, pre的下一个节点:
ListNode leftNode = pre.next;
2.3.3 rightNode节点
从leftNode节点开始, 移动(right - left)次数即可, 比如图中示例, right - left = 4 - 2 = 2次:
ListNode rightNode = leftNode;
for(int i = 0; i < right - left; i++){
rightNode = rightNode.next;
}
2.3.4 next节点
无需多说, rightNode的下一个节点:
ListNode nextNode = rightNode.next;
2.4 斩断一切, 分手了就老实删除联系方式啊!
对于原来的链表,我们在反转局部链表2->3->4之前,
我们需要将局部链表与原来链表的前后给断开, 割袍断义, 如图所示:

对应的代码:
pre.next = null;
rightNode.next = null;
2.5 反转局部链表

我们可以把上次做的反转链表Ⅰ给挪过来, 你看, 没有白做吧!
reverse(leftNode);
反转链表对应的代码:
// 反转链表
public static void reverse(ListNode head){
ListNode cur = head;
ListNode pre = null;
// 当前访问节点不为空, 继续遍历
while (cur != null){
// 提前放置回旋镖, 记录下一个要访问的节点
ListNode next = cur.next;
// 往左边链接
cur.next = pre;
// 前驱节点向右移动
pre = cur;
// 开始访问下一个节点
cur = next;
}
}
2.6 重新链接, 移情别恋
2.6.1 前驱节点链接

以前在一起的时候, 前驱节点[节点1]是和节点2在一起的,
分手后, 却爱上了节点2的好闺蜜, 节点4!
此时前驱节点会链接上 反转后的局部链表的新头节点[rightNode]4
pre.next = rightNode;
2.6.2 leftNode链接

此时, 节点2也不甘示弱, 爱上了路人5, 此时,新的链表已经重组!
leftNode.next = nextNode;
2.7 结束这一切吧!
返回重组后的新链表头节点, 虚拟节点的下一个节点!
dummyNode.next = head;
3. 运行打印
成功!
这里的打印工具可以参考我分享的博客:
打印二叉树效果更佳!
打印节点(链表或者二叉树)工具_二叉链表打印方式都有哪些-优快云博客

4. 完整代码
Java
public class Leetcode_92_ReverseLinkedList2 {
/**
* @author: Rehse
* @description: 反转链表2 : 穿针引线
* @param[1] head
* @param[2] left
* @param[3] right
* @return ListNode
* @time: 2025/8/9 23:11
*/
public static ListNode reverseBetween(ListNode head, int left, int right) {
// 防止头节点为空额外处理判断, 统一增加个虚拟头节点, 并链接上链表头节点head
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
// 找到前驱节点pre
ListNode pre = dummyNode;
for(int i = 0; i < left - 1; i++){
pre = pre.next;
}
// 找到待反转的局部链表的首尾, 即左右边界节点
ListNode leftNode = pre.next;
ListNode rightNode = leftNode;
for(int i = 0; i < right - left; i++){
rightNode = rightNode.next;
}
// 找到待反转的局部链表的再下一个节点, 用于重组后的链接
ListNode nextNode = rightNode.next;
// 反转局部链表前 先斩断羁绊
pre.next = null;
rightNode.next = null;
// 反转链表
reverse(leftNode);
// 重新链接
pre.next = rightNode;
leftNode.next = nextNode;
// 返回头节点
return dummyNode.next;
}
// 反转链表
public static void reverse(ListNode head){
ListNode cur = head;
ListNode pre = null;
// 当前访问节点不为空, 继续遍历
while (cur != null){
// 提前放置回旋镖, 记录下一个要访问的节点
ListNode next = cur.next;
// 往左边链接
cur.next = pre;
// 前驱节点向右移动
pre = cur;
// 开始访问下一个节点
cur = next;
}
}
public static void main(String[] args) {
// 构造链表(二叉树打印工具)
PrintUtils utils = new PrintUtils(new int[]{1, 2, 3, 4, 5}, 0);
// 获取节点并打印出原来链表
utils.getAndPrint();
// 获取链表 作为本题核心函数形参
ListNode head = reverseBetween(utils.getListNode(), 2, 4);
// 打印链表结构
PrintUtils.printListNode(head);
}
}
C++
// 定义单链表节点(与 Java 的 ListNode 语义一致)
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
static ListNode* reverseBetween(ListNode* head, int left, int right) {
// 防止头节点为空额外处理判断, 统一增加个虚拟头节点, 并链接上链表头节点head
ListNode* dummyNode = new ListNode(-1);
dummyNode->next = head;
// 找到前驱节点pre
ListNode* pre = dummyNode;
for(int i = 0; i < left - 1; i++){
pre = pre->next;
}
// 找到待反转的局部链表的首尾, 即左右边界节点
ListNode* leftNode = pre->next;
ListNode* rightNode = leftNode;
for(int i = 0; i < right - left; i++){
rightNode = rightNode->next;
}
// 找到待反转的局部链表的再下一个节点, 用于重组后的链接
ListNode* nextNode = rightNode->next;
// 反转局部链表前 先斩断羁绊
pre->next = nullptr;
rightNode->next = nullptr;
// 反转链表
reverse(leftNode);
// 重新链接
pre->next = rightNode;
leftNode->next = nextNode;
// 返回头节点
return dummyNode->next;
}
// 反转链表
static void reverse(ListNode* head){
ListNode* cur = head;
ListNode* pre = nullptr;
// 当前访问节点不为空, 继续遍历
while (cur != nullptr){
// 提前放置回旋镖, 记录下一个要访问的节点
ListNode* next = cur->next;
// 往左边链接
cur->next = pre;
// 前驱节点向右移动
pre = cur;
// 开始访问下一个节点
cur = next;
}
}
};
JS
// 定义单链表节点(与 Java 的 ListNode 语义一致)
class ListNode {
constructor(val, next = null) {
this.val = val;
this.next = next;
}
}
class Solution {
static reverseBetween(head, left, right) {
// 防止头节点为空额外处理判断, 统一增加个虚拟头节点, 并链接上链表头节点head
let dummyNode = new ListNode(-1);
dummyNode.next = head;
// 找到前驱节点pre
let pre = dummyNode;
for(let i = 0; i < left - 1; i++){
pre = pre.next;
}
// 找到待反转的局部链表的首尾, 即左右边界节点
let leftNode = pre.next;
let rightNode = leftNode;
for(let i = 0; i < right - left; i++){
rightNode = rightNode.next;
}
// 找到待反转的局部链表的再下一个节点, 用于重组后的链接
let nextNode = rightNode.next;
// 反转局部链表前 先斩断羁绊
pre.next = null;
rightNode.next = null;
// 反转链表
this.reverse(leftNode);
// 重新链接
pre.next = rightNode;
leftNode.next = nextNode;
// 返回头节点
return dummyNode.next;
}
// 反转链表
static reverse(head){
let cur = head;
let pre = null;
// 当前访问节点不为空, 继续遍历
while (cur != null){
// 提前放置回旋镖, 记录下一个要访问的节点
let next = cur.next;
// 往左边链接
cur.next = pre;
// 前驱节点向右移动
pre = cur;
// 开始访问下一个节点
cur = next;
}
}
}
5. 致谢, 更新预告--->LRU 缓存
感谢你看完这篇分享博客,
觉得我写的屎山代码有用的话, 点赞关注收藏么么哒!
重铸Java荣光, 我辈义不容辞!
25届Java小登不定时分享学习笔记,即使已经入职也要继续保持学习哇, 让猪脑子能够保持清醒!
接下来将会更新一道面试超级热门题目:146. LRU 缓存 - 力扣(LeetCode)
LRU是链表熟练操作落地应用的一道题目,
是Redis的一种非常推荐的缓存淘汰策略, 一定要好好吃透!
CodeTop排名南波儿兔!CodeTop是一个面试总结网页, 根据各个大厂算法面试频率对题目进行排序, 下面贴上链接:CodeTop 面试题目总结

333

被折叠的 条评论
为什么被折叠?



