链表面试题
2018-03-25 18:36
197 查看
目录
用C语言实现一些经典的关于链表的面试题
题目中如果直接出现某个函数调用可能是之前关于链表博客里面写的函数,具体可以点击此处
想要下载整个代码(包括链表基本实现)戳此
void PrintHelp(SListNode* pHead);//1.从尾打印链表 void PrintFromTail(SListNode* pHead); void DeleteNodeNotTail(SListNode* pHead);//2.删除一个非尾节点(不能遍历) void SListInsert1(SListNode* pos, DataType data);//3.无头链表一个节点前插入链表(不能遍历) SListNode* JosephCircle(SListNode* s, size_t circle_num);//4.单链实现约瑟夫环 void SListBubbleSort(SListNode* pHead);//5.逆置链表 void SListBubbleSort(SListNode* pHead);//6.冒泡排序 SListNode* SListUnion(SListNode* pHead1, SListNode* pHead2);//7.合并两个有序链表,并且链表仍有序 SListNode* FindMiddleNode(SListNode* pHead);//8.查找链表的中间结点 SListNode* FindLastKNode(SListNode* pHead, size_t k);//9.查找倒数第K个结点 void* DeleteLastKNode(SListNode** pHead, size_t k);//10.删除倒数第K个结点 //11.链表带环问题 SListNode* JudgeCycle(SListNode* pHead);//判断是否带环 size_t GetCycleLenth(SListNode* meet);//求环长度 SListNode* GetCycleEntrance(SListNode* pHead, SListNode* meet);//求环入口 SListNode* JudgeCross(SListNode* pHead1, SListNode* pHead2);//12.判断链表是否相交(不带环) SListNode* JudgeCrossCycle(SListNode* pHead1, SListNode* pHead2);//13.判断链表是否相交(可以带环) SCListNode* CopyComplexList(SCListNode* pHead);//复制复杂链表
解题思路及实现代码
1.从尾打印链表
思路比较简单,递归打印即可实现void PrintFromTail(SListNode* pHead) { PrintHelp(pHead); printf("\n"); } void PrintHelp(SListNode* pHead)//从尾打印链表,辅助函数 { assert(pHead); if (pHead->_pNext != NULL) { PrintHelp(pHead->_pNext); } printf("%d ", pHead->_data);//递归打印 }
2.删除单链表的一个非尾节点(不能遍历)
删除节点需要找到要删除的节点前一个节点,但是由于不能遍历,无法得到该节点前一个节点,需要将问题进行转化,可将后一个节点数据前移,问题转化为删除目标节点的后一个节点,就能顺利解决,也是为什么题目中有“非尾节点”的要求,不知道要删除的节点的前一个节点是不可能删除的void DeleteNodeNotTail(SListNode* pos) { assert(pos); SListNode* cur = pos; SListNode* next = pos->_pNext; if (cur->_pNext == NULL) { printf("The node is tail.\n"); return; } cur->_data = next->_data;//把下一节点的数据前移,问题转为删除后一个节点 if (next->_pNext != NULL) { cur->_pNext = next->_pNext; } else { cur->_pNext = NULL; } free(next); }
3.无头链表一个节点前插入链表(不能遍历)
同第二题,思路类似,插入依旧需要知晓前一个节点,所以可以将新节点插在目标节点后面,再对数据进行调整void SListInsert1(SListNode* pos, DataType data) { SListNode* cur = pos; SListNode* next = BuySListNode(cur->_data); next->_pNext = cur->_pNext; next->_data = cur->_data; cur->_pNext = next; cur->_data = data; }
4.单链实现约瑟夫环
约瑟夫环并不难,实现只需要知道其原理即可,有疑问可点击上面链接了解约瑟夫环SListNode* JosephCircle(SListNode* pHead, size_t circle_num) { assert(pHead); SListNode* cur = pHead; SListNode* next = NULL; while (cur->_pNext) { cur = cur->_pNext; } cur->_pNext = pHead; cur = cur->_pNext; while (cur->_pNext != cur) { size_t count = circle_num; while (--count) { cur = cur->_pNext; } next = cur->_pNext; cur->_data = next->_data; cur->_pNext = next->_pNext; free(next); } return cur; }
5.逆置链表
链表逆置可通过三个指针来实现,关键是理清三个指针的关系,实现至少需要三个指针void ReverseSList(SListNode** ppHead) { assert(*ppHead); SListNode* cur = (*ppHead); SListNode* new_head = NULL; SListNode* next; while (cur) { next = cur->_pNext; cur->_pNext = new_head; new_head = cur; cur = next; } (*ppHead) = new_head; }
6.链表的冒泡排序
冒泡的关键在于控制冒泡结束的位置,设置指针记下结尾作为冒泡终止条件,即可完成冒泡排序的操作,我未进行优化,可以设置flag当数据未进行交换时,提前结束冒泡void SListBubbleSort(SListNode* pHead) { assert(pHead); SListNode* cur = pHead; SListNode* end = NULL; while (end != pHead) { while (cur->_pNext != end) { if (cur->_data > cur->_pNext->_data) { cur->_data ^= cur->_pNext->_data; cur->_pNext->_data ^= cur->_data; cur->_data ^= cur->_pNext->_data; } cur = cur->_pNext; } end = cur; cur = pHead; } }
7.双链表合并,合并后依旧有序(归并排序思想)
有点类似于归并排序,谁小先拿谁当作数据插入后方SListNode* SListUnion(SListNode* pHead1, SListNode* pHead2) { SListNode* new_head = NULL, *cur = NULL; if (pHead1 == NULL && pHead2 == NULL) { return NULL; } if (pHead1 == NULL) { return pHead2; } if(pHead2 == NULL) { return pHead1; } if (pHead1->_data <= pHead2->_data) { new_head = pHead1; pHead1 = pHead1->_pNext; } else { new_head = pHead2; pHead2 = pHead2->_pNext; } cur = new_head; while (pHead1 && pHead2) { if (pHead1->_data <= pHead2->_data) { cur->_pNext = pHead1; pHead1 = pHe d7cb ad1->_pNext; } else { cur->_pNext = pHead2; pHead2 = pHead2->_pNext; } cur = cur->_pNext; } if (pHead1 == NULL) { cur->_pNext = pHead2; } else { cur->_pNext = pHead1; } return new_head; }
8.查找链表的中间结点(思路:快慢指针法)
设置快慢指针,慢指针走一步,快指针走两步,循环只需要控制快指针的终止条件即可,当快指针结束指向末尾,慢指针即中点注:若为偶数个节点,查找上中位数的节点
SListNode* FindMiddleNode(SListNode* pHead) { assert(pHead); SListNode* fast = pHead; SListNode* cur = pHead; while (fast && fast->_pNext != NULL) { fast = fast->_pNext; if (fast->_pNext != NULL) { fast = fast->_pNext; cur = cur->_pNext; } } return cur; }
9.查找链表倒数第K个结点
依旧是通过快慢指针实现,快指针先走k步,慢指针再和快指针同步走即可SListNode* FindLastKNode(SListNode* pHead, size_t k) { assert(pHead); SListNode* cur = pHead; SListNode* end = pHead; while (k--) { if (end == NULL) { return NULL; } end = end->_pNext; } while (end) { end = end->_pNext; cur = cur->_pNext; } return cur; }
10.删除倒数第K个节点
这里通过几个链接使用的函数来实现功能,只要理解原理也可以写出(我说这些没别的,我懒)void DeleteLastKNode(SListNode** ppHead, size_t k) { SListErase(ppHead, FindLastKNode((*ppHead), k)); }
11.判断链表是否带环,带环求环长度和入口点
三个函数分别实现一个功能,是否带环可以通过快慢指针来判断,绝不能通过直接遍历(如果直接遍历,将出现死循环)判断环:快指针走两步,慢指针走一步,当存在环,就变成追及问题,每次逼近一个节点,迟早能追上,所以不能快指针一次走三步,他只能一次走两步
求环长度:通过判断环函数返回相遇点,相遇点必定在环内,此时记下相遇点位置,循环一次即可
求入口点:相遇点指针每次走一步,另一指针从头开始每次走一步,当两者相遇,该点就是入口点
原因:相遇点开始走过的长度 + 头指针走过的长度相加 = 环长度的n倍
SListNode* JudgeCycle(SListNode* pHead)//判断是否带环 { assert(pHead); SListNode* fast = pHead; SListNode* slow = pHead; while (fast) { slow = slow->_pNext; fast = fast->_pNext; if (fast != NULL) { fast = fast->_pNext; } if (fast == slow) { return slow; } } return NULL; } size_t GetCycleLenth(SListNode* meet)//求环长度 { SListNode* cur = meet; size_t count = 1; while (cur->_pNext != meet) { cur = cur->_pNext; count++; } return count; } SListNode* GetCycleEntrance(SListNode* pHead, SListNode* meet)//求环的入口点 { while (pHead != meet) { pHead = pHead->_pNext; meet = meet->_pNext; } return meet; }
12.判断链表是否相交(假设不带环)
思路非常简单,两个指针从头开始,直接走到尾,判断尾指针是否相同,即可判断另一种思路:转换为环求入口点
SListNode* JudgeCross(SListNode* pHead1, SListNode* pHead2) { //两种思路: //1.找到两个尾指针,若相同,相交,再长的链表先走相差的步数,两者同时走找交点 //2.同样的找尾指针,若相同,说明相交,将尾连任意表的头构成环,转换成求环入口问题 //这里只实现第一种 assert(pHead1 && pHead2); SListNode* cur1 = pHead1; SListNode* cur2 = pHead2; SListNode* plong = NULL; SListNode* pshort = NULL; int count = 0; int count1 = 1, count2 = 1; while (cur1->_pNext != NULL)//均走到尾,若尾指针相同,说明相交 { cur1 = cur1->_pNext; count1++; } while (cur2->_pNext != NULL) { cur2 = cur2->_pNext; count2++; } if (cur1 != cur2) { return NULL; } count = abs(count1 - count2); if (count1 > count2) { plong = pHead1; pshort = pHead2; } else { plong = pHead2; pshort = pHead1; } while (count--) { plong = plong->_pNext; } while (plong != pshort) { plong = plong->_pNext; pshort = pshort->_pNext; } return plong; }
13.判断链表是否相交(设链表可以带环)
画图分析情况,对每种情况分别进行处理即可,大量调用了之前写的的函数SListNode* JudgeCrossCycle(SListNode* pHead1, SListNode* pHead2) { SListNode* cur1 = JudgeCycle(pHead1); SListNode* cur2 = JudgeCycle(pHead2); SListNode* traversal = NULL; if (cur1 == NULL && cur2 == NULL)//情况1:都不带环 { return JudgeCross(pHead1, pHead2); } if (cur1 == NULL || cur2 == NULL)//情况2:一个有环一个无环,不可能相交 { return NULL; } //至此两个链均带环,考虑三种情况 //先取出环入口 cur1 = GetCycleEntrance(pHead1, cur1); cur2 = GetCycleEntrance(pHead1, cur2); traversal = cur1->_pNext; //情况3:相交且环入口相同 if (cur1 == cur2) { return cur1; } //情况4:带环相交入口不同 //情况5:带环不相交 while (1) { traversal = traversal->_pNext; if (traversal == cur2)//该种情况为情况4,打印两个地址返回其中一个指针 { printf("#%p\n#%p\n", cur1, cur2); return cur1; } if (traversal == cur1)//带环不想交,返回空 { return NULL; } } }
14.复杂链表的复制
解释一下题目,每个节点有两个指针相当于单链表的基础上多一个random指针,这个指针可以指向任意节点或为空,要求复制该链表直接复制非常困难,思路是在原链表的基础上每个节点后复制相同节点,如此复制,random节点就可以直接复制目标rando的next,再断开节点拆分成两个链表实现复制
如果这段话理解有困难可以看图理解
typedef struct SCListNode { DataType _data; struct SCListNode* _next; struct SCListNode* _random; }SCListNode; SCListNode* CopyComplexList(SCListNode* pHead) { SCListNode* cur = pHead; SCListNode* next = NULL; SCListNode* newnode = NULL; SCListNode* newhead = NULL; while (cur) { next = cur->_next; cur->_next = (SCListNode*)malloc(sizeof(SCListNode)); cur->_next->_next = next; cur = next; } cur = pHead; newhead = cur->_next; while (cur) { newnode = cur->_next; newnode->_random = cur->_random->_next; cur = cur->_next->_next; } cur = pHead; while (cur) { newnode = cur->_next; cur->_next = newnode->_next; cur = newnode->_next; if (newnode->_next == NULL) { continue; } newnode->_next = cur->_next; } return newhead; }
相关文章推荐
- 剑指Offer面试题26复杂链表的复制,面试题27二叉搜索树和双向链表(递归)
- 《剑指offer》(面试题17):合并两个排序的链表
- 面试题5. 从尾到头打印链表
- 剑指offer代码解析——面试题15求链表中倒数第K个结点
- c语言实现单链表基础面试题
- 剑指Offer面试题18:删除链表的节点
- 单链表常见面试题
- 线性表11|单链表小结:腾讯面试题 - 数据结构和算法16
- 关于链表的一些面试题
- 【C语言】单链表相关面试题(一)
- 面试题17:合并两个排序的链表
- 面试题5:从尾到头打印链表
- 微软系列面试题c/c++第一题双向链表
- 剑指 offer代码解析——面试题26复杂链表的复制
- 剑指Offer系列-面试题57:删除链表中重复的结点
- 剑指Offer:面试题17——合并两个排序的链表
- 链表面试题总结
- 数据结构之链表面试题汇总(三)判断单链表是否有环、取出环的起始点、得到有环链表中环的长度
- 【剑指offer】面试题13、在 O(1)时间上删除链表结点
- 面试题57. 删除链表中重复的结点