有关链表的一些常见面试题

来源:互联网 发布:算法书籍 编辑:程序博客网 时间:2024/06/06 15:01
在我们面试的时候链表是考官们比较喜欢考的问题之一,链表总的来说比较简单,但是却比较容易出错,我刚开始接触链表的时候,一不小心就出现了各种各样的问题,因此在这里总结了一些链表有关的面试题。

1.删除一个无头单链表的非尾节点

这里无头单链表并不是真正的无头,只是不告诉我们而已,在做这个题的时候,根据我们的思路,如果要删除一个节点的简单方法就是,把这个节点的前一个节点的next指向后一个节点,然后把这个节点释放掉。这样就删除了一个节点。但是我们的问题中我们找不到前一个节点,因此我们需要另辟蹊径,我们从题目里面看出删除的是非尾节点,为什么要删除非尾节点?我们从这里入手,画出了这样一个图去解决这个问题:

这里写图片描述
通过这样的思路就可以解决我们的问题了。有了这样的思路之后我们写代码就变得异常简单了

//删除一个无头单链表的非尾节点void DelNotTail(ListNode* pos){    ListNode* tmp = pos->next;    assert(pos && pos->next);    pos->data = tmp->data;    pos->next = tmp->next;    free(tmp);    tmp = NULL;}

2.在无头单链表的一个节点前插入一个节点

有了第一个题的思路,我们是不是可以举一反三呢?既然没办法直接在前面插入一个节点,那么我们在后面插入一个节点,然后交换两个数是不是就可以了。简单吧,想到了这一点,然后我们就可以写出代码了。
//在无头单链表的一个节点前插入一个节点void InsertFront(ListNode* pos, DataType x){    ListNode* tmp = BuyNode(pos->data);    ListNode* next = pos->next;    assert(pos && pos->next);    pos->next = tmp;    tmp->next = next;    pos->data = x;}

3.单链表实现约瑟夫环

做这个题首先就要知道什么是约瑟夫环,可以在网上百度一下什么是约瑟夫环(有一个关于约瑟夫环的故事),简单的来讲就是有一个圆环,每次每隔一定间隔释放一个节点,直到最后剩下唯一的一个节点,刚开始拿到这个题,我就被吓住了,让我在纸上画一画还行,让我写代码,感觉好难,但是当我认真的分析了之后发现并不难,因为它是一个环,每次释放掉一个节点之后,可以把前一个节点和后一个节点连接起来,那么**终止条件**是什么?只需要考虑它是一个环,而且最后剩下唯一一个节点,那么终止条件不就是他的下一个节点就是它本身了么?图如下,假设每隔k个节点释放一个节点,最后剩下了一个n

这里写图片描述
对于每个问题只要我们认真分析之后发现都不难

//在函数外不是一个环ListNode* JosephRing(ListNode* pList, DataType k){    ListNode* next = pList;    ListNode* cur = NULL;    while(next->next != NULL)  //让单链表成为一个环    {        next = next->next;    }    next->next = pList;    next = pList;    while(next->next != next)  //结束条件是下一个节点是本身    {        int count = k;        while(--count)        {            cur = next;            next = next->next;        }        cur->next = next->next;        next = next->next;    }    return cur;}

4.链表逆置

他弄个洋拿到一个问题先分析,对这个题依旧如此,即使第一次我们思考了很久才得出答案,或者都没有得出答案,但是我们思考过了,下次遇到到的时候我们就会有一个印象了。对于这个题,我们可以设置三个指针依次去逆置它,但由于逻辑性要很强,我也不很了解,所以我们采用了另一种方法,通过依次采摘节点的方法;
这里写图片描述
在这样分析了之后我们写出代码

//链表逆置void Reverse(ListNode** pList){    ListNode* NewList = NULL;    ListNode* cur = *pList;    if(*pList == NULL || (*pList)->next == NULL)        return;    while(cur)    {        ListNode* tmp = cur; //摘节点        cur = cur->next;        tmp->next = NewList;        NewList = tmp;    }    PrintList(NewList);}

5.合并有序链表

通过遍历两个链表,每次将较小的结点插入到合并后的新链表。

ListNode* MergeList(ListNode* pList1, ListNode* pList2){    ListNode* list = pList1;    ListNode* newlist = NULL;    //如果有链表为空,直接返回另外一个链表即可    if(pList1 == NULL)        PrintList(pList2);    else if(pList2 == NULL)        PrintList(pList1);    //摘一个头节点,选取较小的那个作为新链表的头    if(pList2->data < list->data)    {        list = pList2;        pList2 = pList2->next;    }    else    {        list = pList1;        pList1 = pList1->next;    }    newlist = list;    //有其中一个链表结束则结束循环    while(pList1 !=NULL && pList2 != NULL)    {        if(pList1->data <pList2->data)        {            newlist->next = pList1;            newlist = pList1;            pList1 = pList1->next;        }        else        {            newlist->next = pList2;            newlist = pList2;            pList2 = pList2->next;        }    }    //直接将剩下的链表链到新链表后    if(pList1 == NULL)        newlist->next = pList2;    else        newlist->next = pList1;    return list;}

6.寻找中间节点

设置一个快慢指针,快指针每次走两步,慢指针每次走一步,注意结束条件。

ListNode* MidNode(ListNode* pList){    ListNode* slow = pList;    ListNode* fast = pList;    //链表为空,直接返回空    if(pList == NULL)        return NULL;    while(fast != NULL &&fast->next != NULL)    {        fast = (fast->next)->next;        slow = slow->next;    }    return slow;}

7.查找单链表的倒数第k个节点

同样设置一个快慢指针,快指针先走K步,然后同时走,同样注意考虑边界条件

ListNode* FindKNode(ListNode* pList, DataType k){    ListNode* slow = pList;    ListNode* fast = pList;    //链表为空,直接返回空    if(pList == NULL)        return NULL;    while(k--)    {        if(fast == NULL)//链表节点少于k个,返回空            return NULL;        fast = fast->next;    }    while(fast)    {        slow = slow->next;        fast = fast->next;    }    return slow;}

8.这里留下几个稍微难一点的题目,可以思考下

1.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。
2.判断两个链表是否相交,若相交,求交点。(假设链表不带环)
3.判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
4.复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。

//ps: 复杂链表的结构
struct ComplexNode
{
int _data ; // 数据
struct ComplexNode * _next; // 指向下一个节点的指针
struct ComplexNode * _random; // 指向随机节点(可以是链表中的任意节点 or 空)
};

原创粉丝点击