题目## 题目
题目的主要信息:
- 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转
- 链表其他部分不变,返回头节点
举一反三:
学习完本题的思路你可以解决如下题目:
方法一:头插法迭代(推荐使用)
思路:
在学会了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:返回时去掉我们添加的表头。
图示:

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) O(n),最坏情况下需要遍历全部链表节点,比如 m m m为链表最后一个位置,或者 n n n为链表最后一个位置时
- 空间复杂度: O ( 1 ) O(1) O(1),常数级指针变量,无额外辅助空间使用
方法二:递归(扩展思路)
思路:
我们来看看另一种分析:如果m == 1,就相当于反转链表的前
n
n
n元素;如果 m != 1,我们把 head 的索引视为 1,那么我们是想从第
m
m
m 个元素开始反转,如果把 head.next 的索引视为1,那相对于 head.next的反转的区间应该是从第
m
−
1
m - 1
m−1 个元素开始的,以此类推,反转区间的起点往后就是一个子问题,我们可以使用递归处理:
- 终止条件: 当
m == 1,就可以直接反转前n个元素。 - 返回值: 将已经反转后的子问题头节点返回给上一级。
- 本级任务: 递归地缩短区间,拼接本级节点与子问题已经反转的部分。
//从头开始往后去掉前面不反转的部分
ListNode node = reverseBetween(head.next, m - 1, n - 1)
而每次反转,如果n == 1,相当于只颠倒第一个节点,如果不是,则进入后续节点(子问题),因此反转过程也可以使用递归:
- 终止条件: 当
n == 1时,只反转当前头节点即可。 - 返回值: 将子问题反转后的节点头返回。
- 本级任务: 缩短 n n n进入子问题反转,等子问题回到本级再反转当前节点与后续节点的连接。
//颠倒后续的节点,直到n=1为最后一个
ListNode node = reverse(head.next, n - 1)
具体做法:
- step 1:准备全局变量temp,最初等于null,找到递归到第 n n n个节点时,指向其后一个位置,要将反转部分的起点(即反转后的尾)连接到这个指针。
- step 2:按照第一个递归的思路缩短子问题找到反转区间的起点,将反转后的部分拼接到前面正常的后面。
- step 3:按照第二个递归的思路缩短终点的子问题,从第 n n n个位置开始反转,反转过程中每个子问题作为反转后的尾,都要指向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) O(n),最坏情况下递归遍历全部链表节点,比如 m m m为链表最后一个位置,或者 n n n为链表最后一个位置时
- 空间复杂度: O ( n ) O(n) O(n),遍历全部节点时递归栈深度最坏为 n n n
题目主要信息:
- 给定一个链表,从头开始每k个作为一组,将每组的链表节点翻转
- 组与组之间的位置不变
- 如果最后链表末尾剩余不足k个元素,则不翻转,直接放在最后
举一反三:
学习完本题的思路你可以解决如下题目:
方法:递归(推荐使用)
思路:
现在我们想一想,如果拿到一个链表,想要像上述一样分组翻转应该做些什么?首先肯定是分段吧,至少我们要先分成一组一组,才能够在组内翻转,之后就是组内翻转,最后是将反转后的分组连接。
但是连接的时候遇到问题了:首先如果能够翻转,链表第一个元素一定是第一组,它翻转之后就跑到后面去了,而第一组的末尾元素才是新的链表首,我们要返回的也是这个元素,而原本的链表首要连接下一组翻转后的头部,即翻转前的尾部,如果不建立新的链表,看起来就会非常难。但是如果我们从最后的一个组开始翻转,得到了最后一个组的链表首,是不是可以直接连在倒数第二个组翻转后的尾(即翻转前的头)后面,这样从后往前是不是看起来就容易多了。
怎样从后往前呢?我们这时候可以用到自上而下再自下而上的递归或者说栈。接下来我们说说为什么能用递归?如果这个链表有 n n n个分组可以反转,我们首先对第一个分组反转,那么是不是接下来将剩余 n − 1 n-1 n−1个分组反转后的结果接在第一组后面就行了,那这剩余的 n − 1 n-1 n−1组就是一个子问题。我们来看看递归的三段式模版:
- 终止条件: 当进行到最后一个分组,即不足k次遍历到链表尾(0次也算),就将剩余的部分直接返回。
- 返回值: 每一级要返回的就是翻转后的这一分组的头,以及连接好它后面所有翻转好的分组链表。
- 本级任务: 对于每个子问题,先遍历k次,找到该组结尾在哪里,然后从这一组开头遍历到结尾,依次翻转,结尾就可以作为下一个分组的开头,而先前指向开头的元素已经跑到了这一分组的最后,可以用它来连接它后面的子问题,即后面分组的头。
具体做法:
- step 1:每次从进入函数的头节点优先遍历链表k次,分出一组,若是后续不足k个节点,不用反转直接返回头。
- step 2:从进入函数的头节点开始,依次反转接下来的一组链表,反转过程同BM1.反转链表。
- step 3:这一组经过反转后,原来的头变成了尾,后面接下一组的反转结果,下一组采用上述递归继续。
图示:

Java实现代码:
import java.util.*;
public class Solution {
public ListNode reverseKGroup (ListNode head, int k) {
//找到每次翻转的尾部
ListNode tail = head;
//遍历k次到尾部
for(int i = 0; i < k; i++){
//如果不足k到了链表尾,直接返回,不翻转
if(tail == null)
return head;
tail = tail.next;
}
//翻转时需要的前序和当前节点
ListNode pre = null;
ListNode cur = head;
//在到达当前段尾节点前
while(cur != tail){
//翻转
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
//当前尾指向下一段要翻转的链表
head.next = reverseKGroup(tail, k);
return pre;
}
}
C++实现代码:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
//找到每次翻转的尾部
ListNode* tail = head;
//遍历k次到尾部
for(int i = 0; i < k; i++){
//如果不足k到了链表尾,直接返回,不翻转
if(tail == NULL)
return head;
tail = tail->next;
}
//翻转时需要的前序和当前节点
ListNode* pre = NULL;
ListNode* cur = head;
//在到达当前段尾节点前
while(cur != tail){
//翻转
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
//当前尾指向下一段要翻转的链表
head->next = reverseKGroup(tail, k);
return pre;
}
};
Python实现代码:
class Solution:
def reverseKGroup(self , head: ListNode, k: int) -> ListNode:
#找到每次翻转的尾部
tail = head
#遍历k次到尾部
for i in range(0,k):
#如果不足k到了链表尾,直接返回,不翻转
if tail == None:
return head
tail = tail.next
#翻转时需要的前序和当前节点
pre = None
cur = head
#在到达当前段尾节点前
while cur != tail:
#翻转
temp = cur.next
cur.next = pre
pre = cur
cur = temp
#当前尾指向下一段要翻转的链表
head.next = self.reverseKGroup(tail, k)
return pre
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n),一共遍历链表 n n n个节点
- 空间复杂度: O ( n ) O(n) O(n),递归栈最大深度为 n / k n/k n/k
583

被折叠的 条评论
为什么被折叠?



