1. 题目
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
2. 解题思路
2.1. 反转整个链表
首先我们要知道最基本的反转链表思路:
ListNode reverse(ListNode head) {
if (head.next == null) return head;
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
2.2. 反转从head节点开始的n段链表
这次我们实现一个这样的函数–>反转从head开始的n个节点:
ListNode successor = null; // 后驱节点
// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
successor = head.next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
ListNode last = reverseN(head.next, n - 1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = successor;
return last;
}
比如说对于下图链表,执行 reverseN(head, 3)
:
[!question] 为什么不是 head = reverseBetween(…),而是 head.next = reverseBetween(…)?
这个问题的关键点是:我们要反转的是从第 m 个节点到第 n 个节点的子链表,而不是整个链表的 head。我们只需要修改head.next
,而不是head
本身。
把reverseBetween()
理解为反转结束后的head节点就行。
2.3. 反转链表的一部分
现在解决我们最开始提出的问题,给一个索引区间 [m,n]
(索引从 1 开始),仅仅反转区间中的链表元素。
ListNode reverseBetween(ListNode head, int m, int n)
首先,如果 m == 1
,就相当于反转链表开头的 n
个元素嘛,也就是我们刚才实现的功能reverseN()
。通过递归调用,把问题变成“从下一个节点开始的链表中,反转第 m-1
到 n-1
个节点”。每一次递归都“前进一位”,直到 m == 1
时触发 base case,进入实际反转。
假设链表是:1 → 2 → 3 → 4 → 5
,现在要执行 reverseBetween(head, 2, 4)
,反转第2到第4个节点。
第一次调用:
- m = 2, n = 4, head = 1
- 不满足
m=1
,于是执行:head.next = reverseBetween(head.next, 1, 3);
第二次调用:
- m = 1, n = 3, head = 2
- 进入 base case,调用
reverseN(head, 3)
,会反转2 → 3 → 4
成为4 → 3 → 2
。
此时返回的是新的头结点 4
,所以:head.next = 4(即反转后新的头结点),而此时 head 是 1,那么1.next = 4;
这样就把 1 → 2 → 3 → 4 → 5
改成了:1 → 4 → 3 → 2 → 5
。
这部分的完整代码:
ListNode reverseBetween(ListNode head, int m, int n) {
// base case
if (m == 1) {
return reverseN(head, n);
}
// 前进到反转的起点触发 base case
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
}
3. 代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
ListNode successor = null;//后驱节点
/*
反转链表中的前n个节点
*/
public ListNode reverseN(ListNode head, int n) {
if (n == 1) {//经过下面的递归,一定会来到这里,把后驱节点赋上值。
successor = head.next;//记录反转后的头节点 后面原来连接的那个节点
return head;//反转一个节点,就是它自己
}
//以head.next为起点,需要反转前n-1个节点
ListNode last = reverseN(head.next, n - 1);
head.next.next = head;
head.next = successor;
return last;
}
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null || head.next == null) {
return head;
}
//base case 经过递归最终会来到这里的。
if (m == 1) {//left=1,相当于链表开头的right个元素
return reverseN(head, n);
}
/*对于head.next来说就是反转区间[left-1,right-1]
reverseBetween();会返回反转后的链表头节点,head.next就是把反转后的头节点和head连接起来
前进到反转的起点触发 base case
*/
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
}
}