关键内容:链表基础;链表常见操作,虚拟头节点
常见链表大致分为:单链表,双链表,循环链表
关于链表的创建(可以用多个构造函数来初始化)
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
ListNode() :val(0),next(NULL){}
ListNode(ListNode* p): val(0),next(p) {}
};
链表最基本的操作是删除节点和添加节点
数组与链表的性能比较
206.反转链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL) //开始时忘记考虑空链表!!!
{
return NULL;
}
if(head->next==NULL)
{
return head;
}
ListNode* pre=head;
ListNode* tmp=head->next;
ListNode* aft=tmp->next;
if(head->next->next==NULL)
{
tmp->next=head;
head->next=NULL;
return tmp;
}
while(tmp!=NULL)
{
tmp->next=pre;
pre=tmp;
tmp=aft;
if(aft!=NULL) //开始忘记考虑最后一步aft为NULL,导致了内存错误(NULL没有next)
aft=aft->next; //这警示着笔者,对待边界等特殊的点要考虑清楚,周全
}
head->next=NULL;
return pre;
}
};
203.移除元素
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dp=new ListNode(0,head); //此处为设置虚拟头节点
ListNode* cur=new ListNode;
cur=dp; //创建dp,不是为了移动,dp可以理解为固定在那里,为了方便头节点操作一致性
ListNode* tmp=head; //而cur的创建才是为了移动,tmp也是,笔者开始将dp在那里动,错误。
while(tmp!=NULL)
{
if(tmp->val==val)
{
cur->next=tmp->next;
tmp->next=NULL;
tmp=cur->next;
continue;
}
cur=tmp;
tmp=tmp->next;
}
head=dp->next; //此一行代码极度容易忽略!!!
delete dp;
return head;
}
};
总结:
操作链表时,可用原链表直接操作,也可设置虚拟头节点再操作,主要是为了方便头节点操作的一致性;
要清楚在那移动的是什么指针,相对不动的是什么(虚拟头显然是后者);
指针处理要当心,防止NULL->next之类的隐含错误;
一般链表构造时建议使用构造函数为next赋NULL,若不如此,日后用new创建时,对next的操作若遗忘则会出错(但实际上直接写point->next=是可以的,只是不能改变指向的具体值)。