【数据结构】第四站:单链表力扣题(二)

简介: 【数据结构】第四站:单链表力扣题(二)

一、链表的回文结构

题目描述:链表的回文结构_牛客题霸_牛客网

对于这道题,如果没有前面的一些题的基础,是非常难做的,我们的思路是这样的,先找到链表的中间结点,然后从中间结点开始将后半段链表逆置,然后依次比较即可。所以这里就需要复用前面题目的函数了,代码如下所示

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    struct ListNode* middleNode(struct ListNode* phead)
    {
        struct ListNode* fast=phead;
        struct ListNode* slow=phead;
        while(fast&&fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
        }
        return slow;
    }
    struct ListNode* reverse(struct ListNode* phead)
    {
        struct ListNode* prev=NULL;
        struct ListNode* cur=phead;
        while(cur!=NULL)
        {
            struct ListNode* next=cur->next;
            cur->next=prev;
            prev=cur;
            cur=next;
        }
        return prev;
    }
    bool chkPalindrome(ListNode* A) {
        // write code here
        struct ListNode* phead=A;
        struct ListNode* middle=middleNode(phead);
        struct ListNode* NewHead=reverse(middle);
        struct ListNode* cur1=phead;
        struct ListNode* cur2=NewHead;
        while(cur2!=NULL)
        {
            if(cur1->val==cur2->val)
            {
                cur1=cur1->next;
                cur2=cur2->next;
            }
            else
            {
                return false;
            }
        }
        return true;
    }
};

二、相交链表

题目链接:力扣

对于这道题,我们可以采用最简单的双层暴力循环做出来。但是那样效率太低,我们其实可以先算出两个链表的长度,然后让长的链表都差值步,最后两个链表一块走,如果遇到两个结点的地址相同,那么就是相交的位置

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* cur1=headA;
    struct ListNode* cur2=headB;
    int len1=0;
    int len2=0;
    while(cur1)
    {
        len1++;
        cur1=cur1->next;
    }
    while(cur2)
    {
        len2++;
        cur2=cur2->next;
    }
    cur1=headA;
    cur2=headB;
    if(len1>len2)
    {
        int len=len1-len2;
        while(len--)
        {
            cur1=cur1->next;
        }
    }
    else
    {
        int len=len2-len1;
        while(len--)
        {
            cur2=cur2->next;
        }
    }
    while(cur1!=cur2)
    {
        cur1=cur1->next;
        cur2=cur2->next;
    }
    return cur1;
}

三、环形链表

题目链接:力扣

对于这道题,我们可以采用快慢指针法来完成,试想一下,让快指针走两步,慢指针走一步,那么当慢指针走到环的入口的时候,快指针一定在环里面走了一段距离了,而每次快指针与满指针的距离都会缩短一步。那么一定会追的上。最终代码如下所示

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

然而,我们在这里会思考一个问题,慢指针走一步,快指针走两步,如果带环,最后一定可以相遇吗,如果快指针走三步四步呢?

对于这个问题,我们需要好好思考一下。我们假定下面这样一个带环的链表

当fast走进环入口的时候,slow正好走到一半

当slow走到入口的时候,fast已经在环内某个位置了,我们不妨记作此时fast距离slowN步 这样的话,由于距离每次减少1。所以N最终一定会减到0。所以会相遇

那么如果是fast一次三步呢?

我们还是上图进行分析

对于现在相差N步,由于每次距离减少2

所以当N为偶数的时候,一定会减为0

当N为奇数的时候,这时候fast就最终一定会超越slow一步,此时进入新一轮的追击

我们设周长为C,那么fast需要追C-1步

这样的话当C-1为偶数的话,自然追的上,C-1为奇数的话,则永远也不可能追的上

当fast为一次走四步的时候也是同理的。

在这里我们或许还会碰到一个新的问题,如何计算环的长度,这个思路其实也很简单,我们只需要求出相遇点,然后让一个指针不动,另外一个指针一直走然后计数即可。当回到相遇点的时候,正好就是环的长度

四、环形链表Ⅱ

题目链接:力扣

对于这道题,我们可能就比较难以思考了。

我们的分析过程是这样的:

由于是环形链表,那么我们可以求出他的相遇点,既然有了相遇点。我们继续设相遇点与入口的距离为X,头节点与入口点的距离为L

然后我们计算fast和slow两个指针在相遇的瞬间他们已经走的距离

fast的距离为L+N*C+X,其中N大于等于1。因为fast先入环需要追击一圈

slow的距离为L+X

注意这里slow在环内所走的路程一定不超过一圈,即0<=X<C,这是因为fast的速度是slow的二倍,假如fast追赶slow所需要的路程不超过C-1,由于相对速度为1,那么slow所走的路程就不超过C-1。

由于fast的速度是slow的二倍,所以fast的路程是slow的二倍。

所以 L+N*C+X=2(L+X)

可以得出,L=N*C-X

