单链表OJ题

1. 删除链表中等于给定值 val 的所有结点。 https://leetcode.cn/problems/remove-linked-list-elements/description/
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val)
{
    struct ListNode *tail=NULL,*newhead=NULL;//如果头结点的值为val需要重新改newhead
    struct ListNode *cur=head;
    while(cur)//遍历整个链表
    {
        if(cur->val!=val)
        {
            if(tail==NULL)
            {
                tail=newhead=cur;
            }
            else
            {
                tail->next=cur;//如果上一个被删除,需要重新连接节点不是要删除的节点
                tail=tail->next;
            }
            cur=cur->next;
        }
        else
        {
            struct ListNode *tmp=cur->next;//把要删除的节点的下一个节点储存起来
            free(cur);//删除值为val的节点
            cur=tmp;//再将刚刚存储的节点赋值过来
        }
    }
    if(tail)
        tail->next=NULL;//把最后的tail的next置为NULL
    return newhead;

}

2. 反转一个单链表。
https://leetcode.cn/problems/reverse-linked-list/description/
在这里插入图片描述

struct ListNode* reverseList(struct ListNode* head)
{
    if(head==NULL)
        return NULL;
    
    struct ListNode* newhead,*cur;

    cur=head;
    newhead=NULL;

    while(cur)//遍历整个链表,当最后一个节点走完退出循环
    {
        struct ListNode* next=cur->next;//先将cur的下一个节点保存起来

        cur->next=newhead;//newhead重新赋值给下一个cur的next,newhead刚开始为NULL,后面依次赋值为cur
        newhead=cur;//newhead向右移一位
        cur=next;//将next赋值给cur
    }

    return newhead;
}

3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
https://leetcode.cn/problems/middle-of-the-linked-list/description/
在这里插入图片描述

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode *slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)//单数时候fast-next为null跳出,双数的时候fast为null跳出
    {
        slow=slow->next;//slow走一步
        fast=fast->next->next;//fast走两步
    }
    return slow;
}

4. 输入一个链表,输出该链表中倒数第k个结点。
https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
在这里插入图片描述
解题思路:
倒数第k个走到最后一个一共需要走k-1步,所以设置两个参数,slow和fast,先让fast走k-1步,再让slow和fast同时走,这样fast就比slow多走了k-1步。这样一来,当fast走到最后一个的时候,slow正好走到倒数第k个节点。
在这里插入图片描述

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
    // write code here
    if(pListHead==NULL)
        return NULL;

    struct ListNode* slow,*fast;
    slow=fast=pListHead;

    while (--k) //先走k-1步
    {
        if (fast->next==NULL) //如果链表没有k这么大,就提前跳出循环
            return NULL;
        
        fast=fast->next;
    }
    while (fast->next) //先走k-1步,就要当fast->next==null的时候跳出循环
    {
        slow=slow->next;
        fast=fast->next;
    }

    return slow;
}

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
https://leetcode.cn/problems/merge-two-sorted-lists/description/
在这里插入图片描述
解题思路一不带哨兵位
创建一个tail和head节点,比较cur1和cur2的大小,谁小的就尾插到tail后面,当其中一个链表走完的时候跳出循环,再用判断是否两个链表都走完了,将剩余的链表直接接在tail后面。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //如果其中一个链表为空,直接返回另一个链表
    if(list1==NULL)
        return list2;
    if(list2==NULL)
        return list1;

    struct ListNode* cur1 = list1,*cur2 = list2;
    struct ListNode* head,*tail;
    head=tail=NULL;

    while(cur1 && cur2)
    {
        //依次比较,取小的尾插
        if(cur1->val < cur2->val)
        {
            if(head==NULL)
            {
                head=tail=cur1;
            }
            else
            {
                tail->next=cur1;
                tail=tail->next;
            }
            cur1=cur1->next;
        }
        else
        {
            if(head==NULL)
            {
                head=tail=cur2;
            }
            else
            {
                tail->next=cur2;
                tail=tail->next;
            }
            cur2=cur2->next;
        }
    }
    //如果其中一个list排完序,另一个没排完,则把另一个继续排完
    if(cur1)
        tail->next=cur1;
    if(cur2)
        tail->next=cur2;
    
    return head;
}

解题思路二:带哨兵位
使用哨兵位将哨兵位的next指向头节点,tail初始化与哨兵位一样next指向null,这样就省去了判断刚开始需要判断head和tail是否为null的步骤,只需要将两个链表中val小的节点依次尾插到tail后面即可,第一步tail的next就是指向头节点,这样哨兵位的next就是头节点,最后将guard的next,即为新链表的头节点赋值给head,再返回即可。
这里需要注意的是,哨兵位最后进行malloc,因为这样的话这样出了作用域不会销毁。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //如果其中一个链表为空,直接返回另一个链表
    if(list1==NULL)
        return list2;
    if(list2==NULL)
        return list1;

    struct ListNode* cur1 = list1,*cur2 = list2;
    struct ListNode* head,*tail,*guard;
    guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//哨兵位最好malloc,这样出了作用域不会销毁

    tail->next=NULL;
    while(cur1 && cur2)
    {
        //依次比较,取小的尾插
        if(cur1->val < cur2->val)
        {

            tail->next=cur1;
            tail=tail->next;            
            cur1=cur1->next;
        }
        else
        {
            tail->next=cur2;
            tail=tail->next;
            cur2=cur2->next;
        }
    }
    //如果其中一个list排完序,另一个没排完,则把另一个继续排完
    if(cur1)
        tail->next=cur1;
    if(cur2)
        tail->next=cur2;

    head=guard->next;//guard->next指向头节点,将其赋值给head
    free(guard);
    return head;
}

