单链表的经典题目练习

单链表的经典题目练习_第1张图片

哈喽,小伙伴们,上一次我们学习了单链表的知识,这次我们就要运用学到的知识来做一些相关的题目。我们都知道,要学好数据结构与算法,一定要多刷相关的题目才能有所提高。所以我们一起来学习一些单链表的经典题目算法题。

1.移除元素

题目简介:

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

单链表的经典题目练习_第2张图片

思路分析:

思路1:遍历原链表,遇到val就执行删除val节点的操作

思路2:定义新链表,遍历原链表找不为val的节点,尾插在新链表中。而尾插分为2种情况

链表为空:插入进来的节点就是链表的头节点和尾节点。

链表不为空:插入进来的节点就是链表的新的尾节点

单链表的经典题目练习_第3张图片

代码实现:

/**
 * 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;
}

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

2.链表的中间节点

题目描述 :

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

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

单链表的经典题目练习_第4张图片

思路分析:

思路1:统计链表中节点的个数,通过除2找到中间节点。

思路2:快慢指针法。

单链表的经典题目练习_第5张图片

代码实现:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) 
{
    ListNode *slow,*fast;
    slow = fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return  slow;
}

 注意,如果是下面这种写可能会运行失败。就是把循环的2个条件交换位置,当是这种写法的话如果节点数为奇数没有问题,而当节点数是偶数时,因为fast指针已经指向NULL,所以fast->next就会错误。当是原顺序的时候,遇到偶数节点,fast为NULL时为假,就是“短路”,不会判断第二个条件。

单链表的经典题目练习_第6张图片

 题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

3. 反转链表

题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

单链表的经典题目练习_第7张图片

单链表的经典题目练习_第8张图片

 思路分析:大多数人的想法应该是先找尾节点,从尾节点向前一步一步改next,我也是这么想的,但是题目描述的是单链表,依次往后,所以这种方法不可行。

思路一:创建新链表,遍历原链表的节点将其插入到新的链表中。(头插)

思路二:创建3个指针。分别记录前驱节点,当前节点,后继节点,改变原链表指针指向。n1指向NULL,n2指向第一个节点,n3指向下一个节点。方法是先判断n2是否为空,不为空,让n2节点的next指向n1,然后让n1=n2,n2=n3,n3=n3->next,就这样依次循环,直到n2为空,n2为空时,n3也为空,当赋值的时候n3不能继续往下走了。

单链表的经典题目练习_第9张图片

代码实现:

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{
    //处理空链表
    if(head==NULL)
    {
        return head;
    }
    ListNode *n1,*n2,*n3;
    n1 = NULL, n2 = head, n3 = head->next;

    while(n2 != NULL)
    {
        //修改n2的指向
        n2->next = n1;
        //修改指针位置
        n1 = n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }   
    }
    return n1;
}

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 

 4.合并两个有序链表

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

单链表的经典题目练习_第10张图片

思路分析:创建一个新链表,定义newHead和newTail。在定义l1和l2分别指向2个链表的头,对l1和l2进行比较,谁小就尾插在新链表的尾部,然后l1或者l2往后走,并且newTail指向尾节点,循环往复。只要有一个链表的尾节点指向NULL,就跳出循环,然后另一个链表的节点就尾插在新链表中。

单链表的经典题目练习_第11张图片

 如果一个链表的节点数多于另一个链表,那么跳出循环时不指向空的链表不用继续写一个循环来进行尾插,因为链表的节点都是一个一个链接在一起,只需尾插“第一个”节点就可以,后面的节点可以跟在后面。

代码实现:

typedef struct ListNode ListNode;
//list1和list2是2个链表的头节点
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }
    ListNode *l1,*l2;
    l1 = list1;
    l2 = list2;

    ListNode *newHead,*newTail;//创建新链表
    newHead = newTail =NULL;

    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            if(newHead==NULL)
            {
                //链表为空,头尾指针都指向该节点
                newHead = newTail =l1;
            }
            else
            {
                //链表不为空
                newTail->next = l1;
                newTail = newTail->next;
            }
            l1 = l1->next;
        }
        else
        {
            if(newHead==NULL)
            {
                //链表为空
                newHead = newTail =l2;
            }
            else
            {
                //链表不为空
                newTail->next = l2;
                newTail = newTail->next;
            }
            l2 = l2->next;
        }
    }
    //跳出循环存在2种情况:要么l1走到空了,l2不为空,或者相反
    //不可能存在都为空的情况
    if(l1)
    {
        newTail->next = l1;
        //newTail = newTail->next;//可写可不写
    }
    else
    {
        newTail->next = l2;
        //newTail = newTail->next;
    }
    return newHead;
}

根据代码可以看出有许多代码是重复出现的,那么想一想是不是可以对代码进行优化?答案是可以的 。解决方法就是通过哨兵位来解决,即就是带头的单链表

单链表的经典题目练习_第12张图片

typedef struct ListNode ListNode;
//list1和list2是2个链表的头节点
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }
    ListNode *l1,*l2;
    l1 = list1;
    l2 = list2;

    ListNode *newHead,*newTail;//创建新链表
    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;
        }
    }
    //跳出循环存在2种情况:要么l1走到空了,l2不为空,或者相反
    //不可能存在都为空的情况
    if(l1)
    {
        newTail->next = l1;
        //newTail = newTail->next;//可写可不写
    }
   if(l2)
    {
        newTail->next = l2;
        //newTail = newTail->next;
    }
    //malloc了空间,但是这块空间用不上,应该释放掉
    ListNode *ret = newHead->next;
    free(newHead);
    return ret;
}

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

5. 环形链表的约瑟夫问题

小故事:

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

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。下一个人继续从 1 开始报数。n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

单链表的经典题目练习_第13张图片

思路分析:根据n来创建不带头单向循环链表,逢m删除当前节点。可以画一个图根据代码自己演算一遍。

代码实现

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */

