链表
链表的存储在内存中不是连续的
链表的定义
struct ListNode
{
//存储元素
int val;
//下一个节点位置
ListNode* next;
//构造函数
ListNode(int x, ListNode* next):val(x),next(next){}
};
链表节点的删除就和数组不一样了,数组的删除元素相当于覆盖元素,而链表的删除是直接将节点的指向改变,这样通过被删除的节点就不会通过这个链表寻找到,但他仍然存在在链表中,所以需要手动释放。
移除链表元素
文档讲解:代码随想录
视频讲解:手把手带你学会操作链表 | LeetCode:203.移除链表元素
状态:√
- 思路
删除等于val的节点,相当于将指向这个节点的指针改变指向,指向这个节点后面第一个不等于val的节点。
为了避免第一个节点就是val节点而带来的单独判断,所以采用一个虚拟头结点,即该节点指向原始链表的头部。
循环终止条件:我们要判断的是当前节点的下一个节点值是否等于val,因为我们在循环中确定了当前节点的值不为val,所以循环终止条件就是val->next!=nullptr
循环内部的处理逻辑是,当我们发现下一个节点的值为val时,进行删除操作,当不是val时,进行节点移动操作。
注意由于我们的第一个节点的值可能是val所以,我们最后的返回结果不能是head,虽然我们返回的First->next在定义中是head。
/**
* 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) {
//使用虚拟头结点,处理第一个元素就是val的情况
//用于返回数据
ListNode* First = new ListNode(0);
First->next = head;
//用于处理数据
ListNode* cur = First;
//注意循环判断条件
while(cur->next)
{
if(cur->next->val == val)
{
//存储要删除的节点
ListNode* temp = cur->next;
//改变指向
cur->next = temp->next;
//删除该节点
delete temp;
}
//只有当我们next不等于val的时候才进行节点移动,而不是每个循环都要进行节点移动
else
{
cur = cur->next;
}
//cout << head;
}
return First->next;
}
};
时间复杂度:对于每个节点只处理了1次,所以是O(n)
设计链表
文档讲解:代码随想录
视频讲解:帮你把链表操作学个通透!LeetCode:707.设计链表
状态:容易忘记每次增删之后长度要进行变化
- 思路
本题就是对链表的一个模拟,- 首先要定义一个结构体来表示一个节点单元
- 其次在类中要定义变量,即头结点和链表长度,在构造函数中初始化
- 增加需要定义一个节点用来接收值和处理其指针指向,增加后不要删除,删除节点过程需要定义一个临时变量来存储要删除的节点,使用完之后要删除
- 每次增加和删除之后,要记得对长度进行修改
- 构造函数中定义的是虚拟头结点,在对链表头部添加元素时效果明显
class MyLinkedList {
public:
//一个链表节点
struct ListNode
{
int val;
ListNode* next;
ListNode(int x):val(x),next(nullptr){}
ListNode(int x , ListNode* n):val(x),next(n){}
};
MyLinkedList() {
//虚拟头结点
First = new ListNode(0);
//此时链表长度
size = 0;
}
int get(int index) {
if(index<0 || index >= size)
{
return -1;
}
else
{
int i = index;
ListNode* cur = First;
while(i>=0)
{
cur = cur->next;
i--;
//cout << cur->val;
}
return cur->val;
}
}
//void addAtHead(int val){}
void addAtHead(int val) {
ListNode* AddNew = new ListNode(val,First->next);
First->next = AddNew;
size++;
}
//void addAtTail(int val){}
void addAtTail(int val) {
ListNode* cur = First;
while(cur->next)
{
cur = cur->next;
}
ListNode* AddNew = new ListNode(val);
cur->next = AddNew;
size++;
}
//void addAtIndex(int index, int val){}
void addAtIndex(int index, int val) {
if(index == size)
{
addAtTail(val);
}
else if(index > size)
{
return;
}
else if(index <= 0)
{
addAtHead(val);
}
else
{
ListNode* cur = First;
int i = index;
while(i>0)
{
cur = cur->next;
i--;
}
ListNode* AddNew = new ListNode(val,cur->next);
cur->next = AddNew;
size++;
}
}
// void deleteAtIndex(int index){}
void deleteAtIndex(int index) {
if(index<0 || index >=size)
{
return ;
}
else
{
ListNode* cur = First;
int i = index;
while(i> 0)
{
cur = cur->next;
i--;
}
ListNode* temp = cur->next;
cur->next = temp->next;
delete temp;
size--;
}
}
private:
ListNode* First;
int size;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
翻转链表
文档讲解: 代码随想录
视频讲解:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法
状态:√
- 采用虚拟头结点
虚拟头结点的作用是翻转之后,控制原始的第一个元素指向Null,然后剩下的方法和双指针类似了。直接使用给的头结点返回,再定义一个初始指向头结点下一个节点的指针,通过判断这个指针是否为null,来终止循环。
这个方法还需要判断初始链表是否为空
/**
* 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) {
ListNode* First = new ListNode(0,head);
if(head)
{
ListNode* cur = head->next;
while(cur)
{
ListNode* temp = cur->next;
cur->next = head;
head = cur;
cur = temp;
temp = nullptr;
delete temp;
}
First->next->next = nullptr;
}
return head;
}
};
temp的定义可以拿到循环外
2. 双指针
直接定义一个返回指针初始为空指针,再定义一个移动判断指针初始为头节点,每次移动利用临时变量存储移动指针的下一个节点,然后改变其的指向,使其指向前一个节点就是返回指针此时所在的位置,之后在进行赋值操作就可以了。
这里要特别注意指针的删除:delete指针只是释放指针的内存空间,而不是指针本身。称为野指针,如果后续进行new一个新的指针,编译器可能会把内存分配到这个指针指向的内存,就出现了两个指针指向同一块内存的操作。所以我们在删除之后,需要将指针变为空指针。在力扣中会出现这样的报错
不过还是最好将temp节点放在循环外定义和删除,节省空间
/**
* 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) {
//ListNode* temp;
//双指针,一个用于记录已反转的链表,一个记录还未翻转链表的头节点
ListNode* res = nullptr;
ListNode* cur = head;
while(cur)
{
ListNode* temp = cur->next;
cur->next = res;
res = cur;
cur = temp;
temp = nullptr;
delete temp;
}
//delete temp;
return res;
}
};
- 递归
感觉任何循环都可以划为递归,同样任何递归可以变为循环
我们定义一个递归函数,双指针循环中我们实际上有两个参数,res和cur,分别用于存储翻转后的链表和还未翻转的链表的“头结点”,在反转之后将cur赋值给res,将temp赋值给cur所以递归可以这样写
ListNode* reverse(ListNode* res,ListNode* cur)
{
if(cur == nullptr) return res;
ListNode* temp = cur->next;
cur->next = res;
return reverse(cur,temp);
}