6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
在这里插入图片描述
解题思路:
将val小于x的节点放在一个链表,将val大于等于x的放在另一个链表,最后再将val大于等于x的链表放在小于x的后面。
注意:最后需要将大于等于x链表的最后一个节点gtail的next置为NULL,否则会导致链表形成回环。如下图所示
在这里插入图片描述

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/

#include <cstddef>
#include <cstdlib>
#include <malloc.h>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode* lGuard,*ltail;//小于x的尾插到一个链表
        struct ListNode* gGuard,*gtail;//大于等于x的尾插到一个链表
        struct ListNode* cur=pHead;

        lGuard=ltail=(struct ListNode*)malloc(sizeof(struct ListNode));//哨兵位malloc
        gGuard=gtail=(struct ListNode*)malloc(sizeof(struct ListNode));

        ltail->next=gtail->next=NULL;//初始化

        while (cur) //遍历原来的链表
        {
            if(cur->val<x)//小于x的尾插到一个链表
            {
                ltail->next=cur;
                ltail=ltail->next;
            }
            else //大于等于x的尾插到一个链表
            {
                gtail->next=cur;
                gtail=gtail->next;
            }
            cur=cur->next;
        }

        ltail->next=gGuard->next;//将大于等于x的链表尾插到小于x的链表后面
        gtail->next=NULL;//注意:将链表的最后一位的next置为null,否则gtail的next很可能还指向之前的节点,这样会形成回环。

        pHead = lGuard->next;//重新换头节点

        //释放哨兵位防止内存泄漏
        free(lGuard);
        free(gGuard);
        
        return pHead;
    }
};

7. 链表的回文结构。
https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
在这里插入图片描述
解题思路:
1.找到中间节点
2.从中间节点开始,对后半段逆置
3.前半段和后半段进行比较
注意:比较时其中一个链表指向空时跳出循环,注意前半段的最后一个节点也是中间节点,不会因为后半段的逆置改变前半段的最后一个节点的next。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstddef>
//查找中间节点
struct ListNode *middleNode(ListNode* head)
{
    struct ListNode *slow,*fast;
    slow=fast=head;

    while (fast&&fast->next) 
    {
        fast=fast->next->next;
        slow=slow->next;
    }

    return slow;
}
//逆置
struct ListNode* reverseList(ListNode* head)
{
    if (head==NULL) 
        return NULL; 

    struct ListNode* cur,*newhead;
    cur=head;
    newhead=NULL;

    while (cur) 
    {
        struct ListNode* next=cur->next;
        cur->next=newhead;
        newhead=cur;
        cur=next;
    }

    return newhead;
}
class PalindromeList {
public:
    bool chkPalindrome(ListNode* head) 
    {
        struct ListNode* mid = middleNode(head);
        struct ListNode* rhead = reverseList(mid);

        while (head&&rhead) //其中一个为空跳出循环,注意前半段的最后一个节点也是中间节点,不会因为后半段的逆置改变前半段的最后一个节点的next
        {
            if (head->val!=rhead->val) 
                return false;
            head=head->next;
            rhead=rhead->next;
        }
        return true;
    }
};

8. 输入两个链表,找出它们的第一个公共结点。
https://leetcode.cn/problems/intersection-of-two-linked-lists/
在这里插入图片描述
解题思路:
1.先用两个tail和len分别计算俩个链表的长度
2.判断两个链表的最后节点是不是相同,如果两个链表有交点,那么他们的最后一个节点一定是相同的,因为每个节点只有一个next,最差的情况也是最后一个节点是交点,不相同的话直接放回NULL。
3.让长的链表先走差距步
4.再让两个链表同时走,第一个相同的地址就是交点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    struct ListNode *tailA,*tailB;
    tailA=headA;
    tailB=headB;
    //计算两个链表的长度
    int lenA=1;//这里不要让len初始化为0,因为这样计算链表长度循环条件是tail!=NULL,就无法判断接下来两个链表最后一个节点是否相同这一步
    int lenB=1;
    while(tailA->next)
    {
        tailA=tailA->next;
        ++lenA;
    }
    while(tailB->next)
    {
        tailB=tailB->next;
        ++lenB;
    }

    //如果两个链表有交点,那么他们的最后一个节点一定是相同的,因为每个节点只有一个next,最差的情况也是最后一个节点是交点
    if(tailA!=tailB)
        return NULL;

    //让长的链表先走差距步
    int gap=abs(lenA-lenB);
    struct ListNode *shortlist=headA;
    struct ListNode *longlist=headB;
    if(lenA>lenB)
    {
        longlist=headA;
        shortlist=headB;
    }

    while(gap--)
    {
        longlist=longlist->next;
    }

    //再让两个链表同时走
    while(longlist!=shortlist)
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }

    return longlist;

}

