链表OJ题讲解2

简介: 回文节后是对称的,所以这个题目我们可以先找到中间节点,然后将中间反转,然后两个指针,一个从头开始,一个从反转后的头节点开始,一一比对,如果val不相等一定不是回文,如果相等,那么一定是回文链表。

链表的回文结构

481ba57343f84bb39d9ed0feb122860a.png

回文节后是对称的,所以这个题目我们可以先找到中间节点,然后将中间反转,然后两个指针,一个从头开始,一个从反转后的头节点开始,一一比对,如果val不相等一定不是回文,如果相等,那么一定是回文链表。


偶数个数据时41b362b9e2ec47fc899549c3cfa4d1bd.png

奇数个数据时:

bd1786cd61234e98a1a27e2bee7ddc98.png

所以我们可以清楚的知道当rmid为空时就要结束循环。

代码如下:

// 计算中间节点
struct ListNode* midNode(struct ListNode* head)
{
    struct ListNode* fast = head,*slow = head;
    while(fast&&fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
// 翻转链表
struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* cur = head, *newhead = NULL,*next = NULL;
    while(cur)
    {
        next = cur->next;
        cur->next = newhead;
        newhead = cur;
        cur = next;
    }
    return newhead;
}
bool isPalindrome(struct ListNode* head)
{
    //计算中间节点
    struct ListNode* mid = midNode(head);
    //反转链表
    struct ListNode* rmid =reverseList(mid);
    while(rmid)
    {
        //不相等一定不回文,返回false
        if(head->val!=rmid->val)
        {
            return false;
        }
        head = head->next;
        rmid = rmid->next;
    } 
    //循环结束一定是回文
    return true;
}

链表分割

ba649eeb09344cd78390974eb0a8653d.png

这个题我们需要两个链表,然后遍历原链表,将小于x的尾插到head1,将大于等于x的尾插带head2,然后将两个链表链接一下,需要注意的是我们需要将head2的为尾节点的next置空,不然可能会出现带环链表,还有就是我们的head1有可能为空,这里我们使用带哨兵位的链表,就可以解决这个问题,但是head2也可能为空,这时我们就需要将head1的尾节点next置空,不然就会存在非法访问。


代码如下:

struct ListNode* partition(struct ListNode* head, int x)
{
    struct ListNode* head1,*tail1,*head2,*tail2;
    //开辟两个哨兵位
    head1 = tail1 = (struct ListNode*)malloc(sizeof(struct ListNode));
    head2 = tail2 = (struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val<x)
        {
            //小于的尾插到tail1
            tail1->next = cur;
            cur = cur->next;
            tail1 = tail1->next;
        }
        else
        {
            //大于的尾插到tail2
            tail2->next = cur;
            cur = cur->next;
            tail2 = tail2->next;
        }
    }
    //链接两个链表
    tail1->next = head2->next;
    //防止出现带环链表
    tail2->next = NULL;
    if (head2->next == NULL)
    {
        //第二个链表为空,这是要将这是要将tail1->next置空,不然会出现非法访问
        tail1->next = NULL;
        struct ListNode* next = head1->next;
        free(head2);
        free(head1);
        return next;
    }
    //保存头结点
    struct ListNode* next = head1->next;
    //释放哨兵位
    free(head1);
    free(head2);
    return next;
}

两个链表的交集

image.png

判断是否相交非常简单,我们只需要判断两个链表的为节点是否相同就可以了,如果相同,那么它们一定是相交的,我们就可以找相加的那个节点,我们在找尾的时候可以记录一下两个链表的长度,由于从相交的位置开始后面的节点都一样了,我们可以计算出长的那个链表比短的链表长多少,让长的链表先走多少步,然后让两个链表同时走,相等的第一个节点就是我们要求的。


代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode* curA = headA,*curB = headB;
    int s1 = 1;
    int s2 = 1;
    //找A链表的尾,顺表记录长度
    while(curA->next)
    {
        curA= curA->next;
        s1++;
    }
    //找B链表的尾,顺表记录长度
    while(curB->next)
    {
        curB= curB->next;
        s2++;
    }
    //尾节点不相等一定不相交
    if(curA!=curB)
    {
        return NULL;
    }
    //计算差值
    int k = abs(s1-s2);
    //假设长的链表为A
    struct ListNode* longNode = headA,*shortNode = headB;
    //如果B链表长,则将长链表赋值B
    if(s1<s2)
    {
        longNode = headB;
        shortNode = headA;
    }
    //长链表先走k步
    while(k--)
    {
        longNode= longNode->next;
    }
    //寻找第一个相等的节点
    while(longNode!=shortNode)
    {
        longNode = longNode->next;
        shortNode = shortNode->next;
    }
    return longNode;
}