为了方便理解可以进一步化简为:L=(N-1)*C-X

这个公式的物理意义就是,一个指针从头结点开始走,一个指针从相遇点走,那么必然相遇于入口。代码为

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

其实对于这道题,还有另外一种思路,我们可以将相遇点和相遇点的下一个结点断开,然后让相遇点的下一个结点作为头结点,这样就转化为了相交链表的问题

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* cur1=headA;
    struct ListNode* cur2=headB;
    int len1=0;
    int len2=0;
    while(cur1)
    {
        len1++;
        cur1=cur1->next;
    }
    while(cur2)
    {
        len2++;
        cur2=cur2->next;
    }
    cur1=headA;
    cur2=headB;
    if(len1>len2)
    {
        int len=len1-len2;
        while(len--)
        {
            cur1=cur1->next;
        }
    }
    else
    {
        int len=len2-len1;
        while(len--)
        {
            cur2=cur2->next;
        }
    }
    while(cur1!=cur2)
    {
        cur1=cur1->next;
        cur2=cur2->next;
    }
    return cur1;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast=head;
    struct ListNode* slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            struct ListNode* meet=fast;
            struct ListNode* list1=meet->next;
            struct ListNode* list2=head;
            meet->next=NULL;
            return getIntersectionNode(list1,list2);
        }
    }
    return NULL;
}

五、复制带随机指针的链表

题目链接:力扣

解法一

对于这道题,我们有一种比较简单的思路,直接暴力求解。

具体思路是首先将普通的链表拷贝下来。先不处理random指针。这个比较简单

然后,我们开始处理random,对于random我们只能采用相对位置来进行记录。这样的话,时间复杂度达到了O(N²)

我们直接给出代码

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */
struct Node* copyRandomList(struct Node* head) {
  struct Node* copyhead=NULL;
    struct Node* copytail=NULL;
    struct Node* cur=head;
    while(cur)
    {
        struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
        copy->val=cur->val;
        copy->next=NULL;
        if(copyhead==NULL)
        {
            copyhead=copytail=copy;
        }
        else
        {
            copytail->next=copy;
            copytail=copy;
        }
        cur=cur->next;
    }
    cur=head;
    struct Node* copycur=copyhead;
    while(cur)
    {
        if(cur->random==NULL)
        {
            copycur->random=NULL;
            copycur=copycur->next;
        }
        else
        {
            struct Node* cur2=head;
            int count=0;
            while(cur2!=cur->random)
            {
                cur2=cur2->next;
                count++;
            }
            struct Node* copycur2=copyhead;
            while(count--)
            {
                copycur2=copycur2->next;
            }
            copycur->random=copycur2;
            copycur=copycur->next;
        }
        cur=cur->next;
    }
    return copyhead;
}

解法二

我们可以不难看出上面的时间复杂度太大了,我们可以对上面的解法进行一点点的优化,我们采用指针数组先将每个random的地址给记录下来,最后在赋值这样也是可以的。但是仅仅只是优化了一点点。

解法三

想要真正的优化时间复杂度,那么我们必须要从思路开始下手了。

我们可以采用这样的思路:

首先这是我们的原来的链表

我们可以在每一个结点的后面拷贝一个结点出来,如下图所示

最终形式如下图所示

对于这个操作,其实是不难的

这部分的代码如下所示

复制完成以后,我们接下来就要处理random了

最后一步就是,解开原链表 。

解开原来的链表,我们需要使用一个copyhead和copytail指针来管理copy链表,并且逐步解开解开

最终代码如下所示

/**
 * 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;
        struct Node* next=cur->next;
        cur->next=copy;
        copy->next=next;
        cur=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=cur->next->next;
    }
    //解开两个链表
    cur=head;
    struct Node* copyhead=NULL;
    struct Node* copytail=NULL;
    while(cur)
    {
        struct Node* copy=cur->next;
        struct Node* next=copy->next;
        if(copyhead==NULL)
        {
            copyhead=copytail=copy;
            cur->next=next;
        }
        else
        {
            copytail->next=copy;
            copytail=copy;
            cur->next=next;
        }
        cur=next;
    }
    return copyhead;
}

本节内容到此为止

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

相关文章
|
1月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
30 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
1月前
|
存储
[数据结构] -- 单链表
[数据结构] -- 单链表
25 1
|
1月前
|
存储
【数据结构】——单链表实现
【数据结构】——单链表实现
|
1月前
|
存储
数据结构2——单链表
数据结构2——单链表
32 1
|
1月前
|
存储
数据结构(单链表)
数据结构(单链表)
10 0
|
1月前
|
索引
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
77 0
|
1月前
力扣(LeetCode)数据结构练习题(2)
力扣(LeetCode)数据结构练习题(2)
29 0
|
1月前
|
存储
力扣(LeetCode)数据结构练习题
力扣(LeetCode)数据结构练习题
52 0
|
15天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
6天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
15 1