编程之美8:链表常见面试笔试题集合

楼楼这篇文章决定把面试中关于链表的常见面试题或者笔试题整理一下,现在目前为止只整理了四个题目,后面如果楼主看到还有什么题目需要记录的话,会一直更新的。楼楼略菜,如果有什么错误或不对的地方,希望各位看官留言指出,谢谢啦!

今天又是福来day了,好伤心啊,一周又过去了。

第一题:单链表是否存在环?环的入口是什么?

解法:设置两个快慢指针fast和slow指针,fast指针一下走两步,slow指针一下走一步,若最终fast == slow,那么就证明单链表中一定会有环。如果没有环的话,fast比slow指针走的快,那么fast一定先到达尾节点。

简单证明:利用相对运动的观念,fast指针一下走两步,slow指针一下走一步。那么slow指针相对于是静止的。当fast超过slow之后,那么fast指针以1步一下的速度追赶slow,如果链表有环的话,那么最后fast一定会追赶上slow。证明就这么从概念上理解一下就OK了吧。

代码见后面
如何找出环的入口点?
下面看一张图:
假设环入口到链表头节点的距离为a,蓝色轨迹为fast指针所走的距离设为2s,黑色轨迹为slow指针所走的距离设为s,假设环长为r,并假设fast和slow相遇时fast已经走过n圈,假设链表长度为L。那么
2s=s+nr
s=nr

s=(n1)r+r=(n1)r+La
a+x=(n1)r+La
最后有
a=(n1)r+Lax
其中 Lax 看图理解可知为相遇点到环入口的距离,恰好等于单链表头结点到换入口的距离。所以,为了求环的入口点,我们可以在fast和slow相遇之后,将slow指针重新指向单链表头结点,fast还在相遇点,并且fast和slow都以每次一步的距离往前走,相遇的地方就是环的入口点了。

如何确定带环链表的长度?
如上图可知,第一次相遇,有 s=nr ,那么可以通过s来确定环的长度。第二次,为求得环的入口节点, a=(n1)r+Lax 可以确定链表头结点到环入口节点的长度。那么 a+r 就是链表的总长度啦!

代码见后面

这里写图片描述

第二题:判断两个链表是否相交,若相交求出相交的第一个点(链表有环或者无环)?

一、判断链表是否相交

1. 链表无环

我们先来讨论链表无环的情况。有两种方法可以用来判断无环链表是否相交。

1)将链表A挂在链表B后面,如果AB有交点的话,那么将A挂在B后面,则会构成一个有环的单链表,则只需要利用第一题判断一个链表是否有环的函数,就可以判断出AB是否相交。
2)分别获得链表A和B的尾节点,若两个尾节点相等的话,那么证明AB相交

2.链表有环

1)其中一个链表有环,另外一个链表没环。则两个链表不可能相交。
2)两个链表都有环。
检测A链表环的入口点,判断这个点是否在B链表的环内(有前驱节点)。因为两个相交的有环链表,环都是公共的。

二、求相交的第一个节点

1. 链表无环

方法一:同样可以使用前面的办法,将链表A挂在链表B后面,如果AB有交点的话,那么将A挂在B后面,则会构成一个有环的单链表,且环的入口点就是相交的第一个节点。

方法二:假设A链表的长度为lenA,B链表的长度为lenB,假设lenA > lenB,反过来类似。那么使用两个节点指针,ptrA和ptrB,ptrA一下走lenA -lenB步,ptrByi一下走一步,ptrA = ptrB的那个节点就是链表相交的第一个节点

2. 链表有环

两个链表都有环,可以使用方法二来确定相交的第一个节点。带环链表的长度可以使用题目一种的方法来确定。

第三题:求单链表的倒数第K个节点.

  1. 方法一:
    假设链表的长度为N,链表节点从0开始,第0个节点,第1个节点,倒数第0个节点,倒数第一个节点这样子。那么倒数第K个节点,顺着数过去就是第 NK1 个节点。这样需要遍历链表两遍,第一遍获得链表的长度N,第二遍获取倒数第K个节点,效率比较差。
  2. 方法二:
    使用两个指针都指向链表的头结点,设两个指针分别为ptrA和ptrB,ptrA先走K步,之后ptrA和ptrB分别每次走一步。那么当ptrA到达尾节点的时候,ptrB所指向的节点恰好是倒数第K个节点。

代码见后面

第四题:链表逆序

