C_数据结构(单链表算法题) —— 相交链表、环形链表I、环形链表II、随机链表的复制

目录

一、相交链表

二、环形链表I

三、环形链表II

四、随机链表的复制


一、相交链表

相交链表 - 力扣(LeetCode)

代码详细思路:(目的是找到两个链表的交点)

1. 统计链表长度:

  • 首先,代码分别遍历两个链表 headAheadB,并统计它们的长度 sizeAsizeB

  • 遍历过程中,l1l2 指针分别指向当前节点,并不断向后移动,直到到达链表的末尾。

2. 调整指针位置:

  • 代码计算两个链表长度的差值 gap,并让较长的链表指针先走 gap 步。

  • 这步操作的目的是为了让两个指针在同一个起点开始比较,以便找到交点。

  • 如果 sizeA 小于 sizeB,则将 headB 作为长链表,headA 作为短链表。

3. 同时遍历两个链表:

  • 现在,longListshortList 指针分别指向调整后的长链表和短链表的起点。

  • 代码使用 while 循环同时遍历两个链表,直到其中一个链表遍历结束。

  • 在遍历过程中,比较 longListshortList 是否指向同一个节点:

    • 如果指向同一个节点,则说明找到了交点,返回 longList

    • 如果没有找到交点,则继续向后遍历,将 longListshortList 指针分别移动到下一个节点。

4. 处理无交点情况:

  • 如果 while 循环结束,仍然没有找到交点,则说明两个链表没有交点,返回 NULL

运行代码:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) 
    {
        ListNode* l1 = headA;
        ListNode* l2 = headB;
        int sizeA = 0,sizeB = 0;
        while(l1)
        {
            sizeA++;
            l1 = l1->next;
        }
        while(l2)
        {
            sizeB++;
            l2 = l2->next;
        }
        //求两者相差数的绝对值
        int gap = abs(sizeA-sizeB);

        //让长链表先走gap步
        ListNode* longList = headA;
        ListNode* shortList = headB;
        if(sizeA <sizeB)
        {
            longList = headB;
            shortList = headA;
        }
        while(gap--)
        {
            longList = longList->next;
        }
        //此时longList和shortList指针在同一起跑线上
        //链表相交  与   链表不相交  两种情况
        while(longList && shortList)
        {
            if(longList == shortList)
            {
                //链表相交
                return longList;
            }
            else
            {
                //继续往后走
                longList = longList->next;
                shortList = shortList->next;
            }
        }
        //链表不相交
        return NULL;
    }
};

总结:

这段代码通过计算链表长度、调整指针位置和同步遍历两个链表的方式,实现了查找两个链表交点的功能。

代码的优点:

  • 思路清晰,易于理解。

  • 效率较高,时间复杂度为 O(m + n),其中 m 和 n 分别为两个链表的长度。

代码的不足:

  • 没有考虑链表为空的情况,需要在代码中添加判断。

改进建议:

  • 在代码开头添加判断,如果 headAheadB 为空,则直接返回 NULL

  • 可以使用更简洁的代码来实现指针的调整,例如:

    cpp 复制代码
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) 
        {
            return nullptr;
        }
    
        ListNode *l1 = headA;
        ListNode *l2 = headB;
    
        while (l1 != l2) 
        {
            l1 = (l1 == nullptr) ? headB : l1->next;
            l2 = (l2 == nullptr) ? headA : l2->next;
        }
    
        return l1;
    }

二、环形链表I

环形链表I - 力扣(LeetCode)

代码详细思路:(目的是判断一个单链表是否包含环路)

1. 初始化快慢指针:

  • 代码首先定义两个指针 slowfast,分别指向链表的头部 head

  • slow 指针每次移动一步,而 fast 指针每次移动两步。

2. 循环遍历链表:

  • 代码使用 while 循环,条件是 fast 指针不为空且 fast 指针的下一个节点也不为空。

  • 在循环中:

    • slow 指针每次移动一步,指向下一个节点。

    • fast 指针每次移动两步,指向下一个节点的下一个节点。

    • 每次移动后,代码判断 slowfast 指针是否指向同一个节点。

3. 判断是否包含环路:

  • 如果 slowfast 指针指向同一个节点,则说明链表包含环路,返回 true

4. 处理无环路情况:

  • 如果 while 循环结束,仍然没有找到 slowfast 指针指向同一个节点,则说明链表不包含环路,返回 false

