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 | import java.util.*; |
解法二:一次遍历(对解法一的优化)
解法一一个明显不足在于,当所给子区间[m,n]范围过大,恰好等于链表头尾节点是,遍历成本变大。
解法二的思路在于,固定子区间外的节点。
在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。
curr:指向待反转区域的第一个节点 left;
Cur_next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 Cur_next 会变化;
pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
1 | import java.util.*; |
方法三:递归的思想求解
求解思路
对于反转链表,我们使用递归的思想,将大问题转换为小问题,然后进行相应的求解即可。对于递归过程,判断n的数值,当n为1时返回head指针,否则进行递归,并且反转链表,最后进行拼接,返回拼接之后的链表。
我们来看看另一种分析:如果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。
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
29import 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;
}
}