• 链表的经典问题


    链表的经典问题

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

    如果两个单链表相交,那应该呈“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;
        }
    }
  • 相关阅读:
    python学习日记——基本数据类型
    python学习日记——安装及初识
    STF平台探索
    fiddler基本操作梳理
    fly.js抛物线连续不断加入购物车
    判断是否存在某个字段hasOwnProperty
    vue中提示toFixed不是函数
    vue中父组件给子组件传值,子组件给父组件传值
    js判断用户的浏览器设备是移动端还是pc端
    css预处理器--sass学习($变量名)
  • 原文地址:https://www.cnblogs.com/chenny7/p/4113552.html
Copyright © 2020-2023  润新知