链表分为单链表、双链表、循环链表。
不同于数组,在内存中连续分布,链表的节点是不连续分布的,通过指针串联在一起。
需要熟练掌握默写,虽然在LeetCode模式下ListNode一般是写好的,但是在acm模式下大概率需要自己写。
struct ListNode{
int val;
ListNode* next;
ListNode() : val(0), next(NULL) {}
ListNode(int x) : val(x), next(NULL) {}
ListNode(int x, ListNode* node) : val(x), next(node) {}
};
如果不写这个构造函数的话,C++会自动有一个构造函数,但是就不能在新建节点的时候直接传参进去了,不是很方便。
注意:操作链表很多时候都需要着眼于其前置节点;
所以通常会设置一个dummyHead(虚假头结点)。
下图删除D节点。
虽然操作上修改C的next指向就行,但是C++没有内存回收机制,所以最好还是删除D。
这就需要保留一个指向D的指针,然后:
delete D;
注意:虽然链表的删除和添加本身都是O(1),但是找到对应需要删除的节点、找到插入节点的位置,是O(n)的。
参考文章:关于链表,你该了解这些!
LeetCode链接:https://leetcode.cn/problems/remove-linked-list-elements/description/
如果头结点本身也要被删除,可能会比较麻烦。
所以为了统一操作方式,最好设置一个dummyHead。
lass Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* newHead = new ListNode(0, head);
ListNode* cur = newHead;
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
continue;
}
cur = cur->next;
}
return newHead->next;
}
};
LeetCode链接:https://leetcode.cn/problems/design-linked-list/
一些注意的点:
设置一个dummyHead更方便;
涉及到index的地方,开头先判断一下,如果index太小太大都不行,但是index是>=还是>size的时候不行, 就要看当时的情况;
记得要在添加和删除节点后++ --size;
get要定位当前节点, delete和addAtIndex要定位前置节点.
class MyLinkedList {
public:
struct LinkNode{
int val;
LinkNode* next;
LinkNode() : val(0), next(NULL){}
LinkNode(int x) : val(x), next(NULL){}
LinkNode(int x, LinkNode* node) : val(x), next(node){}
};
MyLinkedList() {
size=0;
dummyHead = new LinkNode();
}
int get(int index) {
if(index<0 || index>=size)
return -1;
LinkNode* cur = dummyHead;
for(int i=0; i<=index; i++){
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkNode* node = new LinkNode(val, dummyHead->next);
dummyHead->next = node;
size++;
return;
}
void addAtTail(int val) {
LinkNode* tail = dummyHead;
LinkNode* node = new LinkNode(val);
while(tail->next != NULL){
tail = tail->next;
}
tail->next = node;
size++;
return;
}
void addAtIndex(int index, int val) {
if(index<0 || index>size)
return;
LinkNode* cur = dummyHead;
for(int i=0; i<index; ++i){
cur = cur->next;
}
LinkNode* node = new LinkNode(val);
node->next = cur->next;
cur->next = node;
size++;
return;
}
void deleteAtIndex(int index) {
if(index<0 || index>=size)
return;
LinkNode* cur = dummyHead;
for(int i=0; i<index; ++i){
cur = cur->next;
}
LinkNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = NULL;
size--;
return;
}
private:
int size;
LinkNode* dummyHead;
};
LeetCode链接:https://leetcode.cn/problems/reverse-linked-list/
可以遍历的过程中不断新建一个链表, 但是这就很复杂;
最好的方式是遍历一遍, 过程中修改指针指向, 然后返回原尾节点(新头节点);
这就需要三个指针(如下图)
prev记录前置节点;
cur记录当前节点;
next记录后继节点(图中没有表现出来, 但是cur之所以能在cur->next改变后依然能移动, 就是以为next的存在).
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL)
return head;
ListNode* cur=head;
ListNode* prev = NULL;
ListNode* next = NULL;
while(cur){
next = cur->next;
cur->next = prev;//这里很巧妙地让最初head的next变为NULL, 不用单独拎出来处理了
prev = cur;
cur = next;
}
return prev;
}
};
链表的操作要特别注意next的指向, 以及循环的边界;
dummyHead的使用也需要很熟练;
链表可以说是很多操作的基础, 所以链表的添加, 删除, 反转等操作应该非常熟练才行.
本文参考:
203.移除链表元素
707.设计链表
206.反转链表