迭代思想:
先用一个 for 循环找到第 m 个位置,然后再用一个 for 循环将 m 和 n 之间的元素反转。但是我们的递归解法不用一个 for 循环,纯递归实现反转。
迭代实现思路看起来虽然简单,但是细节问题很多的,反而不容易写对。相反,递归实现就很简洁优美,下面就由浅入深,先从反转整个单链表说起。
递归反转整个链表
ListNode reverse(ListNode head) {
if (head.next == null) return head;
//last是反转后的头结点
ListNode last = reverse(head.next);
head.next.next = head;
head.next = null;
return last;
}
上述代码是不是优雅有难以理解?
- 想要理解递归代码,首先要明确递归的定义
- 遇到递归千万不要把思维跳进递归栈里,你的脑子够想几层递归
- 而是要思考该递归执行完成的结果是什么(根据定义)
语句解析:
反转前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;
}
为什么要加successor呢?
现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。
反转链表的一部分
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;
}