楼楼这篇文章决定把面试中关于链表的常见面试题或者笔试题整理一下,现在目前为止只整理了四个题目,后面如果楼主看到还有什么题目需要记录的话,会一直更新的。楼楼略菜,如果有什么错误或不对的地方,希望各位看官留言指出,谢谢啦!
今天又是福来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=(n−1)r+r=(n−1)r+L−a
a+x=(n−1)r+L−a
最后有
a=(n−1)r+L−a−x
其中 L−a−x 看图理解可知为相遇点到环入口的距离,恰好等于单链表头结点到换入口的距离。所以,为了求环的入口点,我们可以在fast和slow相遇之后,将slow指针重新指向单链表头结点,fast还在相遇点,并且fast和slow都以每次一步的距离往前走,相遇的地方就是环的入口点了。
如何确定带环链表的长度?
如上图可知,第一次相遇,有 s=nr ,那么可以通过s来确定环的长度。第二次,为求得环的入口节点, a=(n−1)r+L−a−x 可以确定链表头结点到环入口节点的长度。那么 a+r 就是链表的总长度啦!
代码见后面
我们先来讨论链表无环的情况。有两种方法可以用来判断无环链表是否相交。
1)将链表A挂在链表B后面,如果AB有交点的话,那么将A挂在B后面,则会构成一个有环的单链表,则只需要利用第一题判断一个链表是否有环的函数,就可以判断出AB是否相交。
2)分别获得链表A和B的尾节点,若两个尾节点相等的话,那么证明AB相交。
1)其中一个链表有环,另外一个链表没环。则两个链表不可能相交。
2)两个链表都有环。
检测A链表环的入口点,判断这个点是否在B链表的环内(有前驱节点)。因为两个相交的有环链表,环都是公共的。
方法一:同样可以使用前面的办法,将链表A挂在链表B后面,如果AB有交点的话,那么将A挂在B后面,则会构成一个有环的单链表,且环的入口点就是相交的第一个节点。
方法二:假设A链表的长度为lenA,B链表的长度为lenB,假设lenA > lenB,反过来类似。那么使用两个节点指针,ptrA和ptrB,ptrA一下走lenA -lenB步,ptrByi一下走一步,ptrA = ptrB的那个节点就是链表相交的第一个节点。
两个链表都有环,可以使用方法二来确定相交的第一个节点。带环链表的长度可以使用题目一种的方法来确定。
代码见后面
如图所示,蓝色箭头为初始化的位置,红色箭头为第一次改变节点指向后的三个节点指针。这里为什么要使用三个指针呢?这是因为在我将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;
}