编程之美8:链表常见面试笔试题集合
2015-04-24 10:18
274 查看
楼楼这篇文章决定把面试中关于链表的常见面试题或者笔试题整理一下,现在目前为止只整理了四个题目,后面如果楼主看到还有什么题目需要记录的话,会一直更新的。楼楼略菜,如果有什么错误或不对的地方,希望各位看官留言指出,谢谢啦!
今天又是福来day了,好伤心啊,一周又过去了。
简单证明:利用相对运动的观念,fast指针一下走两步,slow指针一下走一步。那么slow指针相对于是静止的。当fast超过slow之后,那么fast指针以1步一下的速度追赶slow,如果链表有环的话,那么最后fast一定会追赶上slow。证明就这么从概念上理解一下就OK了吧。
代码见后面
如何找出环的入口点?
下面看一张图:
假设环入口到链表头节点的距离为a,蓝色轨迹为fast指针所走的距离设为2s,黑色轨迹为slow指针所走的距离设为s,假设环长为r,并假设fast和slow相遇时fast已经走过n圈,假设链表长度为L。那么
2s=s+nr2s = s + nr
s=nrs =nr
有
s=(n−1)r+r=(n−1)r+L−as =(n - 1 )r + r = (n - 1)r + L - a
a+x=(n−1)r+L−aa + x = (n - 1)r + L - a
最后有
a=(n−1)r+L−a−xa = (n - 1)r + L - a - x
其中L−a−xL - a - x看图理解可知为相遇点到环入口的距离,恰好等于单链表头结点到换入口的距离。所以,为了求环的入口点,我们可以在fast和slow相遇之后,将slow指针重新指向单链表头结点,fast还在相遇点,并且fast和slow都以每次一步的距离往前走,相遇的地方就是环的入口点了。
如何确定带环链表的长度?
如上图可知,第一次相遇,有s=nrs =nr,那么可以通过s来确定环的长度。第二次,为求得环的入口节点,a=(n−1)r+L−a−xa = (n - 1)r + L - a - x可以确定链表头结点到环入口节点的长度。那么a+ra + r就是链表的总长度啦!
代码见后面
1)将链表A挂在链表B后面,如果AB有交点的话,那么将A挂在B后面,则会构成一个有环的单链表,则只需要利用第一题判断一个链表是否有环的函数,就可以判断出AB是否相交。
2)分别获得链表A和B的尾节点,若两个尾节点相等的话,那么证明AB相交。
2)两个链表都有环。
检测A链表环的入口点,判断这个点是否在B链表的环内(有前驱节点)。因为两个相交的有环链表,环都是公共的。
方法二:假设A链表的长度为lenA,B链表的长度为lenB,假设lenA > lenB,反过来类似。那么使用两个节点指针,ptrA和ptrB,ptrA一下走lenA -lenB步,ptrByi一下走一步,ptrA = ptrB的那个节点就是链表相交的第一个节点。
假设链表的长度为N,链表节点从0开始,第0个节点,第1个节点,倒数第0个节点,倒数第一个节点这样子。那么倒数第K个节点,顺着数过去就是第N−K−1N-K-1个节点。这样需要遍历链表两遍,第一遍获得链表的长度N,第二遍获取倒数第K个节点,效率比较差。
方法二:
使用两个指针都指向链表的头结点,设两个指针分别为ptrA和ptrB,ptrA先走K步,之后ptrA和ptrB分别每次走一步。那么当ptrA到达尾节点的时候,ptrB所指向的节点恰好是倒数第K个节点。
代码见后面
看代码见后面
今天又是福来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+nr2s = s + nr
s=nrs =nr
有
s=(n−1)r+r=(n−1)r+L−as =(n - 1 )r + r = (n - 1)r + L - a
a+x=(n−1)r+L−aa + x = (n - 1)r + L - a
最后有
a=(n−1)r+L−a−xa = (n - 1)r + L - a - x
其中L−a−xL - a - x看图理解可知为相遇点到环入口的距离,恰好等于单链表头结点到换入口的距离。所以,为了求环的入口点,我们可以在fast和slow相遇之后,将slow指针重新指向单链表头结点,fast还在相遇点,并且fast和slow都以每次一步的距离往前走,相遇的地方就是环的入口点了。
如何确定带环链表的长度?
如上图可知,第一次相遇,有s=nrs =nr,那么可以通过s来确定环的长度。第二次,为求得环的入口节点,a=(n−1)r+L−a−xa = (n - 1)r + L - a - x可以确定链表头结点到环入口节点的长度。那么a+ra + 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个节点.
方法一:假设链表的长度为N,链表节点从0开始,第0个节点,第1个节点,倒数第0个节点,倒数第一个节点这样子。那么倒数第K个节点,顺着数过去就是第N−K−1N-K-1个节点。这样需要遍历链表两遍,第一遍获得链表的长度N,第二遍获取倒数第K个节点,效率比较差。
方法二:
使用两个指针都指向链表的头结点,设两个指针分别为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; }
相关文章推荐
- JAVA基础笔试、面试常见问题集合
- Java程序基础测试 基础笔试、面试常见问题集合
- JAVA -- JAVA基础笔试、面试常见问题集合
- 链表的一些常见笔试面试问题总结及代码
- 笔试面试常见的链表操作
- net基础笔试、面试常见问题集合
- JAVA基础笔试、面试常见问题集合
- Android开发面试经——4.常见Android进阶笔试题(更新中...)
- 笔试面试中常见的位运算用法
- [互联网面试笔试汇总C/C++-17] 链表交点,链表环问题汇总
- 数据结构——链表(面试常见题型总结)
- 【笔试面试——26】链表06——复杂链表的复制
- 程序员笔试面试常见题总结,更新ing
- 程序员面试笔试宝典学习记录(常见面试笔试题目)
- Android开发面试经——4.常见Android进阶笔试题(更新中...)
- 125条常见的java面试笔试题大汇总之一
- Android开发面试经——4.常见Android进阶笔试题(更新中...)
- 125条常见的java面试笔试题大汇总
- 数据结构面试之二——双向链表表、循环链表、有序链表的常见操作
- Java常见笔试面试题目解析(二):final与static final变量(引用类型)的引用不变