反转链表是笔试、面试中考察频率较高的算法题目。本文给出两种解题思路,并用C++实现编码。
1. 递归法
递归写法较为简单,但算法效率不高。算法时间复杂度为 O(n), 空间复杂度为 O(n)。
1.1 思路
递归是函数调用自身的一个过程,可以将问题转换为较小规模的问题进行求解。使用递归法解题时,注意一定不要陷入细节。毕竟人脑的算力是有限的,不能人工维护多个栈。
用递归解题需要考虑以下几点:递归函数效果、递归的较小规模问题、递归的基础条件、递归的返回值。
写递归时,一定要明确递归函数可以达到怎样的效果。本题的效果就是:以头结点head为起点的整个链表反转,并返回新的头结点。
较小规模的问题是:反转以头结点后驱结点为起点的整个链表,并返回新的起点。
递归的基础条件是:只有一个结点的链表,反转后是其自身。如果链表为空,反转之后依旧为空。
递归的返回值:本题用 ListNode* last 接收递归返回值(非基础条件)。
1.2 代码实现
具体代码实现如下
- 首先确定基础条件
- 解决较小规模问题 reverseList(head->next),得到反转链表,返回新结点 last 。
- 较小规模问题解决后,需要将原来的头结点与反转链表相连接。
- 将链表末尾 head 指向NULL
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == NULL)
return head;
if(head->next == NULL)
return head;
ListNode* last = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return last;
}
};
1.3 实现细节
算法实现过程需要注意基础条件的写法。尤其是链表为空的情况,因此需要判断链表是否为空和链表是否只有一个结点。空指针情况下, head->next 会报错。
Line 16: Char 18: runtime error: member access within null pointer of type 'ListNode' (solution.cpp)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:25:18
注意检测下述情况
2. 迭代法
迭代法(双指针法)原地反转两个结点的指向,算法时间复杂度为 O(n),算法的空间复杂度为 O(1)。
2.1 思路
迭代法的本质是遍历和更新状态。本题需要遍历整个链表实现反转。链表的指向具有单向性,所有要想实现反转链表,需要3个结点指针:pre,cur 还有 nxt。cur 是遍历链表的结点,pre 是 cur 的前驱, nxt 是 cur 的后继,用来保存 cur->next,否则一次局部反转后,我们就再也找不到 cur 的后继结点了。
遍历的终止条件:遍历链表完成,即 cur == NULL。
遍历的操作:每个结点实现局部反转,cur->next = pre;
更新状态:pre = cur; cur = nxt;
返回值为反转后的链表头结点 pre。
2.2 代码实现
注意使用 nxt 保存 cur->next。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while(cur != NULL)
{
ListNode* nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
};