原理:

  • 快慢指针算法的原理是,如果链表包含环路,那么快指针最终会追上慢指针。

  • 这是因为快指针在环路中不断绕圈,而慢指针在环路中缓慢移动。

  • 当快指针绕圈的次数是慢指针移动次数的倍数时,两个指针就会相遇。

代码的优点:

  • 思路简洁,易于理解。

  • 效率较高,时间复杂度为 O(n),其中 n 为链表的长度。

  • 空间复杂度为 O(1),因为只使用常数大小的额外空间。

总结:

这段代码使用快慢指针算法,通过判断快慢指针是否相遇来判断链表是否包含环路。该算法简单高效,是判断链表是否包含环路的一种常用方法。

运行代码:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) 
{
    //定义快慢指针
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)
        {
            return true;
        }
    }
    //跳出循环的话,两个指针始终没有相遇
    return false;
}

三、环形链表II

环形链表II - 力扣(LeetCode)

代码详细思路:(检测单链表中是否存在环路,如果存在环路,则返回环路的入口节点,否则返回 NULL)

1. 初始化快慢指针:

  • 代码首先定义两个指针 slowfast,分别指向链表的头部 head

  • slow 指针每次移动一步,而 fast 指针每次移动两步。

2. 循环遍历链表:

  • 代码使用 while 循环,条件是 fast 指针不为空。

  • 在循环中:

    • slow 指针每次移动一步,指向下一个节点。

    • 判断 fast 指针的下一个节点是否为空,如果为空,则说明链表没有环路,返回 NULL

    • fast 指针每次移动两步,指向下一个节点的下一个节点。

    • 每次移动后,代码判断 fastslow 指针是否指向同一个节点。

3. 处理存在环路的情况:

  • 如果 fastslow 指针指向同一个节点,则说明链表包含环路。

  • 为了找到环路的入口节点,代码定义一个新的指针 ptr,指向链表的头部 head

  • 然后,代码同时移动 ptrslow 指针,直到两个指针指向同一个节点。

  • 当两个指针指向同一个节点时,该节点就是环路的入口节点,返回 ptr

4. 处理不存在环路的情况:

  • 如果 while 循环结束,仍然没有找到 fastslow 指针指向同一个节点,则说明链表不包含环路,返回 NULL

原理:

  • 快慢指针算法的原理是,如果链表包含环路,那么快指针最终会追上慢指针。

  • 这是因为快指针在环路中不断绕圈,而慢指针在环路中缓慢移动。

  • 当快指针绕圈的次数是慢指针移动次数的倍数时,两个指针就会相遇。

  • 当两个指针相遇后,代码通过同时移动 ptrslow 指针,找到环路的入口节点。

代码的优点:

  • 思路简洁,易于理解。

  • 效率较高,时间复杂度为 O(n),其中 n 为链表的长度。

  • 空间复杂度为 O(1),因为只使用常数大小的额外空间。

总结:

这段代码使用快慢指针算法,通过判断快慢指针是否相遇来判断链表是否包含环路,并通过同时移动两个指针来找到环路的入口节点。该算法简单高效,是判断链表是否包含环路并找到环路入口节点的一种常用方法。

运行代码:

cpp 复制代码
struct ListNode* detectCycle(struct ListNode* head) 
{
    struct ListNode *slow = head, *fast = head;
    while (fast != NULL) 
    {
        slow = slow->next;
        if (fast->next == NULL) 
        {
            return NULL;
        }
        fast = fast->next->next;
        if (fast == slow) 
        {
            struct ListNode* ptr = head;
            while (ptr != slow) 
            {
                ptr = ptr->next;
                slow = slow->next;
            }
            return ptr;
        }
    }
    return NULL;
}

四、随机链表的复制

随机链表的复制 - 力扣(LeetCode)

代码详细思路:

这段代码的功能是复制一个包含随机指针的链表。它使用了一种巧妙的策略,将复制过程分为三个阶段,并使用快慢指针来进行操作。

1. 创建节点函数 buyNode:

  • 该函数用于创建一个新的节点,并初始化其值 (val) 和指针 (nextrandom)。

  • malloc 函数用于分配内存空间,sizeof(Node) 用于计算节点的大小。

  • newnode->val = x 用于将输入值 x 赋值给新节点的值。

  • newnode->next = newnode->random = NULL 用于初始化新节点的指针为 NULL。

