目录
[2.1 遍历、迭代](#2.1 遍历、迭代)
[2.2 递归](#2.2 递归)
[3.1 c++](#3.1 c++)
[3.2 golang](#3.2 golang)
[4.1 遍历、迭代法](#4.1 遍历、迭代法)
[4.2 迭代法](#4.2 迭代法)
1、题目
链表是一种常用的数据结构,链表的特点是插入、删除节点的效率非常高,因为他不需要移动其他任何元素,只需要改变节点的指向接口,但是他的缺点也很明显,访问任意节点,都需要从链表头遍历,时间复杂度O(n)。
题目:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例一:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例二:
输入:head = []
输出:[]
2、解题思路
2.1 遍历、迭代
这种方法,说的直白点,就是硬拧,把链表指向掉头。
解题过程:
- 从头结点开始,遍历每个节点。
- 保存当前节点的下一个节点cur_next。
- 将当前节点的next指向前一个节点pre,
- 将pre指向当前节点,将当前节点执行cur_next。
- 返回新链表头结点。
2.2 递归
递归的本质在于反向工作,假设有链表:
n1->n2-> n3->......->nk->nk+1->......->nm.
nk之后的链表已经逆序完成,现在只需要将nk+1节点的next指向nk即可,为了避免nk、nk+1两个节点互指,也需要将nk的next指向null。
3、源代码
3.1 c++
- 遍历、迭代:
cpp
struct ListNode
{
int data;
ListNode *next;
};
// 反转链表-遍历、迭代
ListNode *reverseList(ListNode *head)
{
ListNode *pre = nullptr;
ListNode *cur = head;
if (head == nullptr || head->next == nullptr)
{
return head;
}
while (cur)
{
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
- 递归法:
cpp
// 反转链表-递归
ListNode *reverseList2(ListNode *head)
{
ListNode *cur = nullptr;
if (head == nullptr || head->next == nullptr)
{
return head;
}
cur = reverseList2(head->next);
head->next->next = head;
head->next = nullptr;
return cur;
}
3.2 golang
- 遍历法:
Go
func Reverse(head *ListNode) *ListNode {
var pre, next *ListNode
cur := head
for cur != nil {
next = cur.Next
cur.Next = pre
pre = cur
cur = next
}
return pre
}
- 递归法:
Go
// 反转链表--递归
func Reverse2(head *ListNode) *ListNode {
if head == nil || head.Next == nil {
return head
}
newNode := Reverse2(head.Next)
head.Next.Next = head
head.Next = nil // 防止循环
return newNode
}
4、复杂度分析
4.1 遍历、迭代法
时间复杂度:遍历法,需要遍历整个链表,因此时间复杂度为:O(n),n为链表长度。
空间复杂度:程序运行整个过程中,没有申请新的内存,因此空间复杂度为:O(1)。
4.2 迭代法
时间复杂度:递归算法仍然需要遍历整个链表,因此时间复杂度为:O(n),n为链表长度。
空间复杂度:递归需要申请栈空间来保存函数调用关系,因此空间复杂度为:O(n),最多n层调用。