这是Leetcode的上的一道题目:
Reverse a singly linked list.
Hint:
A linked list can be reversed either iteratively or recursively. Could you implement both?
题目要求时间复杂度为O(n),而且空间复杂度为常数级。这就必须对单链表一次遍历并且进行反序。同时,提示有迭代方法和递归方法两种实现可以,其中对于递归方法的实现需要有较多注意点。
1.非递归版本
非递归版本的实现比较容易想到,可以申请一个空白节点指向头结点,一个保存当前遍历节点的下一个节点的指针,在每一次遍历操作中,修改当前节点的指针即可。C语言实现如下:
数据结构定义:
typedef struct ListNode{
int val;
struct ListNode * next;
} ListNode;
函数实现:
ListNode* reverseList(ListNode* head) {
ListNode *q, * p = (ListNode *)malloc(sizeof(ListNode));
p->next = NULL;
while(head)
{
p->val = head->val;
q = head->next;
head->next = p;
p = head;
head = q;
}
q = p->next;
free(p);
return q;
}
经过测试后提交顺利通过。
2.递归版本
递归版本的实现起来困难一些,虽然最后的程序代码并不多,但是需要注意一些技巧。
首先,需要重新写一个递归函数,在给定的reverseList接口函数中进行调用;
其次,实现递归函数的接口,很容易想到内部的变量会在栈中保存,因此不能申请局部自动变量,否则就不是常数级的空间复杂度,因此,此处需要用到局部静态变量这个技巧(静态变量必须使用常量进行初始化)。
最后,递归调用的函数需要返回当前反转后的部分的表头和表尾,这样才能使用表尾指针修改next的值。对于表头指针,在每次递归调用时,直接传入指针变量是无法修改源指针的,需要传入指针的地址(也就是二级指针,参见另一篇文章http://blog.csdn.net/u010487568/article/details/48439313)。
总体来说,需要注意的点还是比较多的,具体实现如下(数据结构定义与非递归版本系统):
ListNode * reverseListRecursive(ListNode * head, ListNode ** nhead)
{
static ListNode node = {0, NULL};
static ListNode * last = &node;
if (! head->next)
{
*nhead = head;
return head;
}
last = reverseListRecursive(head->next, nhead);
last->next = head;
last = head;
return last;
}
ListNode * reverseList(ListNode * head)
{
ListNode * newhead = (ListNode * )malloc(sizeof(ListNode));
ListNode * last = reverseListRecursive(head, &newhead);
last->next = NULL;
return newhead;
}
递归调用时,会一直进行栈增长,直到遇到最后一个节点,此时就会执行if语句内的代码,因为只有一个元素,因此返回值尾指针和头指针是同一个。接收到返回的尾指针后,修改尾指针的next指向当前head节点,并且修改last为当前head节点。这样递归调用完成之后,返回的last指针的next的值不为NULL,而是指向原来链表的第二个元素,因此需要在reverseList函数中进行手动修改,并最终返回新的头指针。