2. 插入节点函数 AddNode:

  • 该函数用于在原链表中每个节点之后插入一个新节点,新节点的值与原节点相同。

  • 它使用一个循环遍历原链表,并对每个节点执行以下操作:

    • 创建一个新的节点 newnode,其值与当前节点 pcur 相同。

    • 将当前节点 pcur 的下一个节点指向新节点 newnode (pcur->next = newnode)。

    • 将新节点 newnode 的下一个节点指向原链表中下一个节点 (newnode->next = next)。

    • 更新当前节点 pcur 为原链表中的下一个节点 (pcur = next)。

3. 复制随机链表函数 copyRandomList:

  • 该函数用于复制一个包含随机指针的链表。它使用三个阶段来完成复制:

    • 第一阶段:复制节点:

      • 首先,它调用 AddNode(head) 函数,在原链表中每个节点之后插入一个新节点,从而复制了原链表中的所有节点。

      • 此时,原链表中每个节点和它的复制节点都相邻。

    • 第二阶段:复制随机指针:

      • 它使用一个循环遍历原链表,并对每个节点执行以下操作:

        • 获取当前节点的复制节点 (copy = pcur->next)。

        • 如果当前节点的随机指针不为空 (pcur->random != NULL),则将复制节点的随机指针指向原节点随机指针的复制节点 (copy->random = pcur->random->next)。

        • 更新当前节点为复制节点的下一个节点 (pcur = copy->next)。

    • 第三阶段:断开链表:

      • 它使用一个循环遍历原链表,并对每个节点执行以下操作:

        • 获取当前节点的复制节点 (pcur->next)。

        • 将复制节点的下一个节点指向当前节点的下一个节点的复制节点 (newTail->next = pcur->next)。

        • 更新复制链表的尾节点 (newTail = newTail->next)。

      • 最后,返回复制链表的头部 (newHead)。

代码的优点:

  • 代码简洁易懂,易于理解。

  • 它巧妙地利用了原链表和复制链表节点的相邻关系,通过遍历一次原链表就能完成复制过程,避免了多次遍历,提高了效率。

总结:

这段代码使用了一种高效的策略,通过三个阶段的处理,成功复制了包含随机指针的链表,并保留了原链表的结构和数据。

运行代码:

cpp 复制代码
/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
typedef struct Node Node;
Node* buyNode(int x)
{
    Node* newnode = (Node*)malloc(sizeof(Node));
    newnode->val = x;
    newnode->next = newnode->random = NULL;

    return newnode;
}
void AddNode(Node* phead)
{
    Node* pcur = phead;
    while(pcur)
    {
        Node* next = pcur->next;
        //创建新结点,尾插到pcur
        Node* newnode = buyNode(pcur->val);
        pcur->next = newnode;
        newnode->next = next;

        pcur = next;
    }
}
struct Node* copyRandomList(struct Node* head) 
{
    if(head == NULL)
    {
        return NULL;
    }
	//第一:原链表上复制结点
    AddNode(head);
    //第二:置random
    Node* pcur = head;
    while(pcur)
    {
        Node* copy = pcur->next;
        if(pcur->random != NULL)
        {
            copy->random = pcur->random->next;
        }
        pcur = copy->next;
    }
    //第三:断开链表
    pcur = head;
    Node* newHead,*newTail;
    newHead = newTail = pcur->next;
    while(pcur->next->next)
    {
        pcur = pcur->next->next;
        newTail->next = pcur->next;
        newTail = newTail->next;
    }
    return newHead;
}
相关推荐
fathing7 分钟前
c# 调用c++ 的dll 出现找不到函数入口点
开发语言·c++·c#
前端青山29 分钟前
webpack指南
开发语言·前端·javascript·webpack·前端框架
hummhumm33 分钟前
第 10 章 - Go语言字符串操作
java·后端·python·sql·算法·golang·database
Jeffrey_oWang37 分钟前
软间隔支持向量机
算法·机器学习·支持向量机
nukix44 分钟前
Mac Java 使用 tesseract 进行 ORC 识别
java·开发语言·macos·orc
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】内存可见性问题 & volatile
java·开发语言·java-ee
Lizhihao_1 小时前
JAVA-队列
java·开发语言
算法歌者1 小时前
[算法]入门1.矩阵转置
算法
用户8134411823611 小时前
分布式训练
算法
林开落L2 小时前
前缀和算法习题篇(上)
c++·算法·leetcode