链表理论基础
文章链接:代码随想录
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
如图所示:
链表的类型
单链表
如上图所示
双链表
单链表中的指针域只能指向节点的下一个节点。
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
如图所示:
循环链表
循环链表可以用来解决约瑟夫环问题。
有n个人(编号为1, 2, 3, ... n)站成一圈。从编号为1的人开始,从1开始报数,报到m的人出列,然后下一个人重新从1开始报数,如此循环,直到所有人都出列为止。问题是,最后留下的人的编号是多少?
链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
链表的定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
链表的操作
删除节点
删除D节点,如图所示:
将C节点的next指针 指向E节点,D节点依然存留在内存里,只是没有在这个链表里。
所以在C++里最好是再手动释放这个D节点,释放这块内存。
其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。
添加节点
性能分析
203. 移除链表元素
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
视频链接:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili
文章链接:
注意头节点和非头节点的移除方式不一样,想要一样的话可以用虚拟头节点法
原链表删除元素
/**
* 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) {
// 原链表删除元素
while(head!=NULL && head->val==val){
ListNode* tmp = head;
head = head->next;
delete tmp;
}
ListNode* cur = head;
while(cur!=NULL && cur->next!=NULL){
if(cur->next->val==val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
return head;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
使用虚拟头节点
/**
* 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* dummy_head = new ListNode(0, head);
ListNode* cur = dummy_head;
while(cur->next!=NULL){
if(cur->next->val==val){
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else{
cur = cur->next;
}
}
// 注意释放掉虚拟头节点,头节点可以直接删掉了
head = dummy_head->next;
delete dummy_head;
return head;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
707. 设计链表
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
视频链接:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili
文章链接:代码随想录
class MyLinkedList {
public:
// 定义链表节点结构体
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){}
};
// 初始化链表
MyLinkedList() {
_dummyHead = new ListNode();
_size = 0;
}
int get(int index) {
if(index<0 || index>_size-1) return -1;
ListNode* cur = _dummyHead->next;
while(index--){
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode* newnode = new ListNode(val);
newnode->next = _dummyHead->next;
_dummyHead->next = newnode;
_size++;
}
void addAtTail(int val) {
ListNode* newnode = new ListNode(val);
ListNode* cur = _dummyHead;
while(cur->next!=NULL){
cur = cur->next;
}
cur->next = newnode;
_size++;
}
void addAtIndex(int index, int val) {
if(index>_size) return;
ListNode* newnode = new ListNode(val);
ListNode* cur = _dummyHead;
while(index--){ //注意此时cur刚好指向index-1,也就是要插入节点前那个
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
_size++;
}
void deleteAtIndex(int index) {
if(index<0 || index>=_size) return;
ListNode* cur = _dummyHead;
while(index--){
cur = cur->next;
}
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
// 打印链表
void printLinkedList(){
ListNode* cur = _dummyHead;
while(cur->next!=NULL){
cout << cur->next->val << " ";
cur = cur->next;
}
cout<<endl;
}
private:
int _size;
ListNode* _dummyHead;
};
/**
* 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);
*/
206. 反转链表
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
视频链接:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
文章链接:代码随想录
双指针解法
/**
* 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* cur = head;
ListNode* pre = NULL;
while(cur){ // 其实就是while(cur!=NULL)
ListNode* tmp = cur->next;
cur->next = pre;
// 此时已经改变了方向,将cur和pre整体后移
pre = cur; // 注意先移pre
cur = tmp;
}
// 返回新链表的头节点
return pre;
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(1)
递归解法
/**
* 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* reverse(ListNode* pre,ListNode* cur){
if(cur==NULL) return pre;
ListNode *tmp = cur->next;
cur->next = pre;
return reverse(cur, tmp);
}
ListNode* reverseList(ListNode* head) {
return reverse(NULL,head);
}
};
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间