如图所示,蓝色箭头为初始化的位置,红色箭头为第一次改变节点指向后的三个节点指针。这里为什么要使用三个指针呢?这是因为在我将A2的next指针指向A1之后,如果不使用A3来保存A2的下一个节点,那么这个节点就丢失了,下面就进行不下去了。
这里写图片描述
看代码见后面

附录:代码

此部分代码包括链表的创建,链表的反转,找出链表的第K个节点,创建带环链表,判断链表是否有环,找出环的入口点等部分的内容。代码主体来源于http://387929.blog.51cto.com/377929/1332211,我只是在这个代码的基础上写了一些和本篇文章相关的内容,在此表示感谢。

/******************************************
 * 文件名称:reverse.c
 * 文件描述:单链表逆序
            请注意:没有添加链表释放的函数,
            这个文件会造成内存泄露,请自己完善
 * 文件作者:by wangluojisuan, in 2013.11.27
 * 文件版本:1.2
 * 修改记录:
*******************************************/
#include <stdio.h>
#include <stdlib.h>
//定义链表节点,包含数据域data与指针域next
typedef struct _link_node_ {
    int data;                                       //数据域
    struct _link_node_ *next;                       //指针域
}linknode_t;
//定义链表,包含链表头及链表最大长度,当前长度
//链表头head不存储内容,head->next为第一个节点
//建议采用这种链表定义方式,可以包含更多的链表信息
typedef struct _link_list_ {
    int m_len;                                      //链表最大长度
    int c_len;                                      //链表当前长度
    linknode_t *head;                               //链表头
}linklist_t;
linklist_t * init_linklist(int len);                //初始化链表
linknode_t * _create_linknode(int value);           //创建节点
int insert_linklist(linklist_t *list, int value);   //链表中插入节点
void show_linklist(linklist_t *list);               //显示链表内容
void reverse_linklist(linklist_t *list);            //反转链表顺序
linknode_t* findKthNode(linklist_t *list, int K);   //找出倒数第K个节点
void  create_CirculList(linklist_t* list);          //创建一个循环链表
bool IsExitsLoop(linklist_t *list);                 //判断链表是否存在环
linknode_t* FindLoopPort(linklist_t *list);         //找出环的入口点
int main(void)
{
    //初始化链表
    //输出内容
    //反转链表
    //输出反转结果
    int i = 0;
    linklist_t *list = NULL;
    list = init_linklist(10);
    if (NULL == list)
        exit(-1);
    for (i = 0; i < 10; i++) {
        if (0 != insert_linklist(list, i))
            printf("error\n");
    }
    show_linklist(list);
    reverse_linklist(list);
    show_linklist(list);

    //找出链表的倒数第K个节点
    int K = 3;
    linknode_t* kthNode = findKthNode(list, K);
    printf("链表倒数第%d个节点的值为%d\n", K, kthNode->data);

    //构造一个循环链表
    create_CirculList(list);
    //判断是否存在环
    if (IsExitsLoop(list))
    {
        printf("链表有环\n");
    }
    else
    {
        printf("链表无环\n");
    }
    //找出环的入口点
    linknode_t *circleEntry = FindLoopPort(list);
    printf("环的入口点处data的值为:%d\n", circleEntry->data);

    return 0;
}

