zoukankan      html  css  js  c++  java
  • 链表的经典问题

    链表的经典问题

    如何判断两个单链表是否相交,如果相交,找出交点(两个链表都不存在环)

    如果两个单链表相交,那应该呈“Y”字形,也就是从交点以后的部分是两个链表的公共节点。

    所以,判断是否相交只要看两个链表的最后一个节点是否为同一个即可。

    那么如何找到交点呢?设两个单链表的长度分别为L1、L2,(假设L1>L2),则(L1-L2)的值就是交汇之前两个链表的长度差;

    因此,只有让更长的链表先走L1-L2步,然后两个链表开始一起走,如果某次走到一个相同的节点,该节点即为交点。

    C代码实现:

    typedef struct _ListNode {
        int data;
        struct _ListNode *next;
    } ListNode;
    
    
    static int GetListLength(ListNode *T) 
    {
        int n;
    
        for(n=0; T; n++) {
            T = T->next; 
        }   
    
        return n;
    }
    
    static ListNode* FindFirstCommonNode(ListNode *T1, ListNode *T2)
    {
        int i;
        int n1 = GetListLength(T1);
        int n2 = GetListLength(T2);
    
        // T1 always own longer list
        if (n1 < n2) {
            return FindFirstCommonNode(T2, T1);
        }   
    
        for (i=0; i<n1-n2; i++) {
            T1 = T1->next;
        }   
    
        while (T1 && T1 != T2) {
            T1 = T1->next;
            T2 = T2->next;    
        }   
        return T1; 
    }

    该问题还有一种思路,就是将其中一个链表首尾相连,然后检测另外一个链表是否有环,如果存在环,则两个链表相交。

    判断一个链表是否有环,并找到环的入口点

    如果一个单链表有环,那应该呈“6”字形。

    设置两个指针(fast, slow),初始值都指向头节点,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定 相遇:如果链表是呈"O"字形,则slow刚好遍历完一次的时候,与fast相遇;如果呈“6”字形,则更早相遇。

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

    2s = s + nr,简化为 s= nr

    s = x + y,x为链表起点到环入口点的距离,y是slow在环内走过的距离;

    可以得到 x = y - s = y - nr,从链表头、相遇点分别设一个指针(p1, p2),每次各走一步,当p1走过距离x时到达入口点,而p2走过的距离为y-nr,y是相遇点与入口点的距离,因此y也走到了入口点,也就是说p1、p2在环入口点相遇了。

    C代码实现:

    static ListNode* FindLoopPort(ListNode *Head)  
    {  
        ListNode *slow = Head; 
        ListNode *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;  
    }  

     

    求一个单链表(无环)的中间节点

    设置两个指针(fast, slow),初始值都指向头节点,slow每次前进一步,fast每次前进二步,当fast走到末尾时,slow刚好指向中间节点。

    假如链表长度为N,如何返回链表的倒数第K个结点

    思路:用两个指针,指针P1先走K-1步,然后指针P2才开始走,当指针P1遍历完链表时,P2还剩K个结点没有遍历。

    实现如下:

    ListNode *FindLastKNode(ListNode *Head, int K)
    {
        if (NULL == Head)
            return NULL;
    
        ListNode *T1 = Head;
        ListNode *T2 = Head;
    
        while (T1 && --K)
            T1 = T1->next;
    
        if (K)     // here, K must be 0
            return NULL;
    
        while (T1->next) {
            T1 = T1->next;
            T2 = T2->next;
        }
    
        return T2;
    }

    在O(1)时间删除链表结点

    在链表中删除一个结点,最常规的做法是遍历链表,找到要删除的结点后再删除,这种做法的时间复杂度是O(n);
    换一种思路,根据待删除结点A,可以知道其下一个结点是B=A->next,将结点B值拷贝给A,然后删除B即可。
    这种方法需要考虑一种特殊情况,A如果是尾结点,则B不存在,此时仍需要遍历链表一次。

    C代码实现:

    void DeleteNode(ListNode* Head, ListNode *pDel)
    {
         if (NULL == pDel || NULL == Head)
              return;
    
         ListNode *p = Head;
         if (NULL == pDel->next) {     // pDel is the last node
              while (pDel != p->next)
                   p = p->next;
              p->next = NULL;
              free(pDel);
    }
    else { p = pDel->next; pDel->next = p->next; pDel->data = p->data; free(p); } }

    如何逆序输出一个单链表

    方法一:从头到尾遍历链表,每经过一个结点的时候,把该结点放到一个栈中;当遍历完整个链表后,再从栈顶开始输出结点的值。
    该方法需要维护一个额外的栈,实现起来比较麻烦。我们注意到递归本质上就是一个栈结构,所以,也可以用递归来实现反向输出链表。


    方法二:也就是说,每访问到一个结点的时候,先递归输出它后面的结点,再输出该结点自身。

    C代码实现:

    void ReversePrint(ListNode *Head)
    {
         if (Head) {
    
              if (Head->next) {
                   ReversePrint(Head->next);
              }
    
              printf("%d ", Head->data);
         }
    }

    如何反转一个单链表

     利用辅助指针就地修改节点的next域,代码如下:

    static ListNode *ReverseList(ListNode *Head)
    {
         ListNode *pNode = Head;
         ListNode *pNext = NULL;
         ListNode *pPrev = NULL;
    
         while (pNode) {
              pNext = pNode->next;    
              if (NULL == pNext)     // meet the end
                   Head = pNode;
              pNode->next = pPrev;
              pPrev = pNode;
              pNode = pNext;
         }   
    
         return Head;
    }

    递归 的实现方法:

    static void ReverseList2(ListNode** Head)
    {
        ListNode *p = *Head;
    
        if (!p) return;
    
        ListNode* rest = p->next;
    
        if (!rest) return;
    
        ReverseList2(&rest);
        rest->next = p;
        p->next = NULL;
    }

    链表的排序

    归并排序实现的时间复杂度为 nlgn,

    struct ListNode* Merge(struct ListNode* p1, struct ListNode *p2) {
        if (!p1) return p2;
        if (!p2) return p1;
        if (p1->val > p2->val) {
            p2->next = Merge(p1, p2->next);
            return p2;
        } else {
            p1->next = Merge(p1->next, p2);
            return p1;
        }
        
    }
    
    struct ListNode* sortList(struct ListNode* head) {
        
        if (!head || !head->next) return head;
        struct ListNode *h1, *h2;
        struct ListNode *p1 = head, *p2 = head, *p = head;
        
        while (p2 && p2->next) {
            p = p1;
            p1 = p1->next;
            p2 = p2->next->next;
        }
        
        p->next = NULL;
        h1 = sortList(p1);
        h2 = sortList(head);
        return Merge(h1, h2);
    }

    以上递归的实现会占用lgN的空间(递归压栈),非递归的实现如下:

    复杂链表的复制

    假设有一个复杂链表,它除了有一个next指针外,还有一个other指针,指向链表中的任一结点或者NULL,

    typedef struct _ListNode {
        int data;
        struct _ListNode *next;
        struct _ListNode *other;
    } ListNode;

    如下图,是一个含义5个节点的该类型的复杂链表,实线表示next指针,虚线表示other指针,NULL指针未标出。

    最简单的方法是,先复制所有节点,并用next指针链接起来,然后假设原始链表的某节点N的other指针指向节点S,由于S的位置可能在N的前面,也可能在N的后面,所以要定位N的位置需要从原始链表的头节点开始找,直到确认节点S在链表中的位置为s;然后在复制链表上节点N的other指针也要指向距离链表头的第s个节点。这种方法的时间复杂度是O(n2)。

    上面这种方法的主要缺点在于无法快速定位N节点的other所指向的S节点的位置,

    下面将介绍一种时间复杂度是O(n)的方法,首先把复制的节点串到原节点后面,如下图:

    然后设置复制节点的other指针(例如 A'->other = A->other->next),如下图

    最后,把偶数顺序的节点和奇数节点的指针分开。

    // 逐个节点复制,并串到原节点后面
    static void CloneNodes(ListNode *Head)
    {
        ListNode *p = Head;
    
        while (p) {
    
            ListNode *pCloned = malloc(sizeof(ListNode));
    
            pCloned->data = p->data;
            pCloned->next = p->next;
            pCloned->other = NULL;
    
            p->next = pCloned;
            p = pCloned->next;
        }
    }
    
    // 设置新节点的other指针
    static void ConnectNodes(ListNode *Head)
    {
        ListNode *pCloned;
        ListNode *p = Head;
    
        while(p){
    
            pCloned = p->next;
    
            if (p->other) {
                pCloned->other = p->other->next;
            }
    
            p = pCloned->next;
        }
    }
  • 相关阅读:
    Anagram
    HDU 1205 吃糖果(鸽巢原理)
    Codeforces 1243D 0-1 MST(补图的连通图数量)
    Codeforces 1243C Tile Painting(素数)
    Codeforces 1243B2 Character Swap (Hard Version)
    Codeforces 1243B1 Character Swap (Easy Version)
    Codeforces 1243A Maximum Square
    Codeforces 1272E Nearest Opposite Parity(BFS)
    Codeforces 1272D Remove One Element
    Codeforces 1272C Yet Another Broken Keyboard
  • 原文地址:https://www.cnblogs.com/chenny7/p/4113552.html
Copyright © 2011-2022 走看看