链表循环

image.png

我们可以用快慢指针来解决这个问题,我们让慢指针走一步,快指针走两步,如果有环,那么当俩个指针都进入环中是,就是追击问题,如果快指针走到NULL,或者快指针的next为空,那么就意味着链表无环。


代码如下:

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

思考

1.如果slow走一步,fast走二步是不是一定可以追上?

答案是一定可以追上,为什么呢,假设slow进环时fast与它相距N步,那么每走一步,他们之间的距离减少一,而N一定小于一圈的长度,所以在一圈之内fast一定追上slow。

8ac546dc3c3c49009c6a38e2bd46b02b.png2.如果slow走一步,fast走三步是不是一定可以追上?


答案是不太一定,假设他们之间的距离还是N,如果N是偶数那么就追上了,如果N奇数,他们就会错过,假设圆的长度为C,他们的距离就会变成C-1,这是又要分情况了,假设C-1位奇数那么就一定追不上了,如果C-1位偶数那么在下一圈的追击中就会追上。

6ee6ce4d92d24873adcd73f6e52b596f.png


链表周期 II

59af2fc719a1487aadae5e03f63e4843.png

方法一

我们需要推导一个公式,假设头到入环点的长度为L,然后假设他们在环中相遇的点为X,入环点到X的距离为N,然后整个环的长度为C,我们可以推导:

64d2233568f64c3d85894cbe915cbdfa.png

有了这个推导关系我们就可以写代码了。

代码如下:

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* slow = head,*fast = head;
    while(fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        //判断是否相遇
        if(fast==slow)
        {
            struct ListNode* meet = fast,*cur = head;
            //一个从相遇点开始走,一个从头开始走
            while(meet!=cur)
            {
                meet = meet->next;
                cur = cur->next;
            }
            //此时相遇的点就是入环点
            return meet;
        }
    }
    //出循环一定没有环
    return NULL;
}

方法二

我们还是找相遇点,然后我们把相遇点的next置空,保存相遇点的next,然后就变成了相交链表。

187ec44fc6e646a686c9c3b67dde99f4.png代码如下:

//找相交链表的第一个相交点
 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
    struct ListNode* curA = headA,*curB = headB;
    int s1 = 1;
    int s2 = 1;
    //找A链表的尾,顺表记录长度
    while(curA->next)
    {
        curA= curA->next;
        s1++;
    }
    //找B链表的尾,顺表记录长度
    while(curB->next)
    {
        curB= curB->next;
        s2++;
    }
    //尾节点不相等一定不相交
    if(curA!=curB)
    {
        return NULL;
    }
    //计算差值
    int k = abs(s1-s2);
    //假设长的链表为A
    struct ListNode* longNode = headA,*shortNode = headB;
    //如果B链表长,则将长链表赋值B
    if(s1<s2)
    {
        longNode = headB;
        shortNode = headA;
    }
    //长链表先走k步
    while(k--)
    {
        longNode= longNode->next;
    }
    //寻找第一个相等的节点
    while(longNode!=shortNode)
    {
        longNode = longNode->next;
        shortNode = shortNode->next;
    }
    return longNode;
}
struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode* slow = head,*fast = head;
    while(fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        //判断是否相遇
        if(fast==slow)
        {
            struct ListNode* meet = fast,*newhead = fast->next;
            //转换相交链表
            meet->next = NULL;
            //找第一个相交点,也就是入环点
            return getIntersectionNode(head,newhead);
        }
    }
    //出循环一定没有环
    return NULL;
}

总结一下:

方法一逻辑强,不好推导,但是推导出来代码好写

方法二逻辑没有那么强,适合我们普通人,但是代码量稍微大一点。


今天的分享就到这里结束了,感谢大家的关注和支持。

相关文章
|
8天前
数据结构——链表OJ题
数据结构——链表OJ题
|
8天前
|
存储
2023 8 -14链表OJ(上)
2023 8 -14链表OJ
37 0
|
8天前
链表OJ详解
链表OJ详解
29 0
|
20小时前
|
索引
|
20小时前
|
2天前
|
算法 搜索推荐 索引
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(下)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
6 0
|
2天前
|
算法 程序员 索引
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(中)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
13 0
|
2天前
|
算法 C语言 C++
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(上)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
12 0
|
2天前
|
算法 测试技术
数据结构与算法⑤(第二章OJ题,上)前五道链表面试题(下)
数据结构与算法⑤(第二章OJ题,上)前五道链表面试题
9 0
|
2天前
|
算法
数据结构与算法⑤(第二章OJ题,上)前五道链表面试题(上)
数据结构与算法⑤(第二章OJ题,上)前五道链表面试题
9 0