算法:leetcode_92_反转链表Ⅱ

目录

0. 前言

1. 题目介绍

2. 思路

2.1 更清晰的示例

2.3.1 原来的链表

2.3.2 反转后的链表

2.2 设置虚拟头节点

2.3 提前记录好这4个关键节点

2.3.1 pre节点

2.3.2 leftNode节点

2.3.3 rightNode节点

2.3.4 next节点

2.4 斩断一切, 分手了就老实删除联系方式啊!

2.5 反转局部链表

2.6 重新链接, 移情别恋

2.6.1 前驱节点链接

2.6.2 leftNode链接

2.7 结束这一切吧!

3. 运行打印

4. 完整代码

Java

C++

JS

5. 致谢, 更新预告


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 反转局部链表

我们可以把上次做的反转链表Ⅰ给挪过来, 你看, 没有白做吧!

算法:leetcode_206_反转链表-优快云博客

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 面试题目总结

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值