目录
前言
照旧案例卡哥的代码随想录,链表的理论基础这一章节写的非常详细。
一、什么是链表
百度:单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
链表的种类有很多:单向链表,双向链表、循环链表等。
链表相对于数组的好处:
可以随时进行扩大和缩小,而数组只能在定义的时候规定大小。
链表的增删比较方便,对于数组会改变数组的结构(比如删除头元素,就得将2-N个元素往前移)。
二、链表的使用
1、移除链表元素(虚拟头节点)
1.1、问题描述
给定一个target值,然后在链表中寻找并且删除。
1.2、解题思路
1、先创建一个节点作为头节点连接到已有的链表,因为删除链表需要用到上一个链表的节点,使用虚拟头节点作为头节点就使原有的所有节点更方便的进行删除节点操作。
2、遍历链表,判断是否与target值相等,相等的话进行删除(使上一个节点的next指向本节点的next即为删除这个节点)。
1.3、代码实现
ListNode* removeElements(ListNode* head, int val) {
//创建虚拟头节点
ListNode *dummy_ptr = new ListNode;
//创建临时遍历链表
ListNode *tmp_ptr = new ListNode;
dummy_ptr->next = head;
tmp_ptr = dummy_ptr;
//循环遍历链表
while(tmp_ptr->next != nullptr)
{
//判断值是否相等
if(tmp_ptr->next->val == val)
{
//删除链表
ListNode *free_ptr = tmp_ptr->next;
tmp_ptr->next = tmp_ptr->next->next;
delete free_ptr;
}
else
{
//遍历下一个节点
tmp_ptr = tmp_ptr->next;
}
}
return dummy_ptr->next;
}
2、翻转链表(双指针)
2.1、问题描述
将给定的一个链表进行翻转。
2.2、解题思路
1、创建两个链表(一个指向要翻转的链表,一个指向空)。
2、遍历要翻转的链表,将每个节点头插到新链表中。
2.3、代码实现
ListNode* reverse(ListNode* cur,ListNode* pre)
{
//旧链表遍历到尾部,返回
if(cur == nullptr) return pre;
//暂时保存旧链表当前节点的下一个节点,防止后续操作使其丢失
ListNode *temp = cur->next;
//头插入新链表
cur->next = pre;
pre = cur;
//递归实现遍历
return reverse(temp,pre);
}
ListNode* reverseList(ListNode* head) {
ListNode *cur = head;
ListNode *pre = nullptr;
//翻转链表
return reverse(cur,pre);
}
3、删除链表的倒数第N个节点(双指针)
3.1、问题描述
删除给定链表中的倒数第N个节点。
3.2、解题思路
1、使用虚拟头节点,方便元素的删除操作。
2、使用双指针(fast,slow),然后先让fast循环N次,再让fast和slow一起循环到fast结束,slow因为少循环了N次所以最后到达的点就是要删除的那个节点。最后再slow的位置上进行删除一个节点然后返回即可。
3.3、代码实现
ListNode* removeNthFromEnd(ListNode* head, int n) {
//创建虚拟头节点
ListNode *dummy_head = new ListNode(0);
dummy_head->next = head;
//fast指针
ListNode *temp1 = dummy_head;
//slow指针
ListNode *temp2 = dummy_head;
//先让fast走N次
while(n-- && temp1!=nullptr)
{
temp1 = temp1->next;
}
//fast和slow一起走,直到fast到结尾
while(temp1->next != nullptr)
{
temp1 = temp1->next;
temp2 = temp2->next;
}
ListNode *temp = temp2->next;
//删除当前节点
temp2->next = temp2->next->next;
delete temp;
return dummy_head->next;
}
4、两两交换链表中节点
4.1、问题描述
将给定的链表中每两个节点进行互换。
4.2、解题思路
1、使用虚拟头节点,便于头节点的互换。
2、两两分组(也就是每次循环都间隔2),在交换前记得要先保存本组中的首节点(便于本组交换),下一组中的首节点(便于连接,遍历),上一组中的尾节点(便于本组与上一组连接)。其余就是对本组两个连接的互相指向了。
4.3、代码实现
ListNode* swapPairs(ListNode* head) {
//创建虚拟头节点
ListNode *dummy_head = new ListNode(0);
dummy_head->next = head;
ListNode *cur = dummy_head;
//循环遍历,条件是后面两个节点都不为空(cur为上一组的尾节点)
while((cur->next!=nullptr) && (cur->next->next!=nullptr))
{
//保留本组的首节点
ListNode *preNode = cur->next;
//保留下一组的头节点
ListNode *nextPreNode = preNode->next->next;
//与上一组相连
cur->next = preNode->next;
//交换节点
preNode->next->next = preNode;
preNode->next = nextPreNode;
//遍历下一组
cur = cur->next->next;
}
return dummy_head->next;
}
5、寻找环形链表的入口(双链表)
5.1、问题描述
寻找给定列表是否使环形链表,并且判断环形链表的入口。
5.2、解题思路
1、使用两个链表(fast,slow),fast每次遍历两个节点,slow每次遍历一个节点,因为有环形的存在所以fast和slow一定会相遇!
2、在遍历过程中如果遍历到了空说明这不是一个环形链表。
3、假设入口之前有X个节点,fast和slow在环中的Y个节点相遇,环剩下的节点为Z。
fast走了:X+Z+n(Y+Z) slow走了:X+Z n表示fast在环中循环了n次。
两者相等之后可列出公式:X =(n-1)(Y+Z)+Y
假设将n当作1,最后就是X=Y。所以当两者相遇之后,使用一个新指针从头节点出发,slow节点从相遇点同时出发,必定会在环形入口相遇。
5.3、代码实现
ListNode *detectCycle(ListNode *head) {
//快指针
ListNode *fast = head;
//慢指针
ListNode *slow = head;
//先寻找相遇点,当有节点循环到null时就说明不是环形链表
while(fast!=nullptr && fast->next!=nullptr)
{
fast = fast->next->next;
slow = slow->next;
//两指针相遇
if(slow == fast)
{
//新指针从头节点触发
ListNode *temp = head;
//没有相遇就一直向下走
while(slow!=temp)
{
slow = slow->next;
temp = temp->next;
}
//返回入口
return temp;
}
}
return nullptr;
}
总结
这些只是本人看完代码随想录视频的一些总结,一些关于链表的删除,头插,尾插我也没有讲。如果需要进一步的提升当然是要去力扣上刷更多关于链表的题目。