
🌟作者介绍:友友们好我是钓鱼的猫猫,可以叫我小猫💕
⏳作者主页:钓鱼的猫猫-CSDN博客🎉
👀项目专栏:经典算法_钓鱼的猫猫的博客-CSDN博客
单链表算法需要提前知识
🎈 1. 从零拿捏链表(一)—— 无头单向不循环链表(单链表)-CSDN博客
🎉小猫和友友们一样喜欢代码🤭。很荣幸能向大家分享我的所学所感,和大家一起进步,成为合格的卷王。✨如果文章有错误,欢迎在评论区✏️指正。那么开始今天的学习吧!😘
目录
一.移除链表元素 (双指针)
题目连接:
1.1 题目描述
1.2 方法思路
1. 检查空链表:如果头节点head为空,直接返回head。
2. 创建虚拟头节点:创建一个新的链表,Newhead作为虚拟头节点,其值为-1,next指向NULL。Newtail用于跟踪新链表的尾部。
3. 处理单节点链表:如果原链表只有一个节点,检查该节点的值是否等于val。如果等于,返回Newhead->next(即NULL);否则,返回原链表head。
4. 遍历原链表:对于每个节点,如果节点的值等于val,则跳过该节点;否则,创建一个新节点,复制原节点的值,并将其插入到新链表的尾部。
5. 返回新链表:最后返回Newhead->next,即新链表的头节点。
1.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)
{
if(head == NULL)
return head;
ListNode* pcur = head;
ListNode* Newhead = (ListNode*)malloc(sizeof(ListNode));
Newhead->next = NULL;
Newhead->val = -1;
ListNode* Newtail = Newhead;
if(pcur->next == NULL)
{
if(pcur->val == val)
{
return Newhead->next;
}
else
{
//尾插
return head;
}
}
while(pcur != NULL)
{
if(pcur->val == val)
{
pcur = pcur->next;
}
else
{
//尾插
ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
NewNode->next = NULL;
NewNode->val = pcur->val;
Newtail->next = NewNode;
pcur = pcur->next;
Newtail = NewNode;
}
}
return Newhead->next;
}
二.反转链表(三指针)
题目链接:
2.1 题目描述
2.2 方法思路
1. 如果链表为空或只有一个节点,直接返回头节点。
2. 初始化三个指针: n1指向NULL,n2指向头节点head,n3指向头节点的下一个节点。
3. 遍历链表,每次迭代中:
- 将n2的next指向n1(反转指针)。
- 将n1移动到n2的位置。
- 将n2移动到n3的位置。
- 如果n3不为NULL,则将n3移动到下一个节点。
4. 当n2为NULL时,遍历结束,此时n1指向原链表的最后一个节点,即反转后的头节点。
2.3 代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head)
{
if(head == NULL)
return head;
if(head->next == NULL)
return head;
ListNode* n1 = NULL;
ListNode* n2 = head;
ListNode* n3 = head->next;
while(n2 != NULL)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3!= NULL)
n3 = n3->next;
}
return n1;
}
三.合并两个有序链表(双指针)
题目链接:
3.1 题目描述
3.2 解题思路
1. 特殊情况处理
首先检查两个链表是否为空:
如果
list1为空,直接返回list2,因为无需合并。如果
list2为空,直接返回list1,同样无需合并。这确保了当其中一个链表为空时,能快速返回另一个链表,提高效率。
2. 创建虚拟头节点
创建一个虚拟头节点
newhead,其值为-1,next指针初始化为NULL。虚拟头节点的作用是为了简化链表操作,避免处理头节点的特殊情况(如头节点可能被改变)。使用一个尾指针
newtail来跟踪新链表的当前尾部,初始时指向newhead。这样可以在O(1)时间内完成尾部插入。3. 比较并合并节点
使用两个指针
l1和l2分别遍历list1和list2。进入循环,比较
l1和l2当前节点的值:
如果
l1->val >= l2->val,则将l2的节点连接到新链表的尾部(newtail->next = l2),并移动l2和newtail到下一个节点。如果
l1->val < l2->val,则将l1的节点连接到新链表的尾部(newtail->next = l1),并移动l1和newtail到下一个节点。这个过程持续直到其中一个链表被完全遍历(即
l1或l2变为NULL)。4. 处理剩余节点
当其中一个链表遍历完后,另一个链表可能还有剩余节点(因为链表长度可能不同)。
检查哪个链表还有剩余节点:
如果
l1为NULL,说明list1已遍历完,将list2的剩余部分(l2)直接连接到新链表的尾部。如果
l2为NULL,说明list2已遍历完,将list1的剩余部分(l1)直接连接到新链表的尾部。这确保了所有节点都被包含在新链表中,且保持升序。
5. 返回结果
返回
newhead->next,因为newhead是虚拟节点,其next指针指向合并后链表的真正头节点。
3.3 代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1 == NULL)
{
return list2;
}else if(list2 == NULL)
{
return list1;
}
ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
newhead->next = NULL;
newhead->val = -1;
ListNode * newtail = newhead;
ListNode* l1 = list1;
ListNode* l2 = list2;
while(l1 && l2)
{
if(l1->val >= l2->val)
{
newtail->next = l2;
newtail = newtail->next;
l2 = l2->next;
}else if(l1->val < l2->val)
{
newtail->next = l1;
newtail = newtail->next;
l1 = l1->next;
}
}
if(l1 == NULL)
{
while(l2 != NULL)
{
newtail->next = l2;
newtail = newtail->next;
l2 = l2->next;
}
}else if(l2 == NULL)
{
while(l1 != NULL)
{
newtail->next = l1;
newtail = newtail->next;
l1 = l1->next;
}
}
return newhead->next;
}
四.链表的中间结点(快慢指针)
题目链接:
4.1 题目描述
4.2 解题思路
使用了快慢指针法来寻找链表的中间节点。以下是详细的解题思路分析:
1. 快慢指针法
定义两个指针
fast和slow,初始时都指向链表的头节点head。
fast指针每次移动两步,slow指针每次移动一步。当
fast指针到达链表末尾时,slow指针恰好指向链表的中间节点。2. 特殊情况处理
如果链表只有一个节点(即
fast->next == NULL),直接返回头节点head,因为中间节点就是头节点本身。3. 循环遍历
使用
while循环,条件为fast->next != NULL,确保fast指针可以移动。在循环体内:
首先移动
slow指针到下一个节点(slow = slow->next)。然后检查
fast指针是否可以移动两步(即检查fast、fast->next和fast->next->next是否存在)。
如果条件满足,则移动
fast指针两步(fast = fast->next->next)。如果条件不满足(即
fast->next->next不存在),说明fast指针无法再移动两步,此时直接返回slow指针,因为slow已经指向中间节点。4. 返回结果
循环结束后,返回
slow指针,即链表的中间节点。为什么能返回第二个中间节点?
当链表长度为偶数时,有两个中间节点。快指针每次移动两步,慢指针每次移动一步,当快指针无法再移动两步时,慢指针恰好指向第二个中间节点。例如,链表
[1,2,3,4,5,6],慢指针会指向节点4(第二个中间节点)。
4.3 代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head)
{
ListNode* fast = head;
ListNode* slow = head;
if(fast->next == NULL)
return head;
while(fast->next != NULL)
{
slow = slow->next;
if(fast && fast->next && fast->next->next)
fast = fast->next->next;
else
return slow;
}
return slow;
}
五.约瑟夫问题(双指针)
题目链接:
5.1 题目描述
5.2 解题思路
通过模拟约瑟夫问题的过程来求解,具体思路如下:
1. 特殊情况处理
如果只有一个人(
n == 1),那么直接返回编号1,因为不需要进行任何淘汰过程。2. 构建环形链表
创建一个单向环形链表,其中每个节点代表一个人,节点的值从1到n依次赋值。
使用
head指针指向链表的头节点(编号1),tail指针指向链表的尾节点(编号n),并通过tail->next = head将链表首尾相连,形成环。3. 初始化指针和计数器
使用两个指针
prev和pcur来遍历链表:
pcur指向当前报数的人。
prev指向pcur的前一个节点,以便于删除节点。初始化报数计数器
sum为1(从1开始报数)。4. 模拟报数和淘汰过程
循环条件:当链表中不止一个节点时(即
pcur->next != pcur),继续循环。内部循环:移动
prev和pcur指针,同时递增sum,直到sum等于m。这意味着当前pcur指向的人报数到了m,需要被淘汰。淘汰节点:将
prev->next指向pcur->next,从而跳过pcur节点(即删除该节点),然后释放pcur节点(代码中未显式释放,但实际应避免内存泄漏)。之后,将pcur移动到下一个节点,并重置sum为1,从下一个人重新开始报数。5. 返回结果
当循环结束时,
pcur指向链表中最后一个剩余的节点,返回其值即为最后留下的人的编号。
5.3 代码实现
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @param m int整型
* @return int整型
*/
#include <stdlib.h>
typedef struct ListNode ListNode;
//创建节点
ListNode* ListNode_Apply_Node(int x)
{
ListNode* newnode = (ListNode *)malloc(sizeof(ListNode));
newnode->next = NULL;
newnode->val = x;
return newnode;
}
int ysf(int n, int m )
{
if(n == 1)
return 1;
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
head->val = 1;
head->next = NULL;
ListNode* tail = head;
//创建环形链表
for(int i = 2;i<=n;i++)
{
ListNode* newnode = ListNode_Apply_Node(i);
tail->next = newnode;
tail = tail->next;
}
tail->next = head;
ListNode* prev = tail;
ListNode* pcur = head;
int sum = 1;
//开始游戏
while(pcur->next != pcur)
{
while(sum != m)
{
prev = prev->next;
pcur = pcur->next;
sum++;
}
prev->next = pcur->next;
pcur = pcur->next;
sum = 1;
}
return pcur->val;
}
六.分割链表
题目链接:
面试题 02.04. 分割链表 - 力扣(LeetCode)
6.1 题目描述
6.2 解题思路
尝试解决分割链表的问题,即将所有小于给定值
x的节点移动到所有大于或等于x的节点之前,且不要求保持原始相对顺序。以下是代码的解题思路分析:1. 基本思路
代码旨在通过遍历原链表,将节点根据其值与
x的比较结果重新排列:小于x的节点采用头插法插入新链表的头部,大于或等于x的节点采用尾插法插入新链表的尾部。这样,所有小于
x的节点都会聚集在链表头部,而大于等于x的节点则集中在尾部。由于头插法,小于x的节点的顺序会被反转,但题目不要求保持原始相对顺序,因此这是可接受的。2. 具体步骤
初始化:处理空链表的情况,直接返回
NULL。创建新链表:代码首先创建了一个新节点
phead,其值设置为原链表头节点的值。这实际上复制了原头节点,而不是直接使用原节点。然后设置尾指针ptail指向phead,用于尾插操作。遍历原链表:从原链表的第二个节点开始遍历(因为头节点已处理)。使用
prev和pcur指针来跟踪当前节点和前一个节点。节点处理:
如果当前节点值大于等于
x,则进行尾插:将当前节点链接到新链表的尾部,并更新ptail。如果当前节点值小于
x,则进行头插:将当前节点插入到新链表的头部,并更新phead指向新的头节点。结束处理:遍历结束后,将尾节点的
next设置为NULL,以防止链表成环,然后返回新链表的头节点phead。
6.3 代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
if(head == NULL)
return NULL;
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
phead->val = head->val;
phead->next = NULL;
ListNode* prev = head;
ListNode* pcur = head->next;
ListNode* ptail = phead;
while(pcur != NULL)
{
if(pcur->val >= x)
{
//尾插
ptail->next = pcur;
ptail = ptail->next;
prev = pcur;
pcur = pcur->next;
}else if(pcur->val < x)
{
//头插
prev = pcur;
pcur = pcur->next;
prev->next = phead;
phead = prev;
}
}
ptail->next = NULL;
return phead;
}
七.最后😘
小猫尽力了,请见谅!😘
那么今天的学习就到这里了。友友们觉得不错的可以给个关注,点赞或者收藏哦!😘感谢各位友友们的支持。希望各位大佬们自行检验哦,毕竟亲手操作让记忆更加深刻。
你的❤️点赞是我创作的动力的源泉
你的✨收藏是我奋斗的方向
你的🙌关注是对我最大的支持
你的✏️评论是我前进的明灯
创作不易,希望友友们支持一下小猫吧😘






324

被折叠的 条评论
为什么被折叠?



