哈喽,小伙伴们,上一次我们学习了单链表的知识,这次我们就要运用学到的知识来做一些相关的题目。我们都知道,要学好数据结构与算法,一定要多刷相关的题目才能有所提高。所以我们一起来学习一些单链表的经典题目算法题。
1.移除元素
题目简介:
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
思路分析:
思路1:遍历原链表,遇到val就执行删除val节点的操作
思路2:定义新链表,遍历原链表找不为val的节点,尾插在新链表中。而尾插分为2种情况
链表为空:插入进来的节点就是链表的头节点和尾节点。
链表不为空:插入进来的节点就是链表的新的尾节点
代码实现:
/**
* 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
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路分析:
思路1:统计链表中节点的个数,通过除2找到中间节点。
思路2:快慢指针法。
代码实现:
/**
* 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时为假,就是“短路”,不会判断第二个条件。
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
3. 反转链表
题目描述:给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
思路分析:大多数人的想法应该是先找尾节点,从尾节点向前一步一步改next,我也是这么想的,但是题目描述的是单链表,依次往后,所以这种方法不可行。
思路一:创建新链表,遍历原链表的节点将其插入到新的链表中。(头插)
思路二:创建3个指针。分别记录前驱节点,当前节点,后继节点,改变原链表指针指向。n1指向NULL,n2指向第一个节点,n3指向下一个节点。方法是先判断n2是否为空,不为空,让n2节点的next指向n1,然后让n1=n2,n2=n3,n3=n3->next,就这样依次循环,直到n2为空,n2为空时,n3也为空,当赋值的时候n3不能继续往下走了。
代码实现:
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.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
思路分析:创建一个新链表,定义newHead和newTail。在定义l1和l2分别指向2个链表的头,对l1和l2进行比较,谁小就尾插在新链表的尾部,然后l1或者l2往后走,并且newTail指向尾节点,循环往复。只要有一个链表的尾节点指向NULL,就跳出循环,然后另一个链表的节点就尾插在新链表中。
如果一个链表的节点数多于另一个链表,那么跳出循环时不指向空的链表不用继续写一个循环来进行尾插,因为链表的节点都是一个一个链接在一起,只需尾插“第一个”节点就可以,后面的节点可以跟在后面。
代码实现:
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;
}
根据代码可以看出有许多代码是重复出现的,那么想一想是不是可以对代码进行优化?答案是可以的 。解决方法就是通过哨兵位来解决,即就是带头的单链表
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. 环形链表的约瑟夫问题
小故事:
编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。下一个人继续从 1 开始报数。n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
思路分析:根据n来创建不带头单向循环链表,逢m删除当前节点。可以画一个图根据代码自己演算一遍。
代码实现
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @param m int整型
* @return int整型
*/
#include <stdlib.h>
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
的节点之前。
你不需要 保留 每个分区中各节点的初始相对位置。
思路分析:
思路1:定义2个新的链表,一个放小于x的,一个放大于等于x的,最后将2个链表连接起来。
思路2:定义两个链表,大链表和小链表,遍历原链表的节点将其放到对应的新链表中,最后将大链表和小链表的首尾相连。如图所示
代码实现
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)官网 - 全球极客挚爱的技术成长平台
好了,小伙伴们,如果看不懂的话,一定要动手自己画图来进行理解,通过图形来帮助记忆,另外题目的解法不只这一种,有其他解法的话可以分享出来,一起进行学习。 感谢大家的观看。