链表经典OJ题

一、返回倒数第k个节点

解题思路:快慢指针。快指针和慢指针的距离始终是k,快指针走到最后的时候,慢指针刚好指向倒数第k个节点。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
int kthToLast(struct ListNode* head, int k)
{
    struct ListNode* fast = head,*slow = head;
    //快指针先走k步
    while(k--)
    {
        fast = fast->next;
    }
    while(fast != NULL)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return slow->val;
}

二、判断是否为回文链表

解题思路:用双指针法找到中间节点,把链表中间节点及以后的部分逆置,然后用双指针法一一比较节点值。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{
    //创建快慢指针
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        //此时slow正好指向中间节点
    }
    return slow;
}


struct ListNode* reverseList(struct ListNode* head)
{
   //判空
   if(head == NULL)
   {
    return head;
   }
   //创建三个指针
   ListNode*n1,*n2,*n3;
   n1 = NULL,n2 = head,n3 = n2->next;
   while(n2)
   {
    n2->next = n1;
    n1 = n2;
    n2 = n3;
    if(n3)
        n3 = n3->next;
   }
   return n1;
}

bool isPalindrome(struct ListNode* head)
{
    ListNode* mid = middleNode(head);
    ListNode* rmid = reverseList(mid);

    while(rmid && head)
    {
        if(rmid->val != head->val)
            return false;
        rmid = rmid->next;
        head = head->next;
    }
    return true;
}

这道题用到了寻找链表中间节点和反转链表的算法,不熟悉的可以看这里:

几道链表经典算法题-CSDN博客

三、相交链表

解题思路:先判断两个链表是否相交,相交再寻找第一个交点。

判断是否相交,只要看两链表的最后一个节点是否相同,相同就是相交,不同就是不相交。

寻找第一个相交节点,要先计算两链表的长度。假设短链表的长度是k,则只需创建两个指针,分别别从短链表的第一个节点和长链表的倒数第k个节点开始同时往后走,遇到的第一个相同节点就是两个链表的第一个交点。这样写出来的算法时间复杂度为O(N)

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode* curA = headA,*curB = headB;
    int lenA = 1,lenB = 1;
    while(curA->next)
    {
        curA = curA->next;
        ++lenA;
    }
    while(curB->next)
    {
        curB = curB->next;
        ++lenB;
    }
    if(curA != curB)
    {
        return NULL;
    }
    //长的先走差距步,再同时走,第一个相等的就是交点
    int gap = abs(lenA-lenB);
    struct ListNode* longList = headA,*shortList = headB;
    if(lenB > lenA)
    {
        longList = headB;
        shortList = headA;
    }

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

四、环形链表

4.1 判环

解题思路:快慢指针。fast指针的速度是slow指针的两倍。如果fast走到了NULL,说明链表不带环;如果fast追上了slow,说明链表带环。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast)
        {
            return true;
        }
    }
    return false;
}

这道题还有两个问题需要想清楚:

1、为什么环形链表中fast和slow一定会相遇?

假设slow进环时fast和slow的距离是N。fast追击slow的过程中,slow每走一步,fast就走两步,两者的距离缩短1。那么N次之后fast必然追上slow。

2、如果fast每次走的步数变为3步、4步、5步、n步,是否可行?

如果fast每次走3步,假设slow进环时fast和slow的距离是N。如果N是偶数,N/2次追上。如果N是奇数,第一次追击会错过,进入新一轮追击,假设环的长度是C,那么错过之后两者距离是C-1。如果C-1是偶数,这一轮追击中fast会追上slow。如果C-1是奇数,那么进入死循环,永远追不上。

也就是说,N是奇数和C是偶数同时满足的时候,就永远追不上。

那么这两个条件可能同时满足吗?

为了验证,我们需要找到C和N的关系式。

假设slow进环时,fast已经在环中走了x整圈。假设链表除了环之外的部分长度为L。根据fast走的距离是slow的三倍,我们可以列出如下等式:

L+x*C+(C-N) = 3L

整理得到

2L = (x+1)*C-N

此式等号左边为偶数。如果N为奇数,C为偶数,那么等号右边为奇数,等号不成立。由此可知这两个条件不可能同时满足,也就是fast一定会追上slow。

如果fast每次走4步,仍然假设slow进环时fast和slow的距离是N。有三种情况,如图:

接下来要讨论C-2、C-1 %3的三种可能结果。不再赘述。

结论是只要n是大于等于2的整数,都可以完成判环的功能。

4.2 寻找环的入口点

