目录
1.链表的定义
// 单链表
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr){} // 节点的构造函数(不写也会默认生成)
};
2.移除链表元素
一般分为两步:
(1)改变欲移除节点的前一个节点的指针(2)释放掉移除节点的内存空间
思路1:根据节点种类的不同我们可以将节点分为头节点和一般节点,一般节点的查找方式是通过前一个节点的指针来查找,而头结点没有前一个节点,所以此方法需要特殊考虑移除头结点的情况
思路2:思考更一般化的方式,建立哨位结点作为链表头结点的前驱节点,这样无论是头结点还是一般节点,每次查找特定值val时都是通过前一个节点的指针
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *t=new ListNode(); // 使用构造函数建立哨位结点
t->next=head; // 将哨位结点与链表连接起来
ListNode *cur=t;
while(cur->next!=NULL)
{
if(cur->next->val==val) // 查找数据域为特定值val的前一个节点
{
ListNode *tmp=cur->next; // 临时变量tmp保存移除的节点
cur->next=cur->next->next;// 改变前一个节点的指针域
delete tmp; // 释放移除节点的内存空间
}
else cur=cur->next;
}
return t->next; // t是我们的哨位结点,链表真正的头节点是t->next
}
};
3.反转链表
思考:不能申请额外的内存空间,反转链表,则只需改变整个链表所有的指针方向即可
双指针法:
最终状态示意图
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *tmp,*cur=head,*pre=NULL; // 初始化cur指针为头节点,pre指针指向空
while(cur) // 以cur指针遍历整个链表
{
tmp=cur->next; // 定义临时变量tmp来保存cur下一个节点
cur->next=pre; // 反转链表指针,将当前节点的指针指向前一个节点
pre=cur; // 以cur指针来更新pre指针
cur=tmp; // 更新cur指针指向最初cur指向的下一个节点
}
return pre;
}
};
4.删除倒数第n个节点
思考:
(1)考虑到删除操作,我们上面发现使用哨位结点的方法更简洁
(2)思考如何定位到倒数第n个节点?
(3)采用双指针法
双指针法的思考:
第一步:快指针先走n步
第二步:快慢指针同时前进,直到快指针走到链表的尽头即指向空时结束
第三步:由于最开始时快慢指针相差n步,当快指针走到空时,此时慢指针的指向刚好是倒数第n个节点
第四步:考虑到删除操作应该对删除节点的前一个节点的指针域进行操作,所以需将慢指针指向倒数第n个节点的前一个节点,所以快指针应该比慢指针再多走一步
解法:
(1)定义哨位结点,对快慢指针进行初始化
(2)快指针先走n+1步
(3)快慢指针同时移动,直至快指针指向空
(4)更改慢指针的指针域,释放慢指针下一个节点的内存空间
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *h=new ListNode(0); // 建立哨位结点
h->next=head; // 哨位结点连接链表
ListNode *fast=h;
ListNode *slow=h;
n++; // 快指针先走n+1步
while(n--&&fast!=NULL)
{fast=fast->next;} // 若让快指针先走n步,再走一步的话,若n值>链表长度时,会出现对空指针的操作,执行会出错
while(fast!=NULL)
{
fast=fast->next;
slow=slow->next;
}
slow->next=slow->next->next;
return h->next;
}
};
5.环形链表
思考:
(1)判断链表是否有环
双指针法:定义快慢指针,令快指针一次走两步,慢指针一次走一步,如此进行下去快指针在环内以相对慢指针为一步的速度追赶慢指针,如若有环存在,则快慢指针终会相遇
(2)如何寻找环的入口
快慢指针相遇状态示意图
思考:为什么慢指针走的路程是x+y而不是x+y+k(y+z)?
因为当快指针与慢指针相遇时,是快指针在环内领先了慢指针一圈,而慢指针的速度是快指针的一半,所以慢指针此时只走了半圈,也就意味着慢指针进入环的第一圈就会被快指针追上,所以慢指针的路程为x+y
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast=head;
ListNode *slow=head;
while(fast!=NULL&&fast->next!=NULL) // 注意:&&具有短路取值的特点,当前一个式子的值足以判断整个式子的值时不会再判断后面的式子
{ //错误写法:while(fast->next!=NULL&&fast!=NULL)此时如果fast是一个空指针,对fast->next的操作实际上是对空指针的操作,执行出错
fast=fast->next->next; // 快指针每次走两步
slow=slow->next; // 慢指针每次走一步
if(fast==slow)
{
ListNode *indexhead=head; // 快慢指针相遇后快指针和头节点相遇处即为环的入口
ListNode *indexfast=fast;
while(indexhead!=indexfast)
{
indexhead=indexhead->next;
indexfast=indexfast->next;
}
return indexhead;
}
}
return NULL;
}
};
6.总结
艰难的第二次,磕磕绊绊的算是坚持了下来,由于对c++的不熟练在做题的过程中遇到了好几次的bug,所以也提醒我在学算法的同时也要推进基本的c++语法学习,双指针法的应用很重要,最近很忙事情很多,希望下次的更新不会太久!