#include 
typedef struct ListNode ListNode ;
ListNode* BuyNode(int x)
{
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    if(newNode == NULL)
    {
        exit(1);
    }
    newNode->val = x;
    newNode->next = NULL;
    return newNode;
}

ListNode* createList(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 phead;
}

int ysf(int n, int m ) 
{
    // write code here
    ListNode* head = createList(n);
    ListNode* pcur = head;
    ListNode* prev = NULL;

    int count = 1;
    while(pcur->next != pcur)
    {
        if(count == m)
        {
            //删除当前节点pcur
            prev->next = pcur->next;
            free(pcur);
            //删除pcur节点之后,要让pcur走到新的位置,count置为初始值
            pcur = prev->next;
            count = 1;
        }
        else 
        {
            //pcur往后走
            prev = pcur;
            pcur = pcur->next;
            count++;
        }
    }
    return pcur->val;
}

也有人会想当m为1的时候,代码就会出现问题,就是当m=1时,第一次进入循环就要删除pcur,而此时prev还是为空。解决办法就是让createlist不返回头节点,而是返回尾节点。既然知道第一个节点的前驱节点,也能够通过尾节点找到第一个节点。需要改以下代码。

6.分割链表

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

你不需要 保留 每个分区中各节点的初始相对位置。

单链表的经典题目练习_第14张图片

思路分析:

思路1:定义2个新的链表,一个放小于x的,一个放大于等于x的,最后将2个链表连接起来。

思路2:定义两个链表,大链表和小链表,遍历原链表的节点将其放到对应的新链表中,最后将大链表和小链表的首尾相连。如图所示

单链表的经典题目练习_第15张图片 代码实现

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;
    }
    greaterTail->next = NULL;

    //大小链表进行相连
    lessTail->next = greaterHead->next;
    ListNode *ret = lessHead->next;
    free(greaterHead);
    free(lessHead);
    return ret;
}

题目连接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

好了,小伙伴们,如果看不懂的话,一定要动手自己画图来进行理解,通过图形来帮助记忆,另外题目的解法不只这一种,有其他解法的话可以分享出来,一起进行学习。 感谢大家的观看。

你可能感兴趣的:(算法,数据结构,leetcode)