那些年我们面试过的单链表算法总结(二)
2014-07-04 09:54
330 查看
上一篇博文描述了一小部分单链表的算法,这篇将继续深入,看看还有哪些面试中常见的链表题目。
题目6: 合并两个有序链表,元素升序。
题目7: 链表的归并排序 O(nlogn)
基于上述合并两个已排序链表的操作,我们可以引入链表的归并排序代码:
题目8: 检测链表是否存在环路
检测链表环路的方法类似于龟兔赛跑,如果跑道是环行的,那么龟兔必然在某一点相遇,利用这一特性,我们以写出如下代码。
题目9: 找出带环链表中环的起点
这道题有些tricky的地方,需要用到一点点数学知识来分析它。
前面一题我们已经看到,当快指针与慢指针相遇时,快指针走过的距离是慢指针的2倍,分别标记为DF和DS,DF=2DS。
假设链表头至环路起点距离为D1,慢指针在环路中走过的距离为D2,环路总长度为DL则可以得出以下关系式:
2(D1+D2) = D1+D2+nDL --> D1=(n-1)DL + DL-D2;
那么可以看到当将某一指针A放在开头,而将另一指针B放在两指针相遇的位置,两指针以相的速度向前移动,则A走过D1的距离后,B走过多次环路与DL-D2(即初次相遇点到环路起点的距离),他们再次相遇,而该相遇到点即为环路起点。
题目10: 判断两单链表是否相交
这题是《编程之美》上的,我觉得有两种方法比较巧妙。
解法1: 将其中一个链表首尾相连,然后根据题目8中的算法来验证另一条链表是否存在环路,存在则两链表相交,不存在,则两链表不相交。
解法2:利用单链表的特性,一个结点的出度只能为1,因此两个链表相交,则某一点之后的所有结点应该是一样的,那么最简单的就是遍历两链表找到尾结点,判断尾结点指针是否一致即可。代码如下:
题目11: 若两链表相交,求两链表的交点
解法1:这道题同样可以参考题目9的方法来找到环路的起点即为相交的起点。
解法2:利用hash map, 由其中一个链表构造hash map,然后从另一个链表开始逐个检查每一个结点是否在hash map中,第一个存在的即为交点。引入O(n)的空间复杂度。
解法3:有些tricky,即假设两个链L1和L2表长度分别是 A,B,并假设A>=B,那么我们可以求得差值 D=A-B, 那么我们首先将L1移动D个结点,然后同时将L1,L2向后遍历并比较结点指针是否一致,如果一致则中断循环,该结点即为首个相交点。该解法巧妙地利用了两个链表的长度差,以获得两个链表距离尾部距离相同的起点,该起点也即跑离相交点相同的起点,因为单链表相并,后面的结点都是一样的,依此结点开始逐个判断即可得到相交结点的位置。代码如下:
题目12: Linux 内核链表的实现
我们一般看到的链表形式是这样的:
题目6: 合并两个有序链表,元素升序。
ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { if(l1==NULL) return l2; if(l2==NULL) return l1; ListNode *head = (l1 -> val) > (l2->val) ? l2:l1; ListNode *remain = (l1 -> val) > (l2->val) ? l1:l2; ListNode *tmp; ListNode *tail = head; while(tail != NULL&&tail -> next != NULL && remain != NULL) { if( tail -> val <= remain-> val && tail-> next -> val >= remain -> val) { tmp = remain; remain = remain -> next; tmp -> next = tail -> next; tail -> next = tmp; tail = tail -> next; } else { tail = tail -> next; } } if(remain!=NULL) tail -> next = remain; return head; }
题目7: 链表的归并排序 O(nlogn)
基于上述合并两个已排序链表的操作,我们可以引入链表的归并排序代码:
int getListLen(ListNode *head) //get how many nodes in the list starting from head. { int i = 0; while( head != NULL) { i++; head = head -> next; } return i; } //The main function which deals with merge sort. ListNode *mergeSort(ListNode *&head, int start, int end) { ListNode *tmp = head; if( start == end) { head = head -> next; tmp -> next = NULL; return tmp; } if( start > end) return NULL; int mid = start + (end - start ) / 2; ListNode *lhead = mergeSort(head, start, mid); ListNode *rhead = mergeSort(head,mid+1,end); return mergeTwoLists(lhead,rhead); } // The sort interface. ListNode *sortList(ListNode *head) { int len = getListLen(head); return mergeSort(head,0,len-1); }我们注意到在进行链表的归并排序的过程中,我们利用引用来改变head的值,那么调用过mergeSort之后,head将指向未排序的另一半。
题目8: 检测链表是否存在环路
检测链表环路的方法类似于龟兔赛跑,如果跑道是环行的,那么龟兔必然在某一点相遇,利用这一特性,我们以写出如下代码。
bool hasCycle(ListNode *head) { int result = false; ListNode *sp = head; ListNode *fp = head; while(fp!=NULL && fp -> next != NULL) { fp = fp ->next -> next; sp = sp -> next; if(fp == sp) return true; } return false; }
题目9: 找出带环链表中环的起点
这道题有些tricky的地方,需要用到一点点数学知识来分析它。
前面一题我们已经看到,当快指针与慢指针相遇时,快指针走过的距离是慢指针的2倍,分别标记为DF和DS,DF=2DS。
假设链表头至环路起点距离为D1,慢指针在环路中走过的距离为D2,环路总长度为DL则可以得出以下关系式:
2(D1+D2) = D1+D2+nDL --> D1=(n-1)DL + DL-D2;
那么可以看到当将某一指针A放在开头,而将另一指针B放在两指针相遇的位置,两指针以相的速度向前移动,则A走过D1的距离后,B走过多次环路与DL-D2(即初次相遇点到环路起点的距离),他们再次相遇,而该相遇到点即为环路起点。
ListNode *detectCycleHead(ListNode *head) { int result = false; ListNode *sp = head; ListNode *fp = head; while(fp!=NULL && fp -> next != NULL) { fp = fp ->next -> next; sp = sp -> next; // we need to keep the step of the two pointers to make the algorithm work. if(fp == sp) { //The next step; sp = head; while(sp!=fp) { sp = sp -> next; fp = fp -> next; } return sp; } } return NULL; }
题目10: 判断两单链表是否相交
这题是《编程之美》上的,我觉得有两种方法比较巧妙。
解法1: 将其中一个链表首尾相连,然后根据题目8中的算法来验证另一条链表是否存在环路,存在则两链表相交,不存在,则两链表不相交。
解法2:利用单链表的特性,一个结点的出度只能为1,因此两个链表相交,则某一点之后的所有结点应该是一样的,那么最简单的就是遍历两链表找到尾结点,判断尾结点指针是否一致即可。代码如下:
ListNode *getTailNode(struct ListNode *head) { while(head->next!=NULL) { head = head -> next; } return head; } bool crossedLinkedList(struct ListNode *head1, struct ListNode *head2) { return getTailNode(head1) == getTailNode(head2); }
题目11: 若两链表相交,求两链表的交点
解法1:这道题同样可以参考题目9的方法来找到环路的起点即为相交的起点。
解法2:利用hash map, 由其中一个链表构造hash map,然后从另一个链表开始逐个检查每一个结点是否在hash map中,第一个存在的即为交点。引入O(n)的空间复杂度。
解法3:有些tricky,即假设两个链L1和L2表长度分别是 A,B,并假设A>=B,那么我们可以求得差值 D=A-B, 那么我们首先将L1移动D个结点,然后同时将L1,L2向后遍历并比较结点指针是否一致,如果一致则中断循环,该结点即为首个相交点。该解法巧妙地利用了两个链表的长度差,以获得两个链表距离尾部距离相同的起点,该起点也即跑离相交点相同的起点,因为单链表相并,后面的结点都是一样的,依此结点开始逐个判断即可得到相交结点的位置。代码如下:
ListNode *getFirstCrossingNode(ListNode *head1, ListNode *head2) { int A = getListLen(head1); int B = getListLen(head2); ListNode *longhead = (A>=B)?head1:head2; ListNode *shorthead = (A<B)?head1:head2; int D = (A>=B)?A-B:B-A; while(D>0) { longhead = longhead->next; D--; } while(longhead!=NULL && shorthead!=NULL && longhead!=shorthead) { longhead = longhead->next; shorthead = shorthead -> next; } return (longhead==shorthead)?longhead:NULL; }
题目12: Linux 内核链表的实现
我们一般看到的链表形式是这样的:
相关文章推荐
- 那些年我们面试过的单链表算法总结(一)
- 微软面试简单算法之 反序链表
- 转:C/C++面试之算法系列--怎样快速检测出一个巨大的单链表中是否具备死链及其位置
- 程序员编程艺术第一~十章集锦与总结--面试、算法、编程
- 微软面试、经典算法、编程艺术、红黑树4大系列总结
- 微软面试、经典算法、编程艺术、红黑树4大系列总结
- C/C++面试之算法系列--典型的几个链表操作-逆序和重排
- 微软等数据结构+算法面试100题(19)--链表
- 华赛面试总结:单链表逆序
- 华赛面试总结:单链表逆序
- C语言链表在笔试面试中常考问题总结
- 链表的一些常见笔试面试问题总结及代码
- 面试算法题总结(二)
- 面试算法题总结(一)
- 常用于面试的链表操作算法
- 把二元查找树转变成排序的双向链表——精选微软经典的算法面试100题中第一题
- 数据结构和算法面试总结
- 【算法面试题】求两个相交链表的首个相交节点(转)
- 单链表算法题总结
- 微软等数据结构+算法面试100题(3)--怎样把一个链表掉个顺序(也就是反序,注意链表的边界条件并考虑空链表)?