题解 | #链表内指定区间反转#

链表内指定区间反转

http://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c

题目的主要信息:
  • 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转
  • 链表其他部分不变,返回头节点
举一反三:

学习完本题的思路你可以解决如下题目:

BM1.反转链表

BM3.链表中的节点每k个一组翻转

方法一:头插法迭代(推荐使用)

思路:

在学会了BM1.反转链表之后,要解决这个问题就很简单了,前一题是整个链表反转,这一题是部分反转,这上一题就是这道题的前置问题啊。那我们肯定是要先找到了第m个位置才能开始反转链表,而反转的部分就是从第m个位置到第n个位置,反转这部分的时候就参照BM1.反转链表

while(cur != null){
    //断开链表,要记录后续一个
    ListNode temp = cur.next; 
    //当前的next指向前一个
    cur.next = pre; 
    //前一个更新为当前
    pre = cur; 
    //当前更新为刚刚记录的后一个
    cur = temp; 
}

具体做法:

  • step 1:我们可以在链表前加一个表头,后续返回时去掉就好了,因为如果要从链表头的位置开始反转,在多了一个表头的情况下就能保证第一个节点永远不会反转,不会到后面去。
  • step 2:使用两个指针,一个指向当前节点,一个指向前序节点。
  • step 3:依次遍历链表,到第m个的位置。
  • step 4:对于从m到n这些个位置的节点,依次断掉指向后续的指针,反转指针方向。
  • step 5:返回时去掉我们添加的表头。

图示:

alt

Java实现代码:

import java.util.*;
public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //加个表头
        ListNode res = new ListNode(-1); 
        res.next = head;
        //前序节点
        ListNode pre = res; 
        //当前节点
        ListNode cur = head; 
        //找到m
        for(int i = 1; i < m; i++){ 
            pre = cur;
            cur = cur.next;
        }
        //从m反转到n
        for(int i = m; i < n; i++){ 
            ListNode temp = cur.next;
            cur.next = temp.next;
            temp.next = pre.next;
            pre.next = temp;
        }
        //返回去掉表头
        return res.next; 
    }
}

C++实现代码:

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
         //加个表头
        ListNode* res = new ListNode(-1);
        res->next = head;
        //前序节点
        ListNode* pre = res; 
        //当前节点
        ListNode* cur = head; 
        //找到m
        for(int i = 1; i < m; i++){ 
            pre = cur;
            cur = cur->next;
        }
        //从m反转到n
        for(int i = m; i < n; i++){ 
            ListNode* temp = cur->next;
            cur->next = temp->next;
            temp->next = pre->next;
            pre->next = temp;
        }
        //返回去掉表头
        return res->next; 
    }
};

Python代码实现:

class Solution:
    def reverseBetween(self , head: ListNode, m: int, n: int) -> ListNode:
        #加个表头
        res = ListNode(-1)
        res.next = head
        #前序节点
        pre = res 
        #当前节点
        cur = head 
        #找到m
        for i in range(1,m): 
            pre = cur
            cur = cur.next
        #从m反转到n
        for i in range(m, n): 
            temp = cur.next
            cur.next = temp.next
            temp.next = pre.next
            pre.next = temp
        #返回去掉表头
        return res.next 

复杂度分析:

  • 时间复杂度:O(n)O(n),最坏情况下需要遍历全部链表节点,比如mm为链表最后一个位置,或者nn为链表最后一个位置时
  • 空间复杂度:O(1)O(1),常数级指针变量,无额外辅助空间使用
方法二:递归(扩展思路)

思路:

我们来看看另一种分析:如果m == 1,就相当于反转链表的前nn元素;如果 m != 1,我们把 head 的索引视为 1,那么我们是想从第 mm 个元素开始反转,如果把 head.next 的索引视为1,那相对于 head.next的反转的区间应该是从第 m1m - 1 个元素开始的,以此类推,反转区间的起点往后就是一个子问题,我们可以使用递归处理:

  • 终止条件:m == 1,就可以直接反转前n个元素。
  • 返回值: 将已经反转后的子问题头节点返回给上一级。
  • 本级任务: 递归地缩短区间,拼接本级节点与子问题已经反转的部分。
//从头开始往后去掉前面不反转的部分
ListNode node = reverseBetween(head.next, m - 1, n - 1)

而每次反转,如果n == 1,相当于只颠倒第一个节点,如果不是,则进入后续节点(子问题),因此反转过程也可以使用递归:

  • 终止条件:n == 1时,只反转当前头节点即可。
  • 返回值: 将子问题反转后的节点头返回。
  • 本级任务: 缩短nn进入子问题反转,等子问题回到本级再反转当前节点与后续节点的连接。
//颠倒后续的节点,直到n=1为最后一个
ListNode node = reverse(head.next, n - 1)

具体做法:

  • step 1:准备全局变量temp,最初等于null,找到递归到第nn个节点时,指向其后一个位置,要将反转部分的起点(即反转后的尾)连接到这个指针。
  • step 2:按照第一个递归的思路缩短子问题找到反转区间的起点,将反转后的部分拼接到前面正常的后面。
  • step 3:按照第二个递归的思路缩短终点的子问题,从第nn个位置开始反转,反转过程中每个子问题作为反转后的尾,都要指向temp。

Java实现代码:

