常见的链表OJ题(中等篇)

本篇博客整理了一些常见的链表oj题(以下所有oj题均出自leetcode平台),用于交流和学习。下面将通过C实现这些oj题。

文章目录

  • 环形链表
  • 复制带随机指针的链表
  • 删除有序链表的重复节点

环形链表

  给定一个链表的首节点 head ,判断链表中是否有环。链表存在环返回true,否则返回false。示例如下:

示例1:

常见的链表OJ题(中等篇)_第1张图片
该链表存在环,返回true

示例2:

常见的链表OJ题(中等篇)_第2张图片
该链表不存在环,返回false


基本思路:使用快慢指针,慢指针一次走一步,快指针一次走两步。若链表无环:链表节点数为偶数个,fast最终会走到NULL;链表节点数为奇数个,fast->next最终会为NULL。若链表有环:slow与fast最终会相遇。

为什么链表有环时,slow与fast一定会相遇呢?下面我们对这一结论进行证明。

常见的链表OJ题(中等篇)_第3张图片

代码实现

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow, *fast;
    slow = fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
            return true;
    }
    return false;
}

拓展问题1:如果fast一次走3步、4步、n步,slow与fast还会相遇吗?

fast一次走3步时:

常见的链表OJ题(中等篇)_第4张图片

设slow进环时,fast与slow的距离为N。fast一次走3步,slow一次走1步,则每走一次fast与slow的距离缩减2。

当N为偶数时,最终距离N会缩减为0。

当N为奇数时,最终距离N会缩减为-1,即fast与slow错过了没能相遇。此时我们设环的长度为C,此时fast与slow的距离为C-1,若环的长度为奇数,则C-1为偶数,每走一次fast与slow的距离缩减2,最终C-1会缩减为0,fast与slow相遇;若环的长度为偶数,则C-1为奇数,最终距离C-1会缩减为-1,即fast与slow的距离又变为C-1,说明fast与slow永远不会相遇。

综上,如果fast一次走3步,当slow进环时fast与slow的距离为奇数且环的长度为偶数时,fast与slow永远不会相遇。所以fast一次走3步,slow与fast不一定会相遇。

同理可证,当fast走4步、n步时,slow与fast也不一定会相遇。


拓展问题2:找到并返回环的入口处的节点。如果链表无环返回NULL。

通过先前的证明,我们已经得出了结论:如果链表带环,slow一次走一步,fast一次走两步,slow与fast最终一定会相遇。

常见的链表OJ题(中等篇)_第5张图片

slow与fast相遇后,将相遇节点的下一个节点作为另一个链表的首节点,则相遇节点为新链表与原链表共同的尾节点。此时新链表与原链表相交的起始节点即为原链表环的入口处的节点。具体的代码实现这里就不做展示了。


复制带随机指针的链表

  给定链表的首节点head,链表的每个节点包含一个额外增加的随机指针random,该指针可以指向链表中的任何节点或空节点。例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。示例如下:

常见的链表OJ题(中等篇)_第6张图片


基本思路第一步,拷贝原链表每个节点的val,并将拷贝的节点依次插入到原链表每个节点的后面。第二步,拷贝原链表每个节点的random状态,如果原链表当前节点的random为NULL,则拷贝节点的random也为NULL;如果原链表当前节点的random指向某个节点,则拷贝节点的random应指向原链表当前节点的random指向的节点的next(即copy->random = cur->random->next)。第三步,独立出复制的链表,并恢复原链表的链接状态。

按照该思路,大致作图如下:

常见的链表OJ题(中等篇)_第7张图片

这里只作出了第一步的草图,根据该图构思第二、三步就比较轻松了,因此这里不再继续作第二、三步的草图。

代码实现

struct Node* copyRandomList(struct Node* head) {
    struct Node* cur = head;
    //将复制的节点依次插入到原链表每个节点的后面
    while (cur)
    {
        struct Node* next = cur->next;
        struct Node* copy = malloc(sizeof (struct Node));
        copy->val = cur->val;
        cur->next = copy;
        copy->next = next;
        cur = next;
    }
    cur = head;
    //拷贝原链表的random顺序
    while (cur)
    {
        struct Node* copy = cur->next;
        if (cur->random == NULL)
            //原链表当前节点的random为N时
            copy->random = NULL;
        else
            //原链表当前节点的random指向原链表某个节点时
            copy->random = cur->random->next;
        cur = copy->next;
    }
    cur = head;
    //独立出复制的链表并恢复原链表的链接
    struct Node* copyHead = malloc(sizeof (struct Node));
    struct Node* copyTail = copyHead;
    while (cur)
    {
        struct Node* copy = cur->next;
        copyTail->next = copy;
        copyTail = copyTail->next;
        cur->next = copy->next;
        cur = cur->next;
    }
    copyTail->next = NULL;//处理链表为空的情况
    struct Node* guard = copyHead;
    copyHead = copyHead->next;
    free(guard);
    return copyHead;
}

删除有序链表的重复节点

  给定一个已排序链表的首节点head ,删除原始链表中所有重复数字的节点(重复节点不保留),只留下不同的数字。返回已排序的链表。示例如下:

常见的链表OJ题(中等篇)_第8张图片


基本思路:创建一个哨兵位头节点用于建立不含重复节点的新链表,同时遍历链表,判断当前节点(cur)与下一个节点(next)是否重复。如果当前节点与下一个节点重复,则让当前节点链向下一个节点的下一个节点(cur->next = next->next),并free下一个节点(next),再迭代next(next = cur->next),接着继续判断当前节点是否与下一个节点重复,直到当前节点与下一个节点不再重复或下一个节点为NULL后(内循环结束),free当前节点后迭代cur(cur = next)。如果当前节点与下一个节点不重复,直接将当前节点尾插到新链表,再迭代cur(cur = next)。直到原链表遍历完毕后(外循环结束),新链表也就建立完成了。

按照该思路,大致作图如下:

常见的链表OJ题(中等篇)_第9张图片

注意:当原链表的尾节点与前一个节点重复时,原链表的尾节点不会尾插到新链表,这样就会导致新链表的尾节点没有链向NULL,所以在原链表遍历结束后需要将新链表的尾节点链向空。

代码实现

struct ListNode* deleteDuplicates(struct ListNode* head){
    struct ListNode* newHead = malloc(sizeof (struct ListNode));
    struct ListNode* tail = newHead;
    struct ListNode* cur = head;
    while (cur)
    {
        struct ListNode* next = cur->next;
        int repeat = 0;//默认当前节点与下一个节点不重复
        while (next && cur->val == next->val)
        {
            repeat = 1;//当前节点与下一个节点重复
            cur->next = next->next;
            free(next);
            next = cur->next;
        }
        if (repeat)//发生过至少一次重复
        {
            free(cur);
            cur = next;
        }
        else//当前节点与下一个节点不重复或下一个节点为NULL
        {
            tail->next = cur;
            tail = tail->next;
            cur = next;
        }
    }
    tail->next = NULL;//注意这里要置空
    struct ListNode* guard = newHead;
    newHead = newHead->next;
    free(guard);
    return newHead;
}

你可能感兴趣的:(链表,c语言)