指向任意节点的带环链表

🌈图示指向任意节点的带环链表

如图:
在这里插入图片描述

🌈快慢指针法判断链表是否带环

🌟思路:快指针fast一次走2步,慢指针slow一次走1步,fast先进环在换中运动,随后slow进入环。两指针每同时移动一次,二者的相对距离减少1,此时二者一定会在环中相遇。
🌟同时还可得出结论:相遇时slow不可能走过一整个环。相同时间内fast走过的路程是slow的二倍,fast和slow相遇时,slow一定不可能走过了一整个环(假设slow走过了一次整个环,则fast走过了两次环,在这期间二者一定会相遇,因此假设不成立)。
🌟图示fast和slow相遇过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🌟拓展思考:快指针fast一次走3步,慢指针slow一次走1步,此时一定会相遇吗?(C是圆环长度)
🌟答:不一定,当C-1为奇数时永远不会相遇。slow进环后,每同时移动一次,二者距离缩小2。当fast在slow后面且相隔1个节点时,再移动一次变成了fast在slow前且相隔1节点,进入了新一轮追逐,追逐距离还是是C-1(注意:追逐距离不是C-2),而C-1是奇数时,不管怎么减2,都不可能减为0,只会从1减到-1,又进入新一轮循环,追逐距离还是C-1,奇数,陷入死循环。
在这里插入图片描述

🌟根据原理类推,fast一次走4步,slow一次走一步,每次距离缩小3。当环的周长C为3的倍数时,会相遇,C的长度模3余2时,陷入死循环,循环时每轮追击距离为C-1;当环的周长模3余1时,陷入死循环,循环时每轮追击距离为C-2。
在这里插入图片描述

🌈找到带环链表中环的起始点

🌟由上文得出结论:fast和slow相遇时slow不可能走过一整个环,但是fast在slow进环前可能已经在环里走了好多圈了。此时fast大概率走了n-1圈但还没有走够n圈(当然巧合情况是fast刚好走了n圈),但是在fast追slow的过程中一定会把n圈补齐。
(想象一个L非常大,C非常小的带环链表),如下图:
在这里插入图片描述
🌟公式推导:
假设起点到入口点长度:L
假设环的周长:C
假设入口点到相遇点的数据:X
fast与slow相遇时:
slow走过的总长度:L+X;fast走过的总长度:L+n ∗ * c+X
列出方程:
2(L+X)=L+n ∗ * C+X ⇒ L+X=n ∗ * C ⇒ L=n ∗ * C-X
具象说明:一个指针从起点走,另一个指针从相遇点走,它们会在入口点相遇
在这里插入图片描述

☀️OJ题1:判断链表是否带环

链接: https://leetcode.cn/problems/linked-list-cycle/description/
在这里插入图片描述
在这里插入图片描述

思路:fast一次走两步,slow一次走一步,若有环则两指针一定会相遇。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    struct ListNode*fast=head,*slow=head;
    while(fast&&fast->next){
        fast=fast->next->next;
        slow=slow->next;
        if(slow==fast)
        return slow;
    }
    return NULL;
}

☀️OJ题2:返回入环节点

链接: https://leetcode.cn/problems/linked-list-cycle-ii/description/
在这里插入图片描述
在这里插入图片描述

🌟法一:公式法( L=n ∗ * C-X)。

先让fast和slow相遇,再让一个指针从相遇点走,另一个指针从头节点走,两指针相遇点即为入环节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow=head,*fast=head,*meet;
    while(fast&&fast->next){
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow){
            meet=fast;
            while(meet!=head){
                meet=meet->next;
                head=head->next;
            }
            return head;
        }
    }
   return NULL;
}

🌟法二:切断环,交叉链表求交点。

先让fast和slow相遇,从相遇点出断开环,此时演变为交叉链表求交点问题(先让长链表走差距步,再同时走,相遇处即环的起点)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode*fast=head,*slow=head,*meet,*meetnext;
    struct ListNode*head1,*head2;
    int count1=0,count2=0;
    while(fast&&fast->next){
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow){
           meet=slow;
           meetnext=meet->next;
           meet->next=NULL;
           head1=head;
           head2=meetnext;
           while(head1){
               head1=head1->next;
               count1++;
           }
           while(head2){
               head2=head2->next;
               count2++;
           }
           int sub=count1-count2;
           head1=head;
           head2=meetnext;
           if(sub>0){
               while(sub--){
                   head1=head1->next;
               }
           }
           else if(sub==0){
               ;
           }
           else{
               while(sub++){
                   head2=head2->next;
               }
           }
           while(head1!=head2){
               head1=head1->next;
               head2=head2->next;
           }
           return head1;
        }
    }
    return NULL;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 建立带有头节点链表,可以使用以下步骤: 1. 定义链表节点的结构体,包含数据域和指向下一个节点的指针域。 2. 定义头节点,头节点不存储任何数据,只是作为链表的起点,其指针域指向第一个节点。 3. 动态申请节点空间,将数据存储到节点中,将节点插入到链表中。 4. 遍历链表,访问节点数据。 下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct node { int data; struct node *next; } Node; int main() { // 定义头节点 Node *head = (Node*)malloc(sizeof(Node)); head->next = NULL; // 插入节点 for (int i = 0; i < 5; i++) { Node *p = (Node*)malloc(sizeof(Node)); p->data = i; p->next = head->next; head->next = p; } // 遍历链表 Node *p = head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); // 释放节点空间 p = head; while (p != NULL) { Node *q = p->next; free(p); p = q; } return 0; } ``` 在该示例代码中,我们定义了一个 `Node` 结构体表示链表节点,其中 `data` 表示数据域,`next` 表示指针域。然后定义了头节点 `head`,并将其指针域设置为 `NULL`。接下来,使用 `for` 循环向链表中插入 5 个节点,每个节点的数据域为 `i`,指向下一个节点的指针域为当前头节点的指针域。最后遍历链表,输出每个节点的数据域。最后释放节点空间。 ### 回答2: 建立带有头节点链表是一种常见的链表数据结构的实现方式。头节点是位于链表首部的一个特殊节点,它不存储任何数据,只用于表示链表的起始位置。下面是一个简单的方法来建立带有头节点链表: 1. 首先,我们需要定义链表节点的结构,通常包括两个部分:数据域和指针域。数据域用于存储节点的数据,指针域用于指向下一个节点。 2. 创建头节点,使用一个特定值来初始化它的数据域,一般为空或者默认值。然后将指针域指向空。 3. 创建其他节点,按照需要依次添加到链表中。对于每个节点,先创建一个新的节点对象,然后将它的数据域填充为要存储的数据值。接下来,将指针域指向链表中的下一个节点。 4. 将最后一个节点的指针域指向空,表示链表结束。 通过上述步骤,我们就成功地建立了一个带有头节点链表。可以通过遍历链表的方式,依次访问链表中的每个节点,并对节点中的数据进行操作。 带有头节点链表相对于不带头节点链表具有以下几个优点: 1. 可以处理空链表的情况,头节点作为链表的起点可以防止链表为空时的异常情况。 2. 方便插入和删除操作,头节点的存在使得在链表任意位置插入或删除节点时,不需要对链表是否为空进行额外判断。 3. 简化链表的遍历操作,头节点作为链表起始点,可以直接从头节点开始遍历整个链表。 因此,在实际应用中,使用带有头节点链表更为普遍。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值