9. 给定一个链表,判断链表中是否有环。
https://leetcode.cn/problems/linked-list-cycle/description/
解题思路:
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,
如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
1.为什么slow走一步,fast走两步,他们会相遇?
在这里插入图片描述
其实这就是一个相对速度的问题,假设slow进环的时候fast和slow之间的距离是N,fast和slow的距离从N开始缩小,每次缩小1,当距离为0的时候就一定可以追上
2.slow走一步,fast走x步(x>=3),他们会不会相遇?
在这里插入图片描述
假设fast每次走3步,这时候就需要分两种情况,如果N是偶数,每次距离缩小2,N-2,N-4…2,0,刚好可以追上。但如果N为奇数,每次距离减少2,最后slow和fast的距离会变成-1,也就是变成环的周长C-1,如果C-1是偶数的话,在下一轮追击就可以追上,如果C-1是奇数的话,就会一直循环追击,永远也追不上。
在这里插入图片描述

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

10. 给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL
https://leetcode.cn/problems/linked-list-cycle-ii/description/
在这里插入图片描述
解题思路一:
设起始点到入口点的距离为L,slow走的距离是L+X,其中X在[0,C)之间,因为slow走一圈,fast就走了两圈,所以不可能slow走超过一圈,所以X在[0,C)之间。
而fast在slow进入环之前可能以及走了n圈,所以fast走的距离是L+nC+X;
再由fast和slow走的距离关系列出式子:2
(L+X)=L+n*C+X,化简得L=(n-1)*C+C-X。
这个表达式可以得出一个结论,一个指针从起始点走,一个指针从相遇点走,会在入口点处相遇。
在这里插入图片描述

struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode *slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)//跳出循环说明不带环,返回NULL
    {
        fast=fast->next->next;
        slow=slow->next;

        if(fast==slow)//找到相遇点
        {
            struct ListNode *meet=slow;
            struct ListNode *start=head;

            while(meet!=start)//找入口点
            {
                meet=meet->next;
                start=start->next;
            }       
            return meet;
        }
    }
    return NULL;
}

解题思路二:
转化为两个链表求交点

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) //求交点
{
    struct ListNode * tailA,*tailB;
    tailA=headA;
    tailB=headB;
    int lenA=1;
    int lenB=1;
    while(tailA->next)
    {
        tailA=tailA->next;
        lenA++;
    }
    
    while(tailB->next)
    {
        tailB=tailB->next;
        lenB++;
    }

    if(tailA!=tailB)
        return NULL;

    int gap=abs(lenA-lenB);
    struct ListNode *longlist=headA,*shortlist=headB;
    if(lenA<lenB)
    {
        longlist=headB;
        shortlist=headA;
    }

    while(gap--)
    {
        longlist=longlist->next;
    }

    while(longlist!=shortlist)
    {
        longlist=longlist->next;    
        shortlist=shortlist->next;    
    }
    return longlist;
}
struct ListNode *detectCycle(struct ListNode *head) 
{
    struct ListNode *slow,*fast;
    slow=fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;

        if(fast==slow)
        {
            //转换成It1和It2求交点
            struct ListNode *meet=slow;
            struct ListNode *It1=head;
            struct ListNode *It2=meet->next;

            meet->next=NULL;//把相遇点的next置NULL

            return getIntersectionNode(It1,It2);
        }
    }
    return NULL;
}

11. 给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点
或空结点。要求返回这个链表的深度拷贝。

https://leetcode.cn/problems/copy-list-with-random-pointer/description/
在这里插入图片描述
解题思路:

  1. 插入拷贝节点在原节点后面
    在这里插入图片描述
    2. 拷贝节点的random等于原节点random的next
    这一步是重点,因为在上一步进行插入拷贝节点在源节点之后,每个源节点的next就是自己的拷贝节点,而每个源节点的random的next就是源节点的random的拷贝节点。所以拷贝节点的random就是自己源节点random的next。
    在这里插入图片描述
  2. 拷贝节点下来,恢复原链表,链接成新链表

在这里插入图片描述
将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;
    //1.插入拷贝节点在原节点后面
    while(cur)
    {
        struct Node* copy = (struct Node *)malloc(sizeof(struct Node));//malloc出了作用域不会销毁
        copy->val=cur->val;
        struct Node* next = cur->next;

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

        cur=next;
    }

    //2.拷贝节点的random等于原节点random的next
    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;
    }

    //3.拷贝节点下来,恢复原链表,链接成新链表
    cur = head;
    struct Node* copyhead,*copytail;
    copyhead = copytail = NULL;

    while(cur)
    {
        struct Node* copy = cur->next;
        struct Node* next = copy->next;

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

        //恢复原链表
        cur->next=next;
        cur=next;
    }

    return copyhead;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值