您的位置:首页 > 职场人生

那些年我们面试过的单链表算法总结(二)

2014-07-04 09:54 330 查看
上一篇博文描述了一小部分单链表的算法,这篇将继续深入,看看还有哪些面试中常见的链表题目。

题目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 内核链表的实现

我们一般看到的链表形式是这样的:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: