BM2 链表内指定区间反转

描述

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4m=2,n=4,
返回 1→4→3→2→5→NULL.

数据范围: 链表长度 0 <<size≤1000,0<m≤n≤size,链表中每个节点的值满足 ∣val∣≤1000
要求:时间复杂度 O(n) ,空间复杂度 O(n)
进阶:时间复杂度 O(n),空间复杂度 O(1)
示例1
输入:
{1,2,3,4,5},2,4
返回值:
{1,4,3,2,5}

示例2
输入: {5},1,1
返回值: {5}

解法一:双指针(两次遍历)

思路步骤:
要反转局部链表,可以将该局部部分当作完整链表进行反转

再将已经反转好的局部链表与其他节点建立连接,重构链表

建议使用虚拟头节点的技巧,可以避免对头节点复杂的分类考虑,简化操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.*;

/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/

public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
// 解法一:双指针(两次遍历)
//说明:方便理解,以下注释中将用left,right分别代替m,n节点

public ListNode reverseBetween (ListNode head, int m, int n) {
//设置虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;

ListNode pre = dummyNode;
//1.走left-1步到left的前一个节点
for(int i=0;i<m-1;i++){
pre = pre.next;
}

//2.走roght-left+1步到right节点
ListNode rigthNode = pre;
for(int i=0;i<n-m+1;i++){
rigthNode = rigthNode.next;
}

//3.截取出一个子链表
ListNode leftNode = pre.next;
ListNode cur = rigthNode.next;

//4.切断链接
pre.next=null;
rigthNode.next=null;

//5.反转局部链表
reverseLinkedList(leftNode);

//6.接回原来的链表
pre.next = rigthNode;
leftNode.next = cur;
return dummyNode.next;
}
//反转局部链表
private void reverseLinkedList(ListNode head){
ListNode pre = null;
ListNode cur = head;
while(cur!=null){
//Cur_next 指向cur节点的下一个节点
ListNode Cur_next = cur.next;
cur.next = pre;
pre = cur;
cur = Cur_next ;
}
}
}

解法二:一次遍历(对解法一的优化)

解法一一个明显不足在于,当所给子区间[m,n]范围过大,恰好等于链表头尾节点是,遍历成本变大。

解法二的思路在于,固定子区间外的节点。

在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。

curr:指向待反转区域的第一个节点 left;
Cur_next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 Cur_next 会变化;
pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
img.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.*;

/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/

public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
//
//说明:方便理解,以下注释中将用left,right分别代替m,n节点

public ListNode reverseBetween (ListNode head, int m, int n) {
//设置虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next =head;
ListNode pre = dummyNode;
for(int i=0;i<m-1;i++){
pre = pre.next;
}

ListNode cur = pre.next;
ListNode Cur_next ;
for(int i=0;i<n-m;i++){
Cur_next = cur.next;
cur.next = Cur_next.next;
Cur_next .next = pre.next;
pre.next = Cur_next ;
}
return dummyNode.next;
}
}

方法三:递归的思想求解

求解思路
对于反转链表,我们使用递归的思想,将大问题转换为小问题,然后进行相应的求解即可。对于递归过程,判断n的数值,当n为1时返回head指针,否则进行递归,并且反转链表,最后进行拼接,返回拼接之后的链表。
img_1.png

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

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

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

  • 终止条件: 当n == 1时,只反转当前头节点即可。

  • 返回值: 将子问题反转后的节点头返回。

  • 本级任务: 缩短nnn进入子问题反转,等子问题回到本级再反转当前节点与后续节点的连接。

    1
    2
    //颠倒后续的节点,直到n=1为最后一个
    ListNode node = reverse(head.next, n - 1)

    具体做法:

  • step 1:准备全局变量temp,最初等于null,找到递归到第nnn个节点时,指向其后一个位置,要将反转部分的起点(即反转后的尾)连接到这个指针。

  • step 2:按照第一个递归的思路缩短子问题找到反转区间的起点,将反转后的部分拼接到前面正常的后面。

  • step 3:按照第二个递归的思路缩短终点的子问题,从第nnn个位置开始反转,反转过程中每个子问题作为反转后的尾,都要指向temp。
    img_2.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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;
    }
    }

评论