解题思路:开始的部分同上一题,让slow和fast在环中相遇。接下来,让head从链表的第一个节点开始走,meet从两指针在环内相遇的地方开始走,两者都每次走一步,那么两者会刚好在链表的入口处相遇。

为什么呢?分析一下:

设链表除环之外的部分长为L,slow和fast相遇点距离环入口处的距离为N,环长为C。假设fast

追上slow时fast已经转了完整的x圈。

slow走的距离为L + N

fast走的距离为L + N +x*C

根据fast指针走的距离是slow的两倍,可以列出等式:

2*(L + N) = L + N + x*C

化简得到L = x*C - N

可知两者总会在链表入口处相遇。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast)
        {
           struct ListNode* meet = slow;
           while(meet != head)
           {
            meet = meet->next;
            head = head->next;
           }
           return meet;
        }
    }
    return NULL;
}

还有第二种解题思路:将meet的下一个节点设为newhead,然后将meet的next指针置为空。

这样就变成了相交链表求交点的问题了。直接CV上一题的代码:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode* curA = headA,*curB = headB;
    int lenA = 1,lenB = 1;
    while(curA->next)
    {
        curA = curA->next;
        ++lenA;
    }
    while(curB->next)
    {
        curB = curB->next;
        ++lenB;
    }
    if(curA != curB)
    {
        return NULL;
    }
    //长的先走差距步,再同时走,第一个相等的就是交点
    int gap = abs(lenA-lenB);
    struct ListNode* longList = headA,*shortList = headB;
    if(lenB > lenA)
    {
        longList = headB;
        shortList = headA;
    }

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

struct ListNode *detectCycle(struct ListNode *head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;

        if(slow == fast)
        {
           struct ListNode* meet = slow;
           struct ListNode* newhead = meet->next;

           meet->next = NULL;
           return getIntersectionNode(head,newhead);
           
        }
    }
    return NULL;
}

虽然题目写了"不允许修改链表",但是这样写在leetcode里也能通过。

五、随机链表复制

解题思路:先复制每个节点,把每个新节点插入在原链表对应旧节点的后面,如图:

这样就在原节点和新节点之间建立了关系,可以接下来就可以解决random指针难以复制的问题了。我们让每个新节点的random指针指向对应旧指针所指节点的后面一个节点,就完成了random指针的复制。

最后,把新的节点尾插到新链表中即可。

cpp 复制代码
/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */


struct Node* copyRandomList(struct Node* head)
{
	struct Node* cur = head;
    //拷贝节点插入在原节点的后面
    while(cur)
    {
        struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
        copy->val = cur->val;

        copy->next = cur->next;
        cur->next = copy;

        cur = copy->next;
    }

    //控制random指针
    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;
        if(cur->random == NULL)
        {
            copy->random = NULL;
        }
        else
        {
            copy->random = cur->random->next;//这一步是整道题目的关键
        }

        cur = copy->next;
    }

    //把拷贝节点取下来尾插成为新链表,然后恢复原链表
    struct Node* copyhead = NULL;
    struct Node* copytail = NULL;
    cur = head;
    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;

        if(copytail == NULL)
        {
            copyhead = copytail = copy;
        }
        else
        {
            copytail->next = copy;
            copytail = copytail->next;
        }

        cur->next = next;
        cur = next;
    }
    return  copyhead;
}
相关推荐
葫三生1 小时前
三生原理文章被AtomGit‌开源社区收录的意义探析?
人工智能·深度学习·神经网络·算法·搜索引擎·开源·transformer
AI进化营-智能译站1 小时前
ROS2 C++开发系列15-模板实现通用算法|宏定义ROS2调试开关|一次编码适配多平台
java·c++·算法·ai
澈2071 小时前
C++引用与指针:核心区别全解析
开发语言·数据结构·c++
刀法如飞1 小时前
Java数组去重的20种实现方式——指导AI解决不同问题的思路
java·算法·面试
良木生香2 小时前
【C++初阶】STL——Vector从入门到应用完全指南(1)
开发语言·c++·神经网络·算法·计算机视觉·自然语言处理·数据挖掘
Brilliantwxx2 小时前
【C++】String的模拟实现(代码实现与坑点讲解)
开发语言·c++·笔记·算法
爱编码的小八嘎2 小时前
C语言完美演绎9-14
c语言
憨波个2 小时前
【说话人日志】DOVER:diarization 输出融合算法
人工智能·算法·音频·语音识别·聚类
爱学习的张大2 小时前
具身智能论文问答(四):pi0
人工智能·算法