linknode_t* findKthNode(linklist_t *list, int K)
{
    linknode_t* fast = list->head;
    linknode_t* slow = list->head;
    //让快指针先走K步
    for (int i = 0; i < K; i++)
        fast = fast->next;

    //此后两个指针每次走一步,等到快指针到达尾节点的时候,慢指针所指向的就是倒数第K个节点了,倒数从第0个算起。
    while (fast->next != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

linknode_t* FindLoopPort(linklist_t *list)  
{  
    linknode_t *slow = list->head; linknode_t *fast = list->head;  

    while ( fast && fast->next )   
    {  
        slow = slow->next;  
        fast = fast->next->next;  
        if ( slow == fast ) break;  
    }  

    if (fast == NULL || fast->next == NULL)  
        return NULL;  

    slow = list->head;  
    while (slow != fast)  
    {  
         slow = slow->next;  
         fast = fast->next;  
    }  

    return slow;  
}  

/*=====================================================
 * 函数名称:IsExitsLoop
 * 函数功能:判断是否链表是否存在环
 * 函数参数:list
 * 返 回 值:void
 * 创 建 人:by duanxiaoxia,in 2015.04.24
 * 修改记录:
======================================================*/
bool IsExitsLoop(linklist_t *list)  
{  
    linknode_t *slow = list->head; linknode_t *fast = list->head;  

    while ( fast && fast->next )   
    {  
        slow = slow->next;  
        fast = fast->next->next;  
        if ( slow == fast ) break;  
    }  

    return !(fast == NULL || fast->next == NULL);  
}  

/*=====================================================
 * 函数名称:create_CirculList
 * 函数功能:创建一个循环链表,环随便指的
 * 函数参数:list
 * 返 回 值:void
 * 创 建 人:by duanxiaoxia,in 2015.04.24
 * 修改记录:
======================================================*/
void  create_CirculList(linklist_t* list)
{
    //随意设置一个环的入口点
    linknode_t *circleEntry = list->head->next->next;
    linknode_t *listEnd = list->head;

    //循环遍历到链表的尾节点,将尾节点的next节点指向circleEntry
    while (listEnd->next != NULL)
    {
        listEnd = listEnd->next;
    }
    listEnd->next = circleEntry;
}
/*=====================================================
 * 函数名称:_create_linknode
 * 函数功能:创建链表节点,节点next指向NULL
 * 函数参数:int value   节点数据域的内容
 * 返 回 值:linknode * 创建好的链表节点,如果出错返回NULL
 * 创 建 人:by wangluojisuan,in 2013.11.27
 * 修改记录:
======================================================*/
linknode_t * _create_linknode(int value)
{
    linknode_t *node = NULL;

    node = (linknode_t *)malloc(sizeof(linknode_t));
    if (NULL == node)
        return NULL;
    node->data = value;
    node->next = NULL;
    return node;
}
/*=======================================================
 * 函数名称:init_linklist
 * 函数功能:初始化一个链表,并设置最大链表长度
 * 函数参数:int len     链表的最大长度
 * 返 回 值:linklist *
            成功  初始化好的链表
            失败  NULL
 * 创 建 人:by wangluojisuan,in 2013.11.27
 * 修改记录:
========================================================*/
linklist_t * init_linklist(int len)
{
    linklist_t *list = NULL;
    list = (linklist_t *)malloc(sizeof(linklist_t));
    if (NULL == list)
        return NULL;
    list->m_len = len;   //设置最大长度
    list->c_len = 0; //设置当前长度
    list->head = _create_linknode(0);    //头结点赋值
    return list;
}
/*=======================================================
 * 函数名称:insert_linklist
 * 函数功能:向链表中插入数据,使用头插法,每次新插入的节点都
            放在head的后面
 * 函数参数:linklist_t *list    链表
            int         value   插入节点数据域值
 * 返 回 值:int
            成功  0
            失败  -1
 * 创 建 人:by wangluojisuan,in 2013.11.27
 * 修改记录:
========================================================*/
int insert_linklist(linklist_t *list, int value)
{
    linknode_t *node = NULL;
    if (list->c_len >= list->m_len)
        return -1;
    node = _create_linknode(value);
    node->next = list->head->next;
    list->head->next = node;
    (list->c_len)++;
    return 0;
}
/*=======================================================
 * 函数名称:show_linklist
 * 函数功能:显示链表的内容
 * 函数参数:linklist_t *list    链表
 * 返 回 值:void
 * 创 建 人:by wangluojisuan,in 2013.11.27
 * 修改记录:
========================================================*/
void show_linklist(linklist_t *list)
{
    linknode_t *node = NULL;
    node = list->head->next;
    while (NULL != node) {
        printf("%d  ", node->data);
        node = node->next;
    }
    printf("\n");
}
/*=======================================================
 * 函数名称:reverse_linklist
 * 函数功能:反转链表节点
 * 函数参数:linklist_t *list    链表
 * 返 回 值:void
 * 创 建 人:by duanxiaoxia, 2015.4.23
 * 修改记录:
========================================================*/
void reverse_linklist(linklist_t *list)
{
    linknode_t *A1 = list->head->next;
    linknode_t *A2 = A1->next;
    linknode_t *A3 = A2->next;

    while (A3->next != NULL)
    {
        A2->next = A1;
        if (A1 == list->head->next)
            A1->next = NULL;
        A1 = A2;
        A2 = A3;
        A3 = A3->next;
    }

    //到达最后一个节点,那么直接将A2指向A1,A3指向A2
    A2->next = A1;
    A3->next = A2;
    list->head->next = A3;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值