外观
206.反转链表
约 5151 字大约 17 分钟
链表递归迭代
2025-05-24
原题
给你单链表的头节点 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
**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
在实际开发中,链表反转是一个非常基础且常见的操作,它常用于各种数据处理场景,比如在某些算法中需要从后向前处理链表数据时。
审题
面试场景模拟
面试官:请实现一个算法来解决这个问题。
我:好的。在开始之前,我想确认一下对题目的理解是否正确。这个问题是要求我将一个单向链表进行反转,使得原链表中的每个节点都指向它的前一个节点而不是后一个节点,对吗?
面试官:是的,具体来说,你需要将链表的指向方向完全反转过来,原来 A->B->C 的链表变成 C->B->A。
我:明白了。关于输入输出,我理解输入是链表的头节点,输出应该是反转后链表的头节点(即原链表的尾节点),这样理解对吗?
面试官:是的,输入会是链表的头节点 head,输出应该是反转后的链表的头节点。
我:关于这个问题的约束条件,我想确认一下:
- 链表可能为空,此时应该返回 null 或空
- 链表节点数量范围是 0 到 5000
- 节点值的范围是 -5000 到 5000
面试官:没错,这些约束条件都很重要。特别注意处理空链表的情况,以及确保你的算法能够处理较长的链表。
解析
思路概述
面对这个问题,我的第一反应是使用迭代法。我们可以遍历链表,在遍历的过程中改变每个节点的 next 指针,使其指向前一个节点。这种方法的时间复杂度是 O(n),空间复杂度是 O(1)。
不过,仔细思考后,我发现这个问题实际上是一个典型的链表操作问题,除了迭代法外,还可以使用递归法来解决,这提供了一种更优雅的实现方式。
就像翻转一本书的页面,我们可以一页一页地翻(迭代),也可以从最后一页开始,确保每一页都被翻转(递归)。
算法设计与分析
按照"五步法"来系统分析这个问题:
1. 确定数据结构选择
对于链表反转问题,我们需要操作的是链表节点之间的指针关系。在迭代法中,我们需要三个指针:
prev
:指向当前节点的前一个节点curr
:指向当前正在处理的节点next
:临时保存当前节点的下一个节点
这种设计的核心思想是在改变当前节点的 next 指针之前,先保存其原本指向的下一个节点,以免链表断裂。实际上,这种"三指针"技巧在许多链表操作中都有应用,比如链表的合并、删除等操作。
2. 确定算法核心逻辑
迭代法:
- 初始化
prev
为 null,curr
为 head - 遍历链表,对于每个节点:
- 保存
curr.next
到next
变量 - 将
curr.next
指向prev
- 将
prev
更新为curr
- 将
curr
更新为next
- 保存
- 返回
prev
(此时curr
已经为 null,prev
指向原链表的最后一个节点)
递归法: 递归的核心思想是:假设后面的子链表已经反转完成,现在要处理当前节点。
- 递归终止条件:如果当前节点为 null 或者只有一个节点,直接返回当前节点
- 递归调用:
newHead = reverseList(head.next)
- 反转当前节点与下一个节点的指向:
head.next.next = head
- 断开当前节点的原 next 指向:
head.next = null
- 返回新的头节点
newHead
这个转移过程看似简单,但蕴含了递归思想的精髓。如果深入思考,你会发现这与数学归纳法有着异曲同工之妙。
3. 初始化与边界条件处理
对于链表反转问题,我们需要特别处理以下边界情况:
- 空链表:直接返回 null
- 只有一个节点的链表:直接返回该节点
处理边界情况不仅是为了代码健壮性,更是算法正确性的保证。在实际工程中,这种严谨的思考方式同样重要。
4. 算法执行流程
迭代法的执行流程:
- 从头节点开始,逐个节点处理
- 对于每个节点,改变其 next 指针指向前一个节点
- 移动到下一个节点继续处理,直到处理完所有节点
递归法的执行流程:
- 递归到链表末尾
- 从末尾开始,逐个处理节点,改变指针指向
- 返回新的头节点(原链表的尾节点)
这里的顺序安排并非随意,而是经过精心设计,确保在改变指针指向时不会丢失对后续节点的访问。
5. 举例推导与复杂度分析
让我们用一个具体例子来验证算法的正确性:
输入:head = [1,2,3,4,5] 预期输出:[5,4,3,2,1]
迭代法执行过程:
- 初始状态:prev = null, curr = 1->2->3->4->5
- 第一次迭代:next = 2->3->4->5, 1->null, prev = 1->null, curr = 2->3->4->5
- 第二次迭代:next = 3->4->5, 2->1->null, prev = 2->1->null, curr = 3->4->5
- 第三次迭代:next = 4->5, 3->2->1->null, prev = 3->2->1->null, curr = 4->5
- 第四次迭代:next = 5, 4->3->2->1->null, prev = 4->3->2->1->null, curr = 5
- 第五次迭代:next = null, 5->4->3->2->1->null, prev = 5->4->3->2->1->null, curr = null
- 返回 prev = 5->4->3->2->1->null
递归法执行过程:
- 递归调用直到 head = 5
- 返回 newHead = 5
- 处理 head = 4:4->5, 5->4, 4->null,返回 newHead = 5->4
- 处理 head = 3:3->4, 4->3, 3->null,返回 newHead = 5->4->3
- 处理 head = 2:2->3, 3->2, 2->null,返回 newHead = 5->4->3->2
- 处理 head = 1:1->2, 2->1, 1->null,返回 newHead = 5->4->3->2->1
时间复杂度:O(n),其中 n 是链表的长度。我们需要遍历链表一次。 空间复杂度:
- 迭代法:O(1),只需要常数级别的额外空间。
- 递归法:O(n),递归调用会使用栈空间,最大深度为链表长度。
源码
核心代码模式
Java
/**
* 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 {
// 迭代法
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next; // 暂存下一个节点
curr.next = prev; // 反转指针
prev = curr; // 移动prev指针
curr = next; // 移动curr指针
}
return prev; // 返回新的头节点
}
// 递归法
public ListNode reverseListRecursive(ListNode head) {
// 基本情况:空链表或只有一个节点
if (head == null || head.next == null) {
return head;
}
// 递归反转剩余部分
ListNode newHead = reverseListRecursive(head.next);
// 反转当前节点与下一个节点的关系
head.next.next = head;
head.next = null;
return newHead; // 返回新的头节点
}
}
Python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 迭代法
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev = None
curr = head
while curr:
next_temp = curr.next # 暂存下一个节点
curr.next = prev # 反转指针
prev = curr # 移动prev指针
curr = next_temp # 移动curr指针
return prev # 返回新的头节点
# 递归法
def reverseListRecursive(self, head: Optional[ListNode]) -> Optional[ListNode]:
# 基本情况:空链表或只有一个节点
if not head or not head.next:
return head
# 递归反转剩余部分
new_head = self.reverseListRecursive(head.next)
# 反转当前节点与下一个节点的关系
head.next.next = head
head.next = None
return new_head # 返回新的头节点
JavaScript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
// 迭代法
var reverseList = function(head) {
let prev = null;
let curr = head;
while (curr !== null) {
const next = curr.next; // 暂存下一个节点
curr.next = prev; // 反转指针
prev = curr; // 移动prev指针
curr = next; // 移动curr指针
}
return prev; // 返回新的头节点
};
// 递归法
var reverseListRecursive = function(head) {
// 基本情况:空链表或只有一个节点
if (head === null || head.next === null) {
return head;
}
// 递归反转剩余部分
const newHead = reverseListRecursive(head.next);
// 反转当前节点与下一个节点的关系
head.next.next = head;
head.next = null;
return newHead; // 返回新的头节点
};
Go
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
// 迭代法
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
next := curr.Next // 暂存下一个节点
curr.Next = prev // 反转指针
prev = curr // 移动prev指针
curr = next // 移动curr指针
}
return prev // 返回新的头节点
}
// 递归法
func reverseListRecursive(head *ListNode) *ListNode {
// 基本情况:空链表或只有一个节点
if head == nil || head.Next == nil {
return head
}
// 递归反转剩余部分
newHead := reverseListRecursive(head.Next)
// 反转当前节点与下一个节点的关系
head.Next.Next = head
head.Next = nil
return newHead // 返回新的头节点
}
TypeScript
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
// 迭代法
function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr: ListNode | null = head;
while (curr !== null) {
const next: ListNode | null = curr.next; // 暂存下一个节点
curr.next = prev; // 反转指针
prev = curr; // 移动prev指针
curr = next; // 移动curr指针
}
return prev; // 返回新的头节点
}
// 递归法
function reverseListRecursive(head: ListNode | null): ListNode | null {
// 基本情况:空链表或只有一个节点
if (head === null || head.next === null) {
return head;
}
// 递归反转剩余部分
const newHead: ListNode | null = reverseListRecursive(head.next);
// 反转当前节点与下一个节点的关系
head.next.next = head;
head.next = null;
return newHead; // 返回新的头节点
}
ACM模式
Java
import java.util.*;
// 链表节点定义
class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public class Main {
// 迭代法反转链表
public static ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归法反转链表
public static ListNode reverseListRecursive(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseListRecursive(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
// 从数组创建链表
public static ListNode createLinkedList(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
ListNode dummy = new ListNode(0);
ListNode current = dummy;
for (int val : arr) {
current.next = new ListNode(val);
current = current.next;
}
return dummy.next;
}
// 将链表转换为数组
public static int[] linkedListToArray(ListNode head) {
List<Integer> list = new ArrayList<>();
while (head != null) {
list.add(head.val);
head = head.next;
}
int[] result = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = list.get(i);
}
return result;
}
// 打印链表
public static void printLinkedList(ListNode head) {
List<Integer> values = new ArrayList<>();
while (head != null) {
values.add(head.val);
head = head.next;
}
System.out.println(values);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入数组
String line = scanner.nextLine();
line = line.substring(1, line.length() - 1); // 移除 [ 和 ]
if (line.isEmpty()) {
System.out.println("[]"); // 空链表情况
return;
}
String[] parts = line.split(",");
int[] nums = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
nums[i] = Integer.parseInt(parts[i].trim());
}
// 创建链表
ListNode head = createLinkedList(nums);
// 反转链表(使用迭代法)
ListNode reversed = reverseList(head);
// 打印结果
printLinkedList(reversed);
scanner.close();
}
}
Python
# 链表节点定义
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 迭代法反转链表
def reverseList(head):
prev = None
curr = head
while curr:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
return prev
# 递归法反转链表
def reverseListRecursive(head):
if not head or not head.next:
return head
new_head = reverseListRecursive(head.next)
head.next.next = head
head.next = None
return new_head
# 从数组创建链表
def createLinkedList(arr):
if not arr:
return None
dummy = ListNode(0)
current = dummy
for val in arr:
current.next = ListNode(val)
current = current.next
return dummy.next
# 将链表转换为数组
def linkedListToArray(head):
result = []
while head:
result.append(head.val)
head = head.next
return result
# 打印链表
def printLinkedList(head):
values = []
while head:
values.append(head.val)
head = head.next
print(values)
if __name__ == "__main__":
# 读取输入数组
line = input().strip()
if line == "[]":
print("[]") # 空链表情况
else:
# 移除 [ 和 ],并分割字符串
nums = [int(x.strip()) for x in line[1:-1].split(",") if x.strip()]
# 创建链表
head = createLinkedList(nums)
# 反转链表(使用迭代法)
reversed_head = reverseList(head)
# 打印结果
printLinkedList(reversed_head)
JavaScript
// 链表节点定义
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
// 迭代法反转链表
function reverseList(head) {
let prev = null;
let curr = head;
while (curr !== null) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归法反转链表
function reverseListRecursive(head) {
if (head === null || head.next === null) {
return head;
}
const newHead = reverseListRecursive(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
// 从数组创建链表
function createLinkedList(arr) {
if (!arr || arr.length === 0) {
return null;
}
const dummy = new ListNode(0);
let current = dummy;
for (const val of arr) {
current.next = new ListNode(val);
current = current.next;
}
return dummy.next;
}
// 将链表转换为数组
function linkedListToArray(head) {
const result = [];
while (head !== null) {
result.push(head.val);
head = head.next;
}
return result;
}
// 打印链表
function printLinkedList(head) {
const values = [];
while (head !== null) {
values.push(head.val);
head = head.next;
}
console.log(JSON.stringify(values));
}
// 主函数
function main() {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('', (line) => {
if (line === '[]') {
console.log('[]'); // 空链表情况
rl.close();
return;
}
// 移除 [ 和 ],并分割字符串
const nums = line.slice(1, -1).split(',').map(x => parseInt(x.trim())).filter(x => !isNaN(x));
// 创建链表
const head = createLinkedList(nums);
// 反转链表(使用迭代法)
const reversed = reverseList(head);
// 打印结果
printLinkedList(reversed);
rl.close();
});
}
main();
Go
package main
import (
"fmt"
"strconv"
"strings"
)
// 链表节点定义
type ListNode struct {
Val int
Next *ListNode
}
// 迭代法反转链表
func reverseList(head *ListNode) *ListNode {
var prev *ListNode
curr := head
for curr != nil {
next := curr.Next
curr.Next = prev
prev = curr
curr = next
}
return prev
}
// 递归法反转链表
func reverseListRecursive(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newHead := reverseListRecursive(head.Next)
head.Next.Next = head
head.Next = nil
return newHead
}
// 从数组创建链表
func createLinkedList(arr []int) *ListNode {
if len(arr) == 0 {
return nil
}
dummy := &ListNode{Val: 0}
current := dummy
for _, val := range arr {
current.Next = &ListNode{Val: val}
current = current.Next
}
return dummy.Next
}
// 将链表转换为数组
func linkedListToArray(head *ListNode) []int {
var result []int
for head != nil {
result = append(result, head.Val)
head = head.Next
}
return result
}
// 打印链表
func printLinkedList(head *ListNode) {
var values []int
for head != nil {
values = append(values, head.Val)
head = head.Next
}
fmt.Println(values)
}
func main() {
var line string
fmt.Scanln(&line)
if line == "[]" {
fmt.Println("[]") // 空链表情况
return
}
// 移除 [ 和 ]
line = line[1 : len(line)-1]
if len(line) == 0 {
fmt.Println("[]") // 空链表情况
return
}
// 分割字符串并转换为整数
parts := strings.Split(line, ",")
nums := make([]int, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part != "" {
num, _ := strconv.Atoi(part)
nums = append(nums, num)
}
}
// 创建链表
head := createLinkedList(nums)
// 反转链表(使用迭代法)
reversed := reverseList(head)
// 打印结果
printLinkedList(reversed)
}
TypeScript
// 链表节点定义
class ListNode {
val: number;
next: ListNode | null;
constructor(val?: number, next?: ListNode | null) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
}
// 迭代法反转链表
function reverseList(head: ListNode | null): ListNode | null {
let prev: ListNode | null = null;
let curr: ListNode | null = head;
while (curr !== null) {
const next: ListNode | null = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归法反转链表
function reverseListRecursive(head: ListNode | null): ListNode | null {
if (head === null || head.next === null) {
return head;
}
const newHead: ListNode | null = reverseListRecursive(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
// 从数组创建链表
function createLinkedList(arr: number[]): ListNode | null {
if (!arr || arr.length === 0) {
return null;
}
const dummy: ListNode = new ListNode(0);
let current: ListNode = dummy;
for (const val of arr) {
current.next = new ListNode(val);
current = current.next;
}
return dummy.next;
}
// 将链表转换为数组
function linkedListToArray(head: ListNode | null): number[] {
const result: number[] = [];
while (head !== null) {
result.push(head.val);
head = head.next;
}
return result;
}
// 打印链表
function printLinkedList(head: ListNode | null): void {
const values: number[] = [];
while (head !== null) {
values.push(head.val);
head = head.next;
}
console.log(JSON.stringify(values));
}
// 主函数
function main(): void {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('', (line: string) => {
if (line === '[]') {
console.log('[]'); // 空链表情况
rl.close();
return;
}
// 移除 [ 和 ],并分割字符串
const nums: number[] = line.slice(1, -1).split(',').map(x => parseInt(x.trim())).filter(x => !isNaN(x));
// 创建链表
const head: ListNode | null = createLinkedList(nums);
// 反转链表(使用迭代法)
const reversed: ListNode | null = reverseList(head);
// 打印结果
printLinkedList(reversed);
rl.close();
});
}
main();
复杂度分析
时间复杂度:O(n),其中 n 是链表的长度。无论是迭代法还是递归法,我们都需要遍历链表中的每个节点一次。
空间复杂度:
- 迭代法:O(1),只需要常数级别的额外空间来存储几个指针变量。
- 递归法:O(n),递归调用会使用系统栈空间,最大深度为链表长度。
测试与优化
测试用例设计
设计几个有代表性的测试用例,包括:
基本测试用例:
- 输入:[1,2,3,4,5]
- 预期输出:[5,4,3,2,1]
边界情况测试:
- 空链表:输入 [],预期输出 []
- 单节点链表:输入 [1],预期输出 [1]
特殊输入测试:
- 两个节点链表:输入 [1,2],预期输出 [2,1]
- 包含重复值的链表:输入 [1,2,2,1],预期输出 [1,2,2,1]
代码优化
讨论代码可能的优化点:
性能优化:
- 迭代法已经达到了 O(1) 的空间复杂度,这是最优的。
- 递归法的空间复杂度是 O(n),如果对空间要求严格,应该使用迭代法。
代码简洁性优化:
- 迭代法的代码已经相当简洁,使用了三个指针完成反转。
- 递归法虽然代码行数少,但理解起来可能更复杂。
可读性优化:
- 添加适当的注释,解释每一步操作的目的。
- 使用有意义的变量名,如
prev
、curr
和next
,使代码更易理解。
面试官追问环节
面试官:这个算法的时间复杂度和空间复杂度是多少?如何计算的?
我:对于迭代法和递归法,时间复杂度都是 O(n),其中 n 是链表的长度。这是因为我们需要遍历链表中的每个节点一次,执行常数时间的操作。
空间复杂度方面,迭代法是 O(1),因为我们只使用了三个指针变量,不管链表多长,额外空间使用量都是常数级别的。
而递归法的空间复杂度是 O(n),这是因为递归调用会使用系统栈空间,最大递归深度等于链表长度,因此空间使用与链表长度成正比。
面试官:如果要求你只遍历一次链表就完成反转,你的算法是否满足要求?如果不满足,如何优化?
我:是的,我的迭代算法确实只遍历了一次链表。在遍历过程中,我们使用三个指针(prev、curr、next)来跟踪节点,并在遍历的同时完成指针反转。
具体来说,对于每个节点,我们先保存其下一个节点(next = curr.next),然后将当前节点的 next 指针指向前一个节点(curr.next = prev),最后移动 prev 和 curr 指针(prev = curr, curr = next)。整个过程只需要一次遍历就能完成链表的反转。
递归法虽然代码看起来简洁,但实际上也是对链表的一次完整遍历,只是遍历方式是通过递归调用实现的。
面试官:在实际工程中,你会选择迭代法还是递归法来实现链表反转?为什么?
我:在实际工程中,我会优先选择迭代法来实现链表反转,原因有以下几点:
空间效率:迭代法的空间复杂度是 O(1),而递归法是 O(n)。对于长链表,递归可能导致栈溢出。
性能考虑:递归调用涉及函数调用栈的开销,包括保存和恢复上下文,这比简单的迭代循环要慢。
代码可读性:虽然递归代码看起来更简洁,但迭代法的逻辑更直观,更容易被其他开发者理解和维护。
调试难度:迭代法更容易调试,可以清晰地看到每一步的状态变化,而递归调用的调试相对复杂。
不过,递归法也有其价值,特别是在教学和理解算法思想方面。在某些情况下,如果链表不是很长,并且代码简洁性比性能更重要,递归法也是一个不错的选择。
结语
链表反转是一个经典的数据结构操作问题,也是面试中的常见题目。通过这个问题,我们不仅学习了如何操作链表,还深入理解了迭代和递归两种不同的思维方式。迭代法直观高效,递归法优雅简洁,两者各有优势。在实际应用中,我们需要根据具体场景选择合适的方法。掌握这个基础操作,将为我们处理更复杂的链表问题打下坚实基础。记住,无论是迭代还是递归,关键在于理解指针操作和链表节点之间关系的变化。