说说一个简单的面试题

过节了终于有时间写写东西了,有些东西不记录下来就会一点点忘掉。

前几天去面试,有一道题我回答的不太令自己满意,回来之后思索了一下,又想到了一些东西,记录一下。

简单问题?

题目就是简单的反转单向链表。

我很快给出了一段代码:

void reverse_chain(Node *h)
{
    if(!h || !h->next) return ;
    Node *previous = NULL,
         *current  = h,
         *next     = h->next;
    while(next) {
        current->next = previous;
        previous = current;
        current  = next;
        next     = next->next;
    }
    current->next = previous;
    h->next = NULL;
}

这段代码显然是有问题的,它没有返回反转后的链表的头指针,只需在最后加上一句 return current; 即可。比较理想的代码如下所示:

Node * reverse_chain(Node *h)
{
    if(!h || !h->next) return h;
    Node *previous = NULL,
         *current  = h,
         *next     = h->next;
    while(next) {
        current->next = previous;
        previous = current;
        current  = next;
        next     = next->next;
    }
    current->next = previous;
    h->next = NULL;
    return current;
}

复杂问题?

接下来,面试官问了我一个问题:可不可以只使用两个指针,完成单向链表的反转?

我思索了一段时间,认为不能够只使用两个指针完成单向链表的反转。理由是这样的,反转单向链表的过程,需要将当前节点指向前一个节点,这里需要两个指针,因为前一个节点的指针已经指向了更前一个节点;完成这一动作后,需要继续处理下面的节点,因此还需要能够索引到下一个节点;这样,一共需要三个指针。(我还特意向面试官询问了,是否考虑在调用栈上保存信息的情况,即使用递归算法)

其实这个答案是正确的。但是面试官一再强调,一定可以只使用两个指针完成单向链表的反转。于是我向面试官请求一些提示。

面试官给出了这样的提示:如果像这样每次处理完一个节点后向后移动一个节点继续处理,则需要三个指针;如果不是采用向后移动的方式,是否有可能采用更少的指针?譬如说每次都将下一个节点插入到链表头部。

每次将当前节点从链表中取下插入到链表头部似乎仍然需要3个指针:一个指向表头,一个指向当前节点,一个指向当前节点的上一个节点。

图表1

图1:将当前节点插入到链表头反转单向链表示意图。

 

最后终于想到了利用原来链表头的next域来存储Previous节点的方法。

面试官友善的说道,最终原来链表头的next域是要指向Nil的,因此利用原来链表头的next域进行存储是一种很自然的想法。

无意义问题?

说实在的,这个链表反转的方法实际上还是用了3个指针,可以说一开始我就没走到面试官所希望我想到的东西的道路上。实际上,我内心深处还是颇为不赞同这样一种方式的,因为这个反转链表的方法有效率上的问题:即如何快速的访问原来链表头的next域?如果为此引入一个指针的话,就实际上需要4个指针,比原来的方法还要多出来一个;否则就需要遍历O(n)的链表才能够发现原来链表头的next域,时间复杂度上得不偿失。

并且,实际上使用了3个指针还是2个指针,在C程序编译完后,实际上只是对应着使用了3个寄存器还是2个寄存器,本质上并没有时空性能上的提升,还为此付出了额外的代码复杂度,实在是没有什么意义。

所以?

所以其实这个问题就是没什么意义,面试官对于问题的描述和引导也做得不好,但是对于这一问题所带来的新的思路,确实令我思索了很久。有的时候,我就是在做一些没有意义的事情,但是探索过程本身,却能给我带来很大的乐趣和价值。

Technorati 标签: ,,
posted @ 2012-01-24 19:29  HCOONa  阅读(1507)  评论(4编辑  收藏  举报