目录
链表理论基础
链表的定义及分类
1、定义:链表是一种通过指针串联在一起的线性结构
2、要素:节点—每个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针)。最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为头节点也就是head
3、单链表:每个节点有一个指针域,只能指向节点的下一个节点
4、双链表:每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表既可以向前查询也可以向后查询
5、循环链表:链表首尾相连
链表的内存存储方式
链表的存储方式:数组在内存中是连续分布的,但是链表在内存中不是连续分布的。链表是通过指针域的指针链接在内存中各个节点,所以链表中的节点在内存中不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于系统的内存管理
在程序中定义一个链表节点
struct ListNode()
{
int val; //数据域
ListNode* next; //指针域
ListNode(int x) : val(x) , next(NULL){} //节点的构造函数
}
初始化一个节点(使用构造函数)
ListNode* head = new ListNode(5);
203.移除链表元素
203. 移除链表元素 - 力扣(LeetCode):给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
不采用虚拟头节点
1、关键:移除头节点和其他节点不同。
2、为什么删除节点都要delete:C++与其他语言不同,删除节点需要注意内存释放,原因如下:不释放的话造成内存泄露,简单地说,就算操作系统分配到程序的那部分内存实际已经没用了,但还没还给操作系统。久而久之,内存就被这样占满了。
采用虚拟头节点
1、关键:判断要不要删的是current->next这个节点,所以规律还是增、删操作定义的cur节点为_dummuhead。
2、易错点:使用虚拟头节点的return,return的是虚拟头节点的下一个节点(因为head节点可能已经被删了),而不是head。
3、代码:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//用虚拟头节点
ListNode* dummyhead = new ListNode();
dummyhead->next = head;
ListNode* cur = dummyhead;
while(cur->next!=nullptr)
{
if(cur->next->val==val)
{
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else
{
cur = cur->next;
}
}
return dummyhead->next;
}
};
707.设计链表
707. 设计链表 - 力扣(LeetCode):在链表类中实现这些功能:定位节点、链表头插、链表尾插、插入某位置节点、删除某位置节点
1、预先准备:定义节点结构体、定义链表构造函数、定义虚拟头节点和链表长度变量
2、关键点:①构造链表时要用虚拟头节点;
②增、删操作的current都是_dummyhead,原因:增删操作都是从前一个节点开始操作的,开始操作的节点就定义为current
3、补充:变量前带上_,如_dummyhead 表明是private私有成员变量
4、代码:
class MyLinkedList {
public:
//定义节点
struct MyLinkedNode
{
int val;
MyLinkedNode* next;
MyLinkedNode(int val): val(val), next(nullptr) {}
};
//初始化链表
MyLinkedList() {
_dummyhead = new MyLinkedNode(0);
_size = 0;
}
//获取链表中下标为index的节点的值
int get(int index) {
if(index > (_size - 1) || index < 0) return -1;
MyLinkedNode* cur = _dummyhead->next;
//找到下标为index的节点
while(index!=0)
{
cur = cur->next;
index--;
}
return cur->val;
}
//在链表的第一个元素之前插入节点
void addAtHead(int val) {
MyLinkedNode* NewNode = new MyLinkedNode(val);
NewNode ->next = _dummyhead->next;
_dummyhead->next = NewNode;
_size++;
}
//在链表末尾插入节点
void addAtTail(int val) {
MyLinkedNode* NewNode = new MyLinkedNode(val);
//错误位置
MyLinkedNode* cur = _dummyhead;
while(cur->next!=nullptr)
{
cur = cur->next;
}
cur->next = NewNode;
_size++;
}
//在链表中第index个位置前插入节点
void addAtIndex(int index, int val) {
//错误位置
if(index > _size) return;
MyLinkedNode* NewNode = new MyLinkedNode(val);
MyLinkedNode* cur = _dummyhead;
//找到下标为index的前一个点
while(index!=0)
{
cur = cur->next;
index--;
}
NewNode->next = cur->next;
cur->next = NewNode;
_size++;
}
//删除链表中下标为index的节点
void deleteAtIndex(int index) {
//错误位置:没有讨论index的大小
if (index >= _size || index < 0) return;
MyLinkedNode* cur = _dummyhead;
//找到链表中下标为index的节点
while(index!=0)
{
cur = cur->next;
index--;
}
//错误位置
MyLinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
private:
MyLinkedNode* _dummyhead;
int _size;
};
206.反转链表
206. 反转链表 - 力扣(LeetCode):给你单链表的头节点 head
,反转链表,返回反转后的链表。
双指针法
1、关键:①定义了两个指针,一个是pre指针,一个是cur指针
②循环终止条件:cur指针为NULL,那么最后的头节点应该是pre指针
2、代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//双指针法
ListNode* pre = new ListNode();
ListNode* cur = new ListNode();
pre = NULL;
cur = head;
while(cur!=NULL)
{
ListNode* temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
递归法
对照双指针法的方式写递归法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
return reverse(NULL ,head);//双指针中的初始条件pre = NULL, cur = head
}
ListNode* reverse(ListNode* pre, ListNode* cur)
{
if(cur==NULL) return pre; //双指针中循环的终止条件
ListNode* tmp =cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
return reverse(pre, cur);
}
};