单链表经典算法

目录

引言

链表经典算法OJ题⽬

1 单链表相关经典算法OJ题1:移除链表元素

题目描述:

思路:

​编辑 代码

2单链表相关经典算法OJ题2:反转链表

题目描述:

思路:

代码 

3单链表相关经典算法OJ题3:合并两个有序链表

题目描述:

思路:

代码 

4 单链表相关经典算法OJ题4:链表的中间结点

题目描述:

思路:

代码 

5 循环链表经典应⽤-环形链表的约瑟夫问题

题目描述:

思路:

代码 

6 单链表相关经典算法OJ题5:分割链表

题目描述:

思路:

代码 

小结


引言

之前我发布过单链表专题,内容很多,测试自己掌握没有的最好方法时做题,今天就带来了六道单链表经典算法题:

1.1 单链表相关经典算法OJ题1:移除链表元素

1.2 单链表相关经典算法OJ题2:反转链表

1.3 单链表相关经典算法OJ题3:合并两个有序链表

1.4 单链表相关经典算法OJ题4:链表的中间结点

1.5 循环链表经典应⽤-环形链表的约瑟夫问题

1.6 单链表相关经典算法OJ题5:分割链表

你们可以先去看看,如果还没学习过单链表的先去看单链表专题打下基础,话不多说,正片开始

链表经典算法OJ题⽬

1 单链表相关经典算法OJ题1:移除链表元素

题目描述:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

思路:

思路一:

使用三个指针遍历链表,将值为val的节点进行释放

 思路二:

创建一个新的链表,遍历原链表,将值不为val的节点尾插到新链表中

这里我使用了思路二,代码比较简明易懂

 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    //创建一个空链表
    ListNode*newHead,*newTail;
    newHead = newTail = NULL;

    //遍历原链表
    ListNode* pcur = head;
    while(pcur)
    {
        //找值不为val的节点,尾插到新链表中
        if(pcur->val != val)
        {
            //链表为空
            if(newHead == NULL)
            {
                newHead = newTail = pcur;
            }
            else
            {
                newTail->next = pcur;
                newTail = newTail->next; 
            }
        }
        pcur = pcur->next;
    }
    if(newTail)
    {
        newTail->next = NULL;
    }
    return newHead;
}

注意 :最后要将newTail下一个节点置为空 

2单链表相关经典算法OJ题2:反转链表

题目描述:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

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

示例 2:

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

示例 3:

输入:head = []
输出:[]

思路:

思路1:

创建新的链表,将原链表的节点进行头插到新的链表

思路2:

创建n1,n2,n2三个指针,完成链

代码 

 typedef struct ListNode ListNode;
struct ListNode* reverseList(struct 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;
        //n2没走完时,n3已经为空,要进行判空
        if(n3)
            n3 = n3->next;
    }
    return n1;
}

注意:n2没走完时,n3已经为空,要进行判空处理 

3单链表相关经典算法OJ题3:合并两个有序链表

题目描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

思路:

 我创建了一个新的链表和两个指针l1,l2,分别指向两个链表的头节点,遍历原链表进行比较val值得大小,将节点小得值直接尾插到新链表中,注意:遍历得结果有两种情况,无论哪一种情况,我都需要将剩余得节点尾插到新链表中.

代码 

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    // 判空
    if (list1 == NULL) {
        return list2;
    }
    if (list2 == NULL) {
        return list1;
    }

    ListNode* l1 = list1;
    ListNode* l2 = list2;

    ListNode *newHead, *newTail;
    // newHead = newTail = NULL;
    newHead = newTail = (ListNode*)malloc(sizeof(ListNode));

    while (l1 && l2)
    {
        if (l1->val < l2->val) {

            newTail->next = l1;
            newTail = newTail->next;
            l1 = l1->next;
        } else {
            newTail->next = l2;
            newTail = newTail->next;
            l2 = l2->next;
        }
    }
    // 跳出循环有两种情况,要么l1走空,要么l2走空
    if (l2) {
        newTail->next = l2;
    }
    if (l1) {
        newTail->next = l1;
    }
    // 动态申请的空间要手动释放
    ListNode* ret = newHead->next;
    free(newHead);
    newHead = NULL;
    return ret;
}

注意:这里我动态开辟了newHead和newTail,简称哨兵,这样就不需要进行初始化,到一定要记得释放我们开辟得空间,不然会造成空间浪费 

4 单链表相关经典算法OJ题4:链表的中间结点

题目描述:

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

示例 2:

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

思路:

 这里使用快慢指针,slow每次走一步,fast每次走两步,2slow=fast,那么我得slow就是我需要的中间节点

代码 

 typedef struct ListNode ListNode; 
