zoukankan      html  css  js  c++  java
  • 链表面试

    某本书上面说了,链表这个东西,实际用的并不多,但是可以提供很好的考察面试者编程技巧和思维能力的素材。这里总结一下,见过的面试题和对应的候选解法。

    题一、 给定单链表,检测是否有环。
        使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。

    http://ostermiller.org/find_loop_singly_linked_list.html

    这篇文章讲了很多好的坏得相关算法。

    题二、 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。
       如果head1==head2,那么显然相交,直接返回head1。
       否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2。假设len1>=len2,那么指针p1由head1开始向后 移动len1-len2步。指针p2=head2,下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点,否则说明两个链表没有 交点。

    题三、 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。
       运用题一,我们可以检查链表中是否有环。
       如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始,于是运用题二的方法,我们找到它们的第一个交点即为所求。

        也可以不断开环。设重合点为p3,从p3开始遍历这个环,同时从表头开始走,检查每步是否在那个环中。这个方法大概有nlogn。

       使用快慢指针,第一次相遇,表明存在循环。继续快慢指针,第二次相遇,得到的iteration步长为环的长度。分别从相遇点和第一个节点出发,都是步长为1的指针,当相遇时,得到的iteration步长为环首的位置。

    题四、只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。
       办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。

    题五、只给定单链表中某个结点p(非空结点),在p前面插入一个结点。
       办法与前者类似,首先分配一个结点q,将q插入在p后,接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。

    题六、给定单链表头结点,删除链表中倒数第k个结点。
       使用两个节点p1,p2,p1初始化指向头结点,p2一直指向p1后第k个节点,两个结点平行向后移动直到p2到达链表尾部(NULL),然后根据p1删除对应结点。
    题七、链表排序

       链表排序最好使用归并排序算法。堆排序、快速排序这些在数组排序时性能非常好的算法,在链表只能“顺序访问”的魔咒下无法施展能力;但是归并排序却如鱼得水,非但保持了它O(nlogn)的时间复杂度,而且它在数组排序中广受诟病的空间复杂度在链表排序中也从O(n)降到了O(1)。真是好得不得了啊,哈哈。以上程序是递推法的程序,另外值得一说的是看看那个时间复杂度,是不是有点眼熟?对!这就是分治法的时间复杂度,归并排序又是divide and conquer。 

    double cmp(ListNode *p ,ListNode *q)

    {return (p->keyVal - q->keyVal);}

    ListNode* mergeSortList(ListNode *head)

    {    
        ListNode *p, *q, *tail, *e;   
        int nstep = 1;    
        int nmerges = 0;    
        int i;    
        int psize, qsize;
       
        if (head == NULL || head->next == NULL)        
        {return head;}    
        while (1)        
        {   p = head;    
        tail = NULL;
        nmerges = 0;   
        while (p)        
        {   nmerges++;  q = p;  psize = 0;    
        for (i = 0; i < nstep; i++){        
            psize++;        
            q = q->next;        
            if (q == NULL)break;        
        }    
        qsize = nstep;    
        while (psize >0 || (qsize >0 && q))        
        {        
            if (psize == 0 ){e = q; q = q->next; qsize--;}
           
            elseif (q == NULL || qsize == 0){e = p; p = p->next; psize--;}
           
            elseif (cmp(p,q) <= 0){e = p; p = p->next; psize--;}
           
            else{e = q; q = q->next; qsize--;}
           
            if (tail != NULL){tail->next = e;}
           
            else{head = e;}
           
            tail = e;
           
        }    
        p = q;

        }    
        tail->next = NULL;    
        if (nmerges <= 1){return head;}   
        else{nstep <<= 1;}    
        }   
    }

    题八、倒转单链表

    给出非递归和递归解法:

    #include <iostream>

    using namespace std;

    struct Node
    {
       
     int data;
       
     Node* next;
    }*head;

    // 非递归写法
    Node* InverseLinkedList(Node* head)
    {
       
     if(head == NULL)
           
     return NULL;
       
     Node* newHead = NULL;
       
     while(head != NULL)
       
     {
           
     Node* nextNode = head->next;
            head
    ->next = newHead;
            newHead
     = head;
            head
     = nextNode;
       
     }
       
     return newHead;
    }

    // 递归写法
    Node* InverseLinkedListRecur(Node* head)
    {
       
     if(head == NULL)
           
     return NULL;
       
     if(head->next == NULL)
           
     return head;
       
     Node* newHead = InverseLinkedListRecur(head->next);
       
     Node *tmp = newHead;
       
     while(tmp->next != NULL)
       
     {
            tmp
     = tmp->next;
       
     }
        tmp
    ->next = head;
        head
    ->next = NULL;
       
     return newHead;
    }

    int main()
    {
       
     // 构建链表 9->8->7->6->5->4->3->2->1->0->NULL
       
     for(int i = 0; i < 10; i++)
       
     {
           
     Node* node = new Node();
            node
    ->data = i;
            node
    ->next = head;
            head
     = node;
       
     }
        head
     = InverseLinkedList(head);
       
     Node *tmp = head;
       
     while(head != NULL)
       
     {
            cout
     << head->data << " ";
            head
     = head->next;
       
     }
        cout
     << endl;
        head
     = InverseLinkedListRecur(tmp);
        tmp
     = head;
       
     while(head != NULL)
       
     {
            cout
     << head->data << " ";
            head
     = head->next;
       
     }
        cout
     << endl;
       
     while(tmp != NULL)
       
     {
           
     Node* next = tmp->next;
           
     delete tmp;
            tmp
     = next;
       
     }
    }
    题九、两个有序链表的合并

       有两个有序链表,各自内部是有序的,但是两个链表之间是无序的

    typedef struct node{
        
    int data;
        
    struct node * next;
    }* List;

    List mergeSortedLinkList(List list1, List list2)
    {
        
    List pList1,pList2,mergedList,pCurNode;

        
    if (list1 == NULL)
        
    {
            
    return list2;
        
    }
        
    if (list2 == NULL)
        
    {
            
    return list1;
        
    }

        pList1 
    = list1;
        pList2 
    = list2;
        mergedList 
    = NULL;
        
    if (pList1==pList2)
        
    {       
            mergedList 
    = pList1;

            pList1 
    = pList1->next;
            pList2 
    = pList2->next;
        
    }
        
    else 
        
    {

            
    if (pList1->data <= pList2->data)
            
    {
                mergedList 
    = pList1;
                pList1 
    = pList1->next;
            
    }
            
    else
            
    {
                mergedList 
    = pList2;
                pList2 
    = pList2->next;
            
    }
        
    }
        pCurNode 
    = mergedList;
        
    while(pList1 && pList2)
        
    {
            
    if (pList1==pList2)
            
    {
                pCurNode
    ->next = pList1;
                pCurNode 
    = pList1;
                pList1 
    = pList1->next;
                pList2 
    = pList2->next;
            
    }
            
    else
            
    {
                
    if (pList1->data <= pList2->data)
                
    {
                    pCurNode
    ->next = pList1;
                    pCurNode 
    = pList1;
                    pList1 
    = pList1->next;
                
    }
                
    else
                
    {
                    pCurNode
    ->next = pList2;
                    pCurNode 
    = pList2;
                    pList2 
    = pList2->next;
                
    }
            
    }
        
    }

        pCurNode
    ->next =pList1?pList1:pList2;

        
    return mergedList;
    }
    题十、找出链表的中间元素
    单链表的一个比较大的特点用一句广告语来说就是“不走回头路”,不能实现随机存取(random access)。如果我们想要找一个数组a的中间元素,直接a[len/2]就可以了,但是链表不行,因为只有a[len/2 - 1] 知道a[len/2]在哪儿,其他人不知道。因此,如果按照数组的做法依样画葫芦,要找到链表的中点,我们需要做两步(1)知道链表有多长(2)从头结点开始顺序遍历到链表长度的一半的位置。这就需要1.5n(n为链表的长度)的时间复杂度了。有没有更好的办法呢?有的。想法很简单:两个人赛跑,如果A的速度是B的两倍的话,当A到终点的时候,B应该刚到中点。这只需要遍历一遍链表就行了,还不用计算链表的长度。
    转自:http://blog.sina.com.cn/s/blog_54b2ce380100uqwr.html

    ===========================================================================================================================================================///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。

    问题:

    1、如何判断一个链表是不是这类链表?
    2、如果链表为存在环,如何找到环的入口点?

    解答:

    一、判断链表是否存在环,办法为:

    设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

    bool IsExitsLoop(slist *head)
    {
        slist 
    *slow = head*fast = head;

        while ( fast && fast->next ) 
        {
            slow 
    = slow->next;
            fast 
    = fast->next->next;
            
    if ( slow == fast ) break;
        }

        return !(fast == NULL || fast->next == NULL);
    }

    二、找到环的入口点

    当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

    2s = s + nr
    s= nr

    设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
    a + x = nr
    a + x = (n – 1)r +r = (n-1)r + L - a
    a = (n-1)r + (L – a – x)

    (L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:

    slist* FindLoopPort(slist *head)
    {
        slist 
    *slow = head, *fast = 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 
    = head;
        while (slow != fast)
        {
             slow 
    = slow->next;
             fast 
    = fast->next;
        }

        return slow;
    }


    扩展问题:

    判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

    比较好的方法有两个:

    一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

    二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

    这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

    转自 http://www.cppblog.com/humanchao/archive/2012/11/12/47357.html
  • 相关阅读:
    P1121 环状最大两段子段和
    无题
    cdoj 1485 柱爷搞子串 sam treap
    自然数幂和
    Gym 100341C AVL Trees NTT
    线性筛分解质因子
    codeforces 366 Ant Man dp
    UVALive 6914 Maze Mayhem 轮廓线dp
    hdu 5790 Prefix 字典树 主席树
    莫比乌斯反演个人小结
  • 原文地址:https://www.cnblogs.com/wuyuankun/p/3687268.html
Copyright © 2011-2022 走看看