上周末参加了一次面试,国内知名互联网企业,面试全程也是相当紧张。
在第二轮当中,面试官让手写一个“最简单的”链表反转函数。
在以前的面试中我也遇到面试官要求现场写链表反转,我一般都会写循环迭代的方式,因为这题主要是考察指针使用的灵活程度,代码并不复杂,两个指针一次遍历并反转next指针指向就好了。
并且据我的经验,如果写成递归形式,那么最终的执行过程中不断调用自身有可能导致栈爆掉(stack overflow),除非是尾递归并且加上了优化。经过一番思索,我感觉链表逆序的递归形式并不太容易写成尾递归(因为最终需要将开始的头结点的next赋值为nullptr),所以花了几分钟,最后交给面试官的答案纸上依然写了循环迭代的形式。
当然,面试官也和我说,这肯定不是最简单的写法。
面试回来思考了下,并且上网查了一下,也没找到比较称心的简单算法。无外乎迭代,递归,然后自己写了个稍微麻烦点写法的尾递归。代码如下:
//迭代
node* reverse_1(node* l)
{
if (!l)
return l;
node* p1 = l;
node* p2 = l->n;
p1->n = nullptr;
while (p1 && p2)
{
node* p = p2->n;
p2->n = p1;
p1 = p2;
p2 = p;
}
return p1;
}
//常规递归
node* reverse_2(node* l)
{
if (!l || !l->n)
return l;
node* p = reverse_2(l->n);
l->n->n = l;
l->n = nullptr;
return p;
}
//尾递归,需要增加一个辅助递归函数
node* reverse_3_help(node* prev, node* curr)
{
if (!curr)
return curr;
node* next = curr->n;
curr->n = prev;
if (!next)
return curr;
return reverse_3_help(curr, next);
}
node* reverse_3(node* l)
{
node* prev = nullptr;
node* curr = l;
return reverse_3_help(prev, curr);
}
实际的结果是,reverse_1写法最常规,执行情况也最理想,速度快,并且不存在栈溢出的问题,reverse_2在链表长度超过一定范围时会导致栈溢出,编译器的优化也无法解决问题,reverse_3也是递归,但是使用了reverse_3_help这个辅助的递归函数实现了尾递归,当然,在没有优化的情况下依然存在栈溢出的问题,但是编译器可以优化尾递归调用,加上优化选项后编译出的程序一样不会出现栈溢出的情况,并且执行效率和reverse_1差不多。
亲测数据:
list长度过长时reverse_2段错误,测试长度为200000时,结果如下:
reverse_1-BEGIN:15:13:40:894...
reverse_1-END : 0.468217ms.
reverse_2-BEGIN:15:13:40:896...
reverse_2-END : 6.229769ms.
reverse_3-BEGIN:15:13:40:902...
reverse_3-END : 0.461505ms.
至于“最简单的”链表逆序,我暂时还没有想到。若有哪位大神可以不吝赐教,定感激涕零。