import java.util.*;
public class Solution {
    ListNode temp = null;
    public ListNode reverse(ListNode head, int n){
        //只颠倒第一个节点,后续不管
        if(n == 1){ 
            temp = head.next;
            return head;
        }
        //进入子问题
        ListNode node = reverse(head.next, n - 1); 
        //反转
        head.next.next = head;
        //每个子问题反转后的尾拼接第n个位置后的节点
        head.next = temp; 
        return node;
    }
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //从第一个节点开始
        if(m == 1) 
            return reverse(head, n);
        //缩减子问题
        ListNode node = reverseBetween(head.next, m - 1, n - 1); 
        //拼接已翻转
        head.next = node;
        return head;
    }
}

C++实现代码:

class Solution {
public:
    ListNode* temp = NULL;
    ListNode* reverse(ListNode* head, int n){
        //只颠倒第一个节点,后续不管
        if(n == 1){ 
            temp = head->next;
            return head;
        }
        //进入子问题
        ListNode* node = reverse(head->next, n - 1); 
        //反转
        head->next->next = head;
        //每个子问题反转后的尾拼接第n个位置后的节点
        head->next = temp;
        return node;
    }
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        //从第一个节点开始
        if(m == 1) 
            return reverse(head, n);
        //缩减子问题
        ListNode* node = reverseBetween(head->next, m - 1, n - 1); 
        //拼接已翻转
        head->next = node;
        return head;
    }
};

Python实现代码:

class Solution:
    def __init__(self):
        self.temp = None
    def reverse(self, head: ListNode, n: int) -> ListNode:
        #只颠倒第一个节点,后续不管
        if n == 1: 
            self.temp = head.next
            return head
        #进入子问题
        node = self.reverse(head.next, n - 1) 
        #反转
        head.next.next = head 
        #每个子问题反转后的尾拼接第n个位置后的节点
        head.next =  self.temp 
        return node
    
    def reverseBetween(self , head: ListNode, m: int, n: int) -> ListNode:
        #从第一个节点开始
        if m == 1: 
            return self.reverse(head, n)
        #缩减子问题
        node = self.reverseBetween(head.next, m - 1, n - 1) 
        #拼接已翻转
        head.next = node 
        return head

复杂度分析:

  • 时间复杂度:O(n)O(n),最坏情况下递归遍历全部链表节点,比如mm为链表最后一个位置,或者nn为链表最后一个位置时
  • 空间复杂度:O(n)O(n),遍历全部节点时递归栈深度最坏为nn
全部评论
头插法核心在于固定pre指针不动,通过temp和pre指针的连接指向彼此将temp指针的元素前移,达到将cur指针的元素后移,实现指定区间里的元素反转和反转之后整个链表的连接,搞明白感觉还是很奇妙的,只有两次循环,最好自己画下来就容易懂得多。
6 回复 分享
发布于 2022-10-09 16:02 安徽
我还是不明白为什么要插入一个表头。。。
2 回复 分享
发布于 2022-10-23 17:41 河南
这个头插法太懵逼了
1 回复 分享
发布于 2022-10-02 20:29 湖北
第一个解法,如果当节点是两个需要反转的时候,temp会指空,那空的next是什么鬼???这是怎么反转的有点懵
点赞 回复 分享
发布于 2022-09-22 12:28 黑龙江
1
点赞 回复 分享
发布于 2022-11-23 22:30 广东
第一个解法m到n的反转解释得不太清楚呀,每一步是什么个意思,BM1反转链表解释得倒挺清楚
点赞 回复 分享
发布于 2022-12-03 10:32 江西
ListNode* res = new ListNode(-1); 这个解法最后返回的是res- >next,res指向的内存空间还在。我觉得应该写成ListNode* temp=res->next; delete res; return temp;
点赞 回复 分享
发布于 2023-02-06 16:23 上海
翻转区间是m=2,n=4, 把cur移动到第二个节点(也就是m位置),同时保存cur前一个节点pre 每次头插的是cur->next  1 2 3 4 5(cur->next=3,把3头插到3 2 4的最前面,其余顺移) ——> 1 3 2 4 5(cur->next=4) ——> 1 4 3 2 5   注意:万一m=1,那么pre需要一个哨兵位安放 (自己看了很久总结的个人理解) ListNode* reverseBetween(ListNode* head, int m, int n) { ListNode* res=new ListNode(-1); //设置哨兵位,防止pre越界 res->next=head; //哨兵位和链表连上 ListNode* pre=res; ListNode*cur=head; for(int i=1;i<m>next; } //cur在m个节点上,pre是他前一个节点 for(int i=m;i<n>next; cur->next=tmp->next; tmp->next=pre->next; pre->next=tmp; } return res->next; } };</n></m>
点赞 回复 分享
发布于 2023-03-13 20:17 天津
为什么返回head不对?为什么必须返回res.Next?
点赞 回复 分享
发布于 2023-03-21 12:23 澳门
本来以为自己递归理解了一些了,方法二直接递归里面有一个新的递归把我干蒙蔽
点赞 回复 分享
发布于 2023-10-13 14:42 澳大利亚
这个题解写的太混了,解释和代码根本对不上,哪里抄来的
点赞 回复 分享
发布于 2023-11-10 23:25 上海

相关推荐

94 54 评论
分享
牛客网
牛客企业服务