链表OJ习题(2)

一. 牛客网 链表的回文结构

https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

思路

1、偶数情况:

2、奇数情况:

代码实现

复制代码
class PalindromeList {
  public:
    //1.找中间节点
    ListNode* middleNode(ListNode* head) {
        //快慢指针
        ListNode* fast, * slow;
        fast = head;
        slow = head;
        while (fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }

    //2.翻转以中间节点为头的链表
    ListNode* reverseList(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 chkPalindrome(ListNode* A) {
        //1.找中间节点
        ListNode* mid = middleNode(A);
        //2.翻转以中间节点为头的链表
        ListNode* right = reverseList(mid);
        //3.遍历原链表和反转后的链表,比较节点的值是否相等
        ListNode* left = A;
        while (right) {
            if (left->val != right->val) {
                return false;
            }
            left = left->next;
            right = right->next;
        }
        return true;
    }
};

二. leetcode 160 相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

思路

共存在三种相交情况(一定会在末尾相遇):

1、在末尾处相交

2、在起点处相交

3、在中间处相交

不存在以下这种情况:因为 c1 只有1个next指针,只能指向一个节点

代码实现

复制代码
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    ListNode* pa=headA;
    ListNode* pb=headB;
    int sizeA=0,sizeB=0;
    while(pa)
    {
        sizeA++;
        pa=pa->next;
    }
    while(pb)
    {
        sizeB++;
        pb=pb->next;
    }
    //求长度差
    int gap=abs(sizeA-sizeB);//求绝对值
    //假设法
    ListNode* longList=headA;
    ListNode* shortList=headB;
    if(sizeA<sizeB)
    {
        longList=headB;
        shortList=headA;
    }
    while(gap--)
    {
        longList=longList->next;
    }
    while(shortList)
    {
        if(longList==shortList)
        {
            return shortList;
        }
        shortList=shortList->next;
        longList=longList->next;
    }
    return NULL;
}

三. 环形链表 |

思路

https://leetcode.cn/problems/linked-list-cycle/description/

slow⼀次走⼀步,fast⼀次走2步,fast先进环,假设slow也走完⼊环前的距离,准备进环,此时fast 和slow之间的距离为N,接下来的追逐过程中,每追击⼀次,他们之间的距离缩小1步

追击过程中fast和slow之间的距离变化:

因此,在带环链表中慢指针走⼀步,快指针走两步最终⼀定会相遇。

思考2:快指针⼀次走3步,走4步,...n步⾏吗?

答案是可以的,有兴趣的同学可以自行推导一下,这里就不再赘述了

代码实现

复制代码
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
    if(head==NULL)
    {
        return false;
    }
    //快慢指针
    ListNode*slow,*fast;
    slow=fast=head;
    while(fast!=NULL && fast->next!=NULL)
    {
        slow=slow->next;
        fast=fast->next->next;
        //如果快指针与慢指针相遇,那么一定有环
        if(slow==fast)
        {
            return true;
        }
    }
    return false;
}

四. 环形链表 ||

https://leetcode.cn/problems/linked-list-cycle-ii/description/

思路

说明:
H为链表的起始点,E为环入口点,M与判环时候相遇点
设:
环的⻓度为R,H到E的距离为L,E到M的距离为 X ,则:M到E的距离为 R-X

在判环时,快慢指针相遇时所⾛的路径⻓度:
fast: L+X + nR
slow:L+X

注意:
1. 当慢指针进⼊环时,快指针可能已经在环中绕了n圈了,n⾄少为1
因为:快指针先进环⾛到M的位置,,最后⼜在M的位置与慢指针相遇
2.慢指针进环之后,快指针肯定会在慢指针⾛⼀圈之内追上慢指针
因为:慢指针进环后,快慢指针之间的距离最多就是环的⻓度,⽽两个指针在移动时,每次它们⾄今
的距离都缩减⼀步,因此在慢指针移动⼀圈之前快,指针肯定是可以追上慢指针的,⽽快指针速度是慢指针的两倍,因此有如下关系是:
2 * (L+X)=L+X+nR
L+X=nR
L=nR-X
L = (n-1)R+(R-X)
(n为1,2,3,4......,n的大小取决于环的大小,环越小n越⼤)

因为R为环的周长,所以无论前面的系数是多少,最终都是环周长的整数倍
所以:
L=R-X
即:⼀个指针从链表起始位置运行,⼀个指针从相遇点位置绕环,每次都走⼀步,两个指针最终会在入口点的位置相遇

代码实现

复制代码
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
    ListNode* slow=head;
    ListNode* fast=head;
    while(fast && fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            //带环
            //头节点到入环节点的距离 == 相遇节点到入环节点的距离
            ListNode* pcur=head;//这里就再一次用到了head,所以之前不能用head遍历
            while(pcur!=slow)
            {
                pcur=pcur->next;
                slow=slow->next;
            }
            return pcur;
        }
    }
    return NULL;
}

五. 随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/description/

思路

1、在原链表基础上拷贝节点并插入到原链表中

2、设置 radom 指针(注:这里未将所有的radom指针画完)

3、断开与原链表的链接(注:这里未将所有的radom指针画完)

代码实现

复制代码
/**
 * 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));
    if (newnode == NULL) {
        return NULL; // 处理内存分配失败的情况
    }
    newnode->val = x;
    newnode->next = newnode->random = NULL;
    return newnode;
}

//在原链表基础上拷贝节点并插入到原链表中
void AddNode(Node* head) 
{
    Node* pcur=head;
    while(pcur)
    {
        Node* newnode=BuyNode(pcur->val);
        Node* next=pcur->next;
        newnode->next=next;
        pcur->next=newnode;
        pcur=next;
    }
}

//设置radom
void setRandom(Node* head)
{
    Node* pcur=head;
    while(pcur)
    {
        Node* copy=pcur->next;
        if(pcur->random)
            copy->random=pcur->random->next;
        pcur=copy->next;
    }
}

struct Node* copyRandomList(struct Node* head) {
    if(head==NULL)
    {
        return head;
    }
	//在原链表基础上拷贝节点并插入到原链表中
    AddNode(head);
    //设置radom
    setRandom(head);
    //断开链表
    Node* pcur=head;
    Node* copyHead,* copyTail;
    copyHead=copyTail=pcur->next;
    while(copyTail->next)
    {
        pcur=copyTail->next;
        copyTail->next=pcur->next;
        copyTail=copyTail->next;
    }
    return copyHead;
}

总结

本篇博客深入解析了经典的链表OJ习题,旨在帮助读者掌握链表操作的核心技巧与解题思路,通过对典型例题的剖析和画图理解,助你巩固数据结构基础。

如果大家在练习中发现新的解题角度,或是有没搞懂的知识点,欢迎在评论区留言讨论。后续我也会持续更新数据结构相关的 OJ 解析,咱们一起在刷题中夯实基础,稳步进阶!