struct ListNode* middleNode(struct ListNode* head) {
    //创建快慢指针
    ListNode*slow = head;
    ListNode*fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

5 循环链表经典应⽤-环形链表的约瑟夫问题

题目描述:

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

数据范围: 1≤𝑛,𝑚≤100001≤n,m≤10000

进阶:空间复杂度 𝑂(1)O(1),时间复杂度 𝑂(𝑛)O(n)

示例1

输入:

5,2     

复制返回值:

3    

复制说明:

开始5个人 1,2,3,4,5 ,从1开始报数,1->1,2->2编号为2的人离开
1,3,4,5,从3开始报数,3->1,4->2编号为4的人离开
1,3,5,从5开始报数,5->1,1->2编号为1的人离开
3,5,从3开始报数,3->1,5->2编号为5的人离开
最后留下人的编号是3      

示例2

输入:

1,1

复制返回值:

1

著名的Josephus问题 据说著名犹太 历史学家 Josephus有过以下的故事:在罗⻢⼈占领乔塔帕特后,39个犹太⼈与 Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被⼈抓到,于是决定了⼀个⾃杀 ⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀,然后再由下⼀ 个重新报数,直到所有⼈都⾃杀⾝亡为⽌。 然⽽Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在 第16个与第31个位置,于是逃过了这场死亡游戏。

思路:

先创建我的循环链表,当链表的头节点不等于尾节点时,说明还有人需要淘汰,这也就是循环的条件,用count进行计数,当count等于m时,进行删除节点的操作,其他情况count加加,不需要销毁节点,最后返回(首尾)节点的val值即可.

代码 

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */
 typedef struct ListNode ListNode;
 //创建节点
 ListNode*buyNode(int x)
 {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    if(node == NULL)
    {
        exit(1);
    }
    node->val = x;
    node->next = NULL;
    return node;
 }
 //创建带环链表
 ListNode*createCircle(int n)
 {
    //先创建第一个节点
    ListNode* phead = buyNode(1);
    ListNode* ptail = phead;
    for(int i = 2;i <= n; i++)
    {
        ptail->next = buyNode(i);
        ptail = ptail->next;
    }
    ptail->next = phead;

    return ptail;
 }
int ysf(int n, int m ) {
    //1.根据n创建带环链表
    ListNode*prev = createCircle(n);
    ListNode*pcur = prev->next;
    int count = 1;
    while(pcur->next != pcur)
    {
        if(count == m)
        {
            //销毁pcur节点
            prev->next = pcur->next;
            free(pcur);
            pcur = prev->next;
            count = 1;
        }
        else
        {
            //此时不需要销毁节点
            prev = pcur;
            pcur = pcur->next;
            count++;
        }
    }
    //此时剩下的一个节点就是要返回的节点里的值
    return pcur->val;
}

6 单链表相关经典算法OJ题5:分割链表

题目描述:

给你一个链表的头节点 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]

思路:

 创建大小链表,用大链表存储大于等于x的节点,反之存储在小链表中,最后将小链表的为节点和大链表的第一个有效节点首尾相连即可

代码 

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x){
    if(head == NULL)
    {
        return head;
    } 
    //创建两个带头链表
    ListNode* lessHead, *lessTail;
    ListNode* greaterHead, *greaterTail;
    lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
    greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
    
    //遍历原链表,将原链表中的节点尾插到大小链表中
    ListNode*pcur = head;
    while(pcur)
    {
        if(pcur->val < x)
        {
            //尾插到小链表
            lessTail->next = pcur;
            lessTail = lessTail->next;
        }
        else
        {
            //尾插到大链表
            greaterTail->next = pcur;
            greaterTail = greaterTail->next;
        }
        pcur = pcur->next;
    }
    //修改大链表的尾节点的next指针指向
    greaterTail->next = NULL;//
    //小链表的尾节点和大链表的第一个有效节点首尾相连
    lessTail->next = greaterHead->next;

    ListNode* ret = lessHead->next;
    free(lessHead);
    free(greaterHead);
    lessHead = greaterHead = NULL;
    return ret;
}

小结

一看就会,一做就废,反正是我目前的状态,如果你还对链表知识点有所疑问的话,赶紧看完知识点后刷起来吧,这里我提供两个刷题平台

牛客-------牛客网在线编程_算法篇_面试必刷TOP101 (nowcoder.com)

力扣-------力扣 (LeetCode) 全球极客挚爱的技术成长平台

好了今天到这里了,如果有疑问的话打在评论区,我如果看到的话,会第一时间回复的,当然想必还有很多大佬可以提供帮助,大家互帮互助一起学习~

后面我会更新循环链表的实现和时间复杂度与空间复杂度,想学习的家人们点赞关注不迷路!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值