算法通关村第二关——指定区间反转问题解析

指定区间反转问题

1、问题描述

​ 本题来自LeetCode92:给你单链表的头指针head和两个整数left和right,其中left <= right。请你反转从位置left到位置right的链表节点,返回反转后的链表。

示例:

输入:head = [1, 2, 3, 4, 5], left = 2, right = 4

输出:[1, 4, 3, 2, 5]

图示:

在这里插入图片描述

​ 这题跟上篇描述的反转链表问题类似,只不过是把整个链表变为了链表中的一部分,有上一篇的解放为基础,本题依旧提供两种方式去解决。

2、解法一:头插法

​ 如果left和right之间的区域很大,恰好是链表的头节点和尾节点时,找到left和right需要遍历一次,反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为O(n),但是遍历了链表两次,这里提供了只遍历一次的方法,链表的序列如下:

在这里插入图片描述

​ 有颜色填充的部分是需要反转的。

​ 反转的整体思想是,在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。

在这里插入图片描述

​ 调整节点的过程就是带虚拟节点的插入操作,每走一步就要考虑各种指针怎么指,既要将节点摘下来接到对应的位置上,还要保证后续节点能够找到。

​ 代码如下:

public ListNode reverseBetween(ListNode head, int left, int right) {
    //设置 dummyNode 是这一类问题的一般做法
    ListNode dummyNode = new ListNode(-1);
    dummyNode.next = head;
    ListNode pre = dummyNode;
    //找到区间左侧的节点
    for	(int i = 0; i < left - 1; i++){
        pre = pre.next
    }
    ListNode cur = pre.next;
    ListNode next;
    for(int i = 0; i < right - left; i++){
        next = cur.next;
        cur.next = next.next;
        next.next = pre.next;
        pre.next = next;
    }
    return dummyNode.next;
}

3、解法二:穿针引线法

​ 这种方法其实就是复用之前的链表反转方法,但是实现难度会更高一点。还是以上面的链表为例。

​ 我们可以先确定好需要反转的部分,也就是题目给出的left和right之间,将其切出来,这样原链表就成了三段链表,我们将需要反转的部分用链表反转的方法反转之后,再将三段链表拼接起来。这种方法类似裁缝一样,找准位置剪下来,再缝回去,所以叫穿针引线法。

在这里插入图片描述

​ 算法步骤如下:

​ 1、先找到三段链表;

​ 2、将待反转的区域反转;

​ 3、把pre的next指针指向反转后的链表头节点,把反转后的链表的尾节点的next指针指向succ。

在这里插入图片描述

​ 代码如下:

public ListNode reverseBetween(ListNode head, int left, int right) {
    // 因为头节点有可能发生变化,所以使用虚拟节点来避免复杂的分类讨论
    ListNode dummyNode = new ListNode(-1);
    dummyNode.next = head;
    ListNode pre = dummyNode;
    
    //第一步:从虚拟节点走left-1步,来到left节点的前一个节点
    for	(int i = 0; i < left -1; i++){
        pre = pre.next;
    }
    
    //第二步:从pre再走right-left+1步,来到right节点
    ListNode rightNode = pre;
    for	(int i = 0; i < right - left + 1; i++){
        rightNode = rightNode.next;
    }
    
    //第三步:切出一个子链表
    ListNode leftNode = pre,next;
    ListNode succ = rightNode.next;
    //如果这里不设置成null,在调用链表反转方法的时候会把rightNode之后的节点也加入反转的链表中
    rightNode.next = null;
     
    //第四步:反转链表的子区间
    reverseList(leftNode);
    
    //第五步:接回原来的链表中
    //因为反转之后,原来子链表的尾节点变为头节点,所以这里直接用rightNode
    pre.next = rightNode;
    left.next = succ;
    return dummyNode.next;
}

private void reverseList(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;
    while(cur != null) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
}

4、总结

​ 指定区间反转其实就是链表反转基础上更进了一步,从整个链表变为了部分链表,其实只用将这部分链表提取出来,就又退还成了链表反转问题,在处理方法上是类似的,只用把一些细节处处理好就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Molche

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值