1.力扣题目链接
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
--
1)双指针
// 双指针
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
ListNode temp = null;
while (cur != null) {
temp = cur.next;// 保存下一个节点
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
}
-
首先,定义了三个指针变量:
prev:用于存储反转后链表的头节点,初始值为null,因为在开始时还没有反转后的链表。cur:当前遍历到的节点,初始值为输入链表的头节点head。temp:用于暂存下一个节点的引用,在修改当前节点的指向后,可以用它来找到下一个节点,以继续遍历。
-
进入循环
while (cur != null),这个循环会遍历原链表中的每一个节点。 -
在循环中,首先保存当前节点
cur的下一个节点的引用,即temp = cur.next。这是因为在修改当前节点cur的next指针后,会失去对下一个节点的引用,所以需要在修改前保存。 -
接着,将当前节点
cur的next指针指向prev,完成了反转操作,即cur.next = prev。这样,原链表中的当前节点变成了反转链表中的头节点。 -
然后,更新
prev为当前节点cur,更新cur为下一个节点temp,继续遍历下一个节点。 -
重复以上步骤,直到遍历完整个链表,最后返回
prev,即反转后链表的头节点。
这段代码采用了迭代的方式来反转链表,使用双指针 prev 和 cur 来实现。这是一种常用的反转链表的方法,它能够在线性时间复杂度内完成链表的反转操作。
举例:
假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。
开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:
-
初始状态:
prev = nullcur = 1temp = null
-
进入循环,执行第一次迭代:
- 保存下一个节点的引用:
temp = 2 - 反转当前节点:
cur.next = null,此时1成为反转链表的头节点。 - 更新
prev为当前节点:prev = 1 - 更新
cur为下一个节点:cur = 2
此时,反转链表为:1 -> null,
prev指向1,cur指向2。 - 保存下一个节点的引用:
-
第二次迭代:
- 保存下一个节点的引用:
temp = 3 - 反转当前节点:
cur.next = prev,变成 2 -> 1 - 更新
prev为当前节点:prev = 2 - 更新
cur为下一个节点:cur = 3
此时,反转链表为:2 -> 1,
prev指向2,cur指向3。 - 保存下一个节点的引用:
-
重复上述步骤,继续迭代,直到遍历完整个链表。
最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,prev 指向5,cur 指向null。
整个链表已经成功反转。函数返回 prev,它指向反转链表的头节点5。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。
2)递归
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null, head);
}
private ListNode reverse(ListNode prev, ListNode cur) {
if (cur == null) {
return prev;
}
ListNode temp = null;
temp = cur.next;// 先保存下一个节点
cur.next = prev;// 反转
// 更新prev、cur位置
// prev = cur;
// cur = temp;
return reverse(cur, temp);
}
}
这段 Java 代码实现了反转链表的功能,但它使用了递归的方式来实现。与之前的迭代方式相比,递归是一种更为简洁但可能会占用更多内存的方法。下面是对这段代码的解释:
-
public ListNode reverseList(ListNode head)是公共方法,用于启动反转链表的过程。它接受链表的头节点head作为输入,并通过调用私有递归函数reverse来实际执行反转操作。 -
private ListNode reverse(ListNode prev, ListNode cur)是私有递归函数。这个函数接受两个参数:prev:表示已经反转部分链表的头节点。在初始调用时,传入的是null,表示反转的链表初始为空。cur:表示当前正在处理的节点。
-
在递归函数内部,首先检查当前节点
cur是否为null,如果是,说明已经遍历完了整个链表,可以返回已反转链表的头节点prev。 -
如果
cur不是null,则继续进行反转操作:- 保存当前节点
cur的下一个节点的引用到temp变量中,以便在递归调用时使用。 - 将当前节点
cur的next指针指向prev,即将当前节点加入已反转链表的头部。 - 然后,递归调用
reverse函数,将prev更新为当前节点cur,将cur更新为temp(下一个节点的引用),继续反转下一个节点。
- 保存当前节点
-
递归调用会一直执行,直到
cur变为null,此时递归结束,函数开始返回反转后链表的头节点prev。
这段代码的核心思想与之前的迭代版本相同,都是通过不断地将当前节点的 next 指向前一个节点来反转链表,只是使用递归方式来实现。同样,这会将原链表反转,最后返回反转后链表的头节点。
举例:
假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。
开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:
-
初始状态:
prev = nullcur = 1
-
第一次递归调用
reverse(prev, cur):- 由于
cur不为null,我们执行以下步骤:- 保存当前节点
cur的下一个节点的引用到temp:temp = 2 - 反转当前节点
cur,将其next指向prev:cur.next = null,此时1成为反转链表的头节点。 - 更新
prev为当前节点cur:prev = 1 - 更新
cur为下一个节点temp:cur = 2
- 保存当前节点
- 由于
-
第二次递归调用
reverse(prev, cur):-
此时的状态为:
prev指向1,cur指向2temp指向3
-
由于
cur不为null,我们执行以下步骤:- 保存当前节点
cur的下一个节点的引用到temp:temp = 3 - 反转当前节点
cur,将其next指向prev,变成 2 -> 1。 - 更新
prev为当前节点cur:prev = 2 - 更新
cur为下一个节点temp:cur = 3
- 保存当前节点
-
-
重复上述递归调用的步骤,直到遍历完整个链表。
最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,prev 指向5,cur 指向null。
整个链表已经成功反转。函数返回 prev,它指向反转链表的头节点5。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。
3)从后向前递归
// 从后向前递归
class Solution {
ListNode reverseList(ListNode head) {
// 边缘条件判断
if(head == null) return null;
if (head.next == null) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode last = reverseList(head.next);
// 翻转头节点与第二个节点的指向
head.next.next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head.next = null;
return last;
}
}
这段代码是一个递归方式实现的链表反转函数,其思路是从后向前递归处理链表节点。下面是对这段代码的解释:
-
ListNode reverseList(ListNode head)是一个公共方法,用于启动链表反转的过程。它接受链表的头节点head作为输入参数。 -
首先,进行一些边缘条件判断:
- 如果输入的头节点
head为null,即链表为空,直接返回null,因为反转一个空链表还是空链表。 - 如果头节点的下一个节点
head.next为null,即链表只有一个节点,直接返回头节点head,因为反转一个只有一个节点的链表还是它自己。
- 如果输入的头节点
-
如果链表包含多个节点,就进行递归调用。递归调用的参数是链表的头节点的下一个节点,即
reverseList(head.next)。这一步是为了翻转链表中除了头节点以外的部分。 -
在递归调用返回后,
last变量将包含已翻转的链表的头节点。此时,链表的头节点是原链表中的尾节点。 -
然后,将头节点
head和尾节点last进行翻转操作:head.next.next = head:这一步将尾节点last的下一个节点指向头节点head,实现了翻转。head.next = null:将头节点的next指针设置为null,因为它变成了新的尾节点。
-
最后,函数返回
last,它现在是反转后链表的新头节点,而整个链表已经成功反转。
这段代码的关键点是使用递归来反转链表,并在递归调用的返回后执行头节点和尾节点的翻转操作,最终得到反转后的链表。这种递归方式同样能够成功地反转链表。
举例:
假设有一个链表,包含以下节点:1 -> 2 -> 3 -> 4 -> 5。
开始时,链表的头节点是1,然后我们调用 reverseList 函数对这个链表进行反转。下面是反转的步骤:
-
初始状态:
head = 1head.next = 2
-
第一次递归调用
reverseList(head.next):- 进入递归,此时
head是2,链表变成了2 -> 3 -> 4 -> 5。 - 递归继续,
head是3,链表变成了3 -> 4 -> 5。 - 递归继续,
head是4,链表变成了4 -> 5。 - 递归继续,
head是5,链表变成了5。 - 此时,
head是链表的尾节点5,满足递归的边缘条件,递归返回。
- 进入递归,此时
-
此时的
last是5,即已经翻转后的链表的头节点。回到第一次递归调用的上下文:head.next.next = head:将3 -> 4 -> 5 中的4的next指向3,翻转了3和4之间的连接,链表变成了2 -> 3 <- 4 <- 5。head.next = null:将2的next指向null,因为2成为了新的尾节点,链表变成了2 -> 3 <- 4 <- 5。
-
第一次递归调用返回,此时
last是5,函数返回last。
最终,反转链表的状态为:5 -> 4 -> 3 -> 2 -> 1,last 指向5,即反转后链表的新头节点。原来的链表已经被颠倒成了5 -> 4 -> 3 -> 2 -> 1。