环形链表 I 环形链表 II 随机链表的复制

环形链表 I

判断一个链表是否带环 

 思路一

快慢指针法

创建一块一慢两个指针,去遍历链表,如果快指针走到了最后一个节点或走到了NULL,则此链表不带环。

如果带环,在进入环后,快慢指针会展开追逐,当快指针追到慢指针时,则此链表带环

代码:

 这里我们让快指针一次走两步,慢指针一次走一步

bool hasCycle(struct ListNode *head) {
    struct ListNode* fast = head,*slow = head;
    

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

    return false;

}

思路一的问题

问题一 :为什么一定会相遇?会不会出现错过的情况?永远追不上

想解释这个问题比较简单,我们假设有一带环链表List,和快指针 fast 慢指针 slow

此图为假设 slow 指针刚进入环时,与fast指针距离为N

因为fast指针的速度是slow指针速度的两倍,所以slow每走一步,fast都会追上slow一步

它们之间的距离也会不断的缩减:N  N -1 N -1 .....3 2 1 0,当N为0时,fast就追上slow了,并不会出现错过的情况

问题二 :slow一次走一步,fast一次走 3步 4步 5步 n步 可以吗 ?

这里我们只推导 slow一次走一步,fast 一次走3步的过程,其余推导过程类似

此图为假设 slow 指针刚进入环时,与fast指针距离为N

情况一:当N是偶数时 —— 直接追上

fast 追击 slow 情况:N  N - 2  N - 3 ......4  2  0 刚好可以追上

情况二 :当 N 是奇数时 —— 展开新一轮的追击

结论:当 N 是奇数 且 C 为偶数时,永远也追不上

验证结论

经过推理我们得出:当 N 是奇数 且 C 为偶数时,fast 永远也追不上 slow,接下来我们尝试建立N与C的联系,来验证它是否正确

 slow 刚进入圆环时,研究slow与fast走过的路程有:

slow 走的距离 :L

fast走的距离 :L + XC + C - N(X为圈数)

fast走的距离为 slow 的3倍 :3L = L + XC + C - N

化简该等式有:2L = (X + 1)C -N

将我们刚刚得出的结论(当 N 是奇数 且 C 为偶数时,永远也追不上)带入该等式,我们发现:

所以结论(当 N 是奇数 且 C 为偶数时,永远也追不上),不成立

结论

当 slow 走一步 fast 走三步时 ,fast一定追的上 slow不存在永远追不上的情况!

环形链表 || 

找环的入口点 

  

 思路一

双指针法

先找到 fast指针 与 slow指针 相遇的节点meet ,如找不到返回NULL

找到meet节点后:让一个指针从头开始走,一个指针从meet开始走(两个指针走的速度一样),当两个指针相遇时,这个节点就是入口点

代码:

 struct ListNode * hasCycle(struct ListNode *head) {
    struct ListNode* fast = head,*slow = head;

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

    return NULL;

}
struct ListNode *detectCycle(struct ListNode *head) {
    //找到meet节点
    struct ListNode* meet = hasCycle(head);
    if(meet == NULL)
    return NULL;

    //让慢指针重新开始走,快指针从meet位置开始走,当它们再次相遇时,就是环的入口点
    struct ListNode* cur_in = meet;//一个指针从meet开始走
    struct ListNode* cur_out = head;//一个指着从链表头部开始走

    while(true)
    {
        if(cur_in == cur_out)
        {
            return cur_in;
        }
        cur_in = cur_in->next;
        cur_out = cur_out->next;
    }
}

思路一的问题

为什么两个指针会在环的入口点相遇 

研究快慢指针相遇走的路程时有:

 slow走过的路程:L + N

fast走过的路程:L + XC + N (X大于等于1,且X为整数 )

fast走过的路程是slow的2倍:2(L + N) = L + XC + N

化简:2(L + N) = L + XC + N         =        L = XC - C      =      L =  (X-1) + C - N

这个公式就是为什么两个相同速度的指针会在环的入口点相遇了:L =  (X-1) + C - N

 

 

 

由于L  = C - N所以它们才会在环的入口点相遇

思路二 

在相遇点把环给解开,这样问题就变成了找两个链表相遇的问题

代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 struct ListNode * hasCycle(struct ListNode *head) {
    struct ListNode* fast = head,*slow = head;
    

    while(fast && fast->next)
    {
        slow = slow -> next;
        fast = fast->next->next;
        if(fast == slow)
        {
            return fast;
        }
    }
    return NULL;
}
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {

    struct ListNode * curA;
    struct ListNode * curB;
    struct ListNode * cur;

    int countA = 0;
    int countB = 0;

    //统计A链表总节点
    cur = headA;
    while(cur)
    {
        cur = cur->next;
        countA++;
    }

    //统计B链表总结点
    cur = headB;
    while(cur)
    {
        cur = cur->next;
        countB++;
    }

    //让链表长的指针走到与链表短的指针同位置
    int gap = abs(countA - countB);
    struct ListNode * longList = headA,*shortList = headB;
    if(countB > countA)//修正长短链表
    {
        longList = headB;
        shortList = headA;
    }

    while(gap--)
    {
        longList = longList -> next;
    }

    //开始查找有没有相交的节点
    while(longList) //随意哪一个走到NULL就结束
    {
        if(longList == shortList) //节点地址相同说明这是相交的第一个节点
        {
            return longList;
        }
        longList = longList -> next;
        shortList = shortList -> next;
    }

    //一直没有相交的节点就返回NULL
    return NULL;

}

struct ListNode *detectCycle(struct ListNode *head) {
    //找到相遇点
    struct ListNode* meet = hasCycle(head);
    if(meet == NULL)
    {
        return NULL;
    }

    //解开环
    struct ListNode* newhead = meet->next;
    meet->next = NULL;

    //找两条链表相交点
    meet = getIntersectionNode(newhead,head);
    return meet;

}

随机链表的复制

 

复制一个链表,难题在于链表中的random指针怎么去复制

由于random指针指向的是自身链表中的节点

你复制的链表节点的各自random指针也要和源链表一样,指向各自的节点 

思路一

把复制链表依次插入到源链表各个节点中

这样,复制节点的random指针就相当于 == 被复制节点random指针指向的next节点

代码: 

struct Node* copyRandomList(struct Node* head) {

    if(head == NULL)
    {
        return NULL;
    }

	struct Node* cur = head;

    //将拷贝链表的节点插缝源链表中
    while(cur)
    {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy -> val = cur -> val;

        copy -> next = cur -> next;//将copy节点插入cur节点与cur下一个节点之间
        cur -> next = copy;

        cur = copy -> next;//cur走向源链表的下一个节点
    }

    //使用我们准备好的链表,它可以帮助我们建立random的联系
    cur = head;
    struct Node* copy;
    while(cur)//当走到NULL时停止,其实这个是拷贝链表最后一个节点的nxte指向NULL
    {
        copy  = cur -> next;//创建copy指向每个源链表后的拷贝节点
        if(cur -> random == NULL)
        {
            copy -> random = NULL;
        }
        else
        {
            copy ->random = cur ->random ->next;//
        }

        cur = copy -> next;
    }

    //解开链表
    cur = head;//cur遍历源链表
    struct Node* source_next;//源链表的下一个节点 

    struct Node* copy_sentry = (struct Node*)malloc(sizeof(struct Node));//哨兵节点
    struct Node* copy_tail;//拷贝链表尾节点
    copy_tail = copy_sentry;
    
    

    while(cur)
    {
        copy = cur -> next;
        source_next = copy->next;

        //拷贝链表的尾插  
        copy_tail->next = copy;
        copy_tail = copy_tail -> next;
        

        //回复源链表
        cur->next = source_next;

        //cur和source_next在源链表上各走一步
        cur = source_next;
       
    }

    return copy_sentry->next;

}

  • 37
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值