链表OJ题(2)---第七期

一、分隔链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

示例 1:


输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:

输入:head = [2,1], x = 2
输出:[1,2]
 

提示:

链表中节点的数目在范围 [0, 200] 内
-100 <= Node.val <= 100
-200 <= x <= 200

来源:力扣(LeetCode)
链接:分隔链表

分析题目:

将比给定值x小的节点放在等于x或者比x大的节点后面,并且顺序还不能乱。

那我们要创建两个链,一个存放比x小的节点,另一条存放比x大的和等于x的

效果图:

 之后我们把存放小节点的链链接存放大节点的链,使大节点的尾节点指向NULL,全部ok

我的蠢逼方法(为了以后回头看自己写的博客,感受自己当年的愚蠢):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* partition(struct ListNode* head, int x)
{
    if(head == NULL)
    {
        return NULL;
    }
    struct ListNode* lessHead = NULL;
    struct ListNode* lessTail = NULL;
    struct ListNode* greaterHead = NULL;
    struct ListNode* greaterTail = NULL;
    while(head != NULL)
    {
        struct ListNode* cur = head;
        head = head->next;
        if(cur->val < x)
        {
            if(lessHead == NULL)
            {
                lessHead = cur;
                lessTail = cur;
            }
            else
            {
                lessTail->next = cur;
                cur->next = NULL;
                lessTail = cur;
            }
        }
        if(cur->val >= x)
        {
            if(greaterHead == NULL)
            {
                greaterHead = cur;
                greaterTail = cur;
                greaterTail->next = NULL;
            }
            else
            {
                greaterTail->next = cur;
                cur->next = NULL;
                greaterTail = cur;
            }
        }
    }
    if(lessHead != NULL)
    {
        lessTail->next = greaterHead;
        return lessHead;
    }
    else
    {
        return greaterHead;
    }
}

完全就是提交完看哪块不完善,就修补修补

高级方法: 

ListNode* partition(ListNode* head, int x) {
    //开一个哨兵位头节点,方便尾插
        struct ListNode* lessHead,*lessTail,*greaterHead,*greaterTail;
        lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        lessTail->next = NULL;
        greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
        greaterTail->next = NULL;
        
        
        struct ListNode* cur = head;

        while(cur)
        {
            //判断
            if(cur->val < x)
            {
                lessTail->next = cur;
                lessTail = cur;
            }
            else
            {
                greaterTail->next = cur;
                greaterTail = cur;
            }
            //cur向后走一位
            cur = cur->next;
        }
        //因为使哨兵位,greaterHead的下一个节点才是存放的第一个存放的节点
        lessTail->next = greaterHead->next;
        //将尾节点指向NULL
        greaterTail->next = NULL;
        //创建一个newHead保存头节点位置,之后释放开辟的两个节点
        struct ListNode* newHead = lessHead->next;
        free(lessHead);
        free(greaterHead);
        
        return newHead;
    }

这其中用到了哨兵位,给一个节点开辟空间,之后指向要存放的节点,

这样的好处可以省略第一次存放的时候新链表中的节点为NULL,要将他赋值为要存放的节点

但记得最后要释放它

二、链表的回文结构

链接:链表的回文结构

思路💡💡💡:

说白了就是判断一个链表是不是中心对称,是的话返回true,不是的话返回false

如果是数组的话这题就十分简单了,找到最左边和最右边,左指针往右走,右指针往左走

同时往中间靠拢,依次对比直到在同一个位置,或者左指针大于右指针

但是链表有单向性,不能找到上一个节点

其实这道题就是我们之前做过的链表找中间节点,和链表前插的问题结合到一起

想找到中间节点mid,之后将中间节点后的节点前插到一个新节点称为newHead

之后将mid的下一个节点赋值给newHead,再将mid的next指向NULL,这样链表就被分割为一半了

可以定义一个cur作为给定链表的头部,之后cur与newHead一一对比,不相同就返回false,相同返回true

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) 
    {
        ListNode* slow = A;
        ListNode* fast = A;
        ListNode* newnode = A;
        ListNode* newhead = NULL;
        //找中间点
        while(fast == NULL && fast->next == NULL)
        {
            slow = slow->next;
            fast = (fast->next)->next;
        }
        newhead = slow->next;
        //逆置
        while(newhead != NULL )
        {
            if(newnode == NULL)
            {
                newnode = newhead;
                newhead = newhead->next;
            }
            else{
                newnode->next = newhead;
                newnode = newhead;
                newhead = newhead->next;
            }
        }
        //判断
        while(newnode && A)
        {
            if(A->val != newnode->val)
            {
                return false;
            }
            A = A->next;
            newnode = newnode->next;
        }
        return true;
    }
};

三、相交链表 

 这道题其实问了两个问题❓❓❓:

他俩是否相交?

相交节点在哪?

思路💡💡💡:

1、尾节点相同就是相交,否则就不相交

2、求交点:长的链表先走(长度差)步,在同时走,第一个相同的就是交点

代码🖋🖋🖋:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {

    struct ListNode* tailA = headA;
    struct ListNode* tailB = headB;
    int lenA = 1;
    int lenB = 1;
    //计算各自长度
    while(tailA)
    {
        tailA = tailA->next;
        ++lenA;
    }
    while(tailB)
    {
        tailB=tailB->next;
        ++lenB;
    }
    //不相交
    if(tailA != tailB)
    {
        return NULL;
    }
    //让长的先走差的步数
    int gap = abs(lenA-lenB);
    //看谁长
    struct ListNode* longList = headA;
    struct ListNode* shortList = headB;
    if(lenA < lenB)
    {
        shortList = headA;
        longList = headB;
    }
    while(gap--)
    {
        longList = longList->next;
    }
    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }
    return shortList;
}

哥们自己写的暴力破解:

思路💡💡💡:

依次取A链表中的每个节点跟B链表中的所有节点比较

如果有地址相同的节点,就是相交,第一个相同的交点

代码🖋🖋🖋:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL || headB==NULL)
    {
        return NULL;
    }
    struct ListNode* curA = headA;
    struct ListNode* curB = headB;
    for(curA;curA != NULL;curA = curA->next)
    {
        curB = headB;
        while(curB)
        {
            if(curB == curA)
            {
                return curA;
            }
            curB = curB->next;
        }
    }
    return NULL;
}

暴力破解的时间复杂度为O(N2),第一种方法的时间复杂度为O(N)

为了追求更高效的运行,大家还是学习一下第一种方法

四、环形链表

判断是否是环链表操作起来很简单,但是不一定能想到

假如两个人一个跑的快一个跑的慢,从一条直道出发,跑进一个圆形跑道,那么他们两个总会相遇

所以我们创建一个快慢指针

/**
 * 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;
        struct ListNode* cur = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast)
            {
                return true;
            }
        }
        
        return false;
}

延伸问题:

1、为什么slow和fast一定会在环中相遇?会不会在环里面错过,永远遇不上

结论:他们一定会相遇

分析证明:

第一步:无论怎么走slow和fast,fast一定是先进环,且slow走了入环前的进程的一半

第二步:随着slow进环,fast已经在环里面走了一段了,走了多少跟环的大小有关系

假设slow进环的时候,slow跟fast的距离是N,fast开始追slow

slow每往前走1步,fast往前走2步,判断一下相遇

每追一次,fast和slow的距离变化:

如果N是偶数

N        N-1        N-2        N-3        .....        0

每追一次他们的距离减少1,他们的距离减到0的时候就是相遇点

2、为什么slow走一步,fast走的两步呢?能不能fast走两次以上呢

结论:fast一次走n步?n > 2不一定会相遇

假设:

slow一次走1步,fast一次走3步

slow进环以后,fast跟slow之间的距离为N,fast开始追slow

如果N是偶数

N        N-2        N-4        N-6        .....        0        可以追上

如果N是奇数

N        N-2        N-4        N-6        .....        -1        这一次追不上

如果N是奇数,距离变成-1意味着什么呢?意味他们之间的距离变成C-1(C是环的长度)

如果C-1是奇数,那么就永远追不上了,如果C-1是偶数,那么就可以追上

五、环形链表 II

这题与上一道题一样,但是需要返回环的头节点

慢指针走的距离:L+X

快指针走的距离:L+N*C+X (N>=1)

快指针走的路程是慢指针的两倍:

2L + 2X = L + N * C + X

L = N*C + X   -> L = (N-1)*C + C - X

入环后,每走C的长度,都会从环的起点开始,所以(N-1)*C忽略不算,推算出L = C - X

那么我们计算出X的位置,之后head和X同时走,再次相遇就是环的起始节点了

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow = head, *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;
}

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

 这道题可是上强度了,非常考验咱们对链表的增删改查

向上图一样我们把链表复制下来很简单,但是节点中的random指向要求与原链表一样,这可就难了

 我们将每一个节点后插入一个与前节点相同的节点,插入完毕后,形成一个新链表

假如说要让复制值为7的这个节点中的random值等于被复制节点,

那么只需要node7->random = node7->next->random

将每一个节点都处理ok了之后,再将复制的链链接起来,形成复制的链表

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) 
{
    //1、拷贝节点插入原节点的后面
	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;
    }
    //2、根据原节点,处理copy节点的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,*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 = copy;
        }
        cur->next = next;
        cur = next;
    }
    return copyHead;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖晨曦7

真的有人赏我一下吗?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值