1.训练计划III
给定一个头节点为 head
的单链表用于记录一系列核心肌群训练编号,请将该系列训练编号 倒序 记录于链表并返回。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
这个问题要求我们将一个单链表中的训练编号倒序排列并返回反转后的链表。可以通过迭代和递归两种方式来解决。下面是对两种方法的详细解释和代码示例。
解题思路
-
链表反转的核心思想是将每个节点的
next
指针反向。具体来说,对于每个节点,我们需要将它的next
指针指向它的前一个节点,而不是它的下一个节点。 -
我们可以用两种方式来实现:
- 迭代(双指针法):通过遍历链表并逐步修改
next
指针来实现反转。 - 递归:通过递归的方式来遍历链表,在递归回溯时修改
next
指针。
- 迭代(双指针法):通过遍历链表并逐步修改
算法流程
1. 迭代(双指针法)
- 设置两个指针:
pre
(前驱节点)和cur
(当前节点)。 - 遍历链表,对于每个节点:
- 暂存当前节点的下一个节点
tmp
。 - 将当前节点的
next
指向前驱节点pre
。 - 移动前驱节点指针和当前节点指针,继续遍历链表。
- 暂存当前节点的下一个节点
- 最后,
pre
会指向反转后的链表头节点。
2. 递归法
- 递归的终止条件是当前节点
cur
为null
。 - 递归遍历链表,直到链表的最后一个节点。
- 在回溯过程中,修改当前节点的
next
指向前驱节点。 - 最终返回的是反转后的链表头节点。
复杂度分析
1. 迭代法
- 时间复杂度:
O(N)
,其中N
是链表的长度。需要遍历链表一次。 - 空间复杂度:
O(1)
,只用了常数空间来存储cur
和pre
两个指针。
2. 递归法
- 时间复杂度:
O(N)
,同样需要遍历链表一次。 - 空间复杂度:
O(N)
,递归调用栈的最大深度为链表的长度。
代码示例
1. 迭代法
class Solution {
public ListNode trainningPlan(ListNode head) {
ListNode cur = head, pre = null;
while (cur != null) {
ListNode tmp = cur.next; // 暂存后继节点
cur.next = pre; // 修改 next 引用指向
pre = cur; // pre 暂存 cur
cur = tmp; // cur 访问下一节点
}
return pre; // 返回反转后的链表头节点
}
}
2. 递归法
class Solution {
public ListNode trainningPlan(ListNode head) {
return recur(head, null); // 调用递归并返回
}
private ListNode recur(ListNode cur, ListNode pre) {
if (cur == null) return pre; // 终止条件
ListNode res = recur(cur.next, cur); // 递归后继节点
cur.next = pre; // 修改节点引用指向
return res; // 返回反转链表的头节点
}
}
在这里,给出一个仿造双指针法写的迭代代码供参考
class Solution {
/**
* 翻转单链表
* @param head 链表的头节点
* @return 翻转后的链表头节点
*/
public ListNode trainningPlan(ListNode head) {
// 调用辅助递归方法,初始时前置节点为 null
return recur(head, null);
}
/**
* 递归方法实现链表翻转
* @param cur 当前处理的节点
* @param pre 当前节点的前一个节点(翻转后将成为当前节点的 next)
* @return 翻转后的链表头节点
*/
ListNode recur(ListNode cur, ListNode pre) {
// 基础条件:当前节点为空时,返回 pre 作为新的头节点
if (cur == null)
return pre;
// 暂存当前节点的下一个节点
ListNode tmp = cur.next;
// 将当前节点的 next 指向前一个节点,实现局部翻转
cur.next = pre;
// 递归处理下一个节点,并返回最终的头节点
retu