代码随想录算法训练营第三天 | 链表理论基础、203.移除链表元素、707.设计链表、206.反转链表
·
代码随想录算法训练营第三天 | 链表理论基础、203.移除链表元素、707.设计链表、206.反转链表
链表理论基础
链表的节点由两部分构成,数据域和指针域,数据域存储数据,指针域存储下一个节点的地址。
链表的类型
类型有单链表、双链表,循环链表。
单链表:节点是一个数据域,一个指针域。单向
双链表:节点是一个数据域,两个指针域。双向。一个指针域指向下一节点的地址,一个指针域指向上一个节点的地址。头节点的一个指针域指向空。
循环链表:节点是一个数据域,两个指针域。首尾相连。头节点的一个指针域指向下一个节点的地址,一个指针域指向最后一个节点的地址。
链表的定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
// 双向链表节点定义
struct DListNode {
int val; // 节点值
DListNode *prev; // 指向前驱节点
DListNode *next; // 指向后继节点
DListNode(int x) : val(x), prev(NULL), next(NULL) {} // 构造函数
};
// 循环单链表节点定义
// 关键区别在于构建时 形成循环
struct CListNode {
int val;
CListNode *next;
CListNode(int x) : val(x), next(NULL) {}
};
// 循环双向链表节点定义 在构建时,令头尾相连
struct CDListNode {
int val;
CDListNode *prev;
CDListNode *next;
CDListNode(int x) : val(x), prev(NULL), next(NULL) {}
};

链表的操作
删除节点:当前节点的前一节点的指针指向后一节点的地址,然后释放当前节点内存。
添加节点:先定义新节点,让前一个节点的指针指向新节点,让新节点的指针指向后一个节点的地址,即可完成新节点的添加
图示详解参见:代码随想录-链表理论基础
203.移除链表元素
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head == nullptr) return head;
ListNode* dummy = new ListNode(); // 定义虚拟节点
dummy->next = head; // 虚拟节点指向头节点
ListNode* cur = head;
ListNode* pre = dummy;
while (cur) {
if(cur->val == val) { // 找到val
pre->next = cur->next; // 删除节点
delete cur;
cur = pre->next; // 下一轮要判断的节点
}
else { // 未找到val, pre和cur都向前移动一步
pre = cur;
cur = cur->next;
}
}
return dummy->next;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
注意节点的定义和声明写法。
707.设计链表
题目链接:707.设计链表
是个注意细节的题目。
- 设计单向链表
class MyLinkedList {
public:
struct ListNode{ // 定义链表结构体
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
MyLinkedList() { // 构造函数,初始化成员变量
head = nullptr;
size = 0;
}
int get(int index) {
if (index >= size) return -1;
if (head == nullptr) return -1; // // index = 0, size = 0
ListNode* cur = head;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
ListNode* node = new ListNode(val);
node->next = head;
head = node;
size++;
}
void addAtTail(int val) {
ListNode* node = new ListNode(val);
if (head == nullptr) {
head = node;
size++;
return;
}
ListNode* cur = head;
while (cur) {
if (cur->next == nullptr) {
cur->next = node;
size++;
break;
}
else {
cur = cur->next;
}
}
}
void addAtIndex(int index, int val) {
if (index > size) return;
if (index == size) {
addAtTail(val);
return;
}
if (index == 0) {
addAtHead(val);
return;
}
ListNode* node = new ListNode(val);
ListNode* pre = head;
ListNode* cur = head->next;
index = index - 1;
while (index--) {
pre = cur;
cur = cur->next;
}
pre->next = node;
node->next = cur;
size++;
}
void deleteAtIndex(int index) {
if (index >= size) return;
if (index == 0) {
ListNode* tmp = head;
head = head->next;
delete tmp;
size--;
return;
}
ListNode* pre = head;
ListNode* cur = head->next;
index = index - 1;
while (index--) {
pre = cur;
cur = cur->next;
}
pre->next = cur->next;
delete cur;
size--;
}
private:
ListNode* head;
int size;
};
注意:按索引插入和删除时,循环的次数。
- 设计双向链表
class MyLinkedList {
public:
struct DListNode{ // 定义链表结构体
int val;
DListNode* prev;
DListNode* next;
DListNode(int x) : val(x), prev(nullptr), next(nullptr) {}
};
MyLinkedList() { // 构造函数,初始化成员变量
head = nullptr;
size = 0;
}
int get(int index) {
if (index >= size) return -1;
if (head == nullptr) return -1; // index = 0, size = 0
DListNode* cur = head;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
DListNode* node = new DListNode(val);
node->next = head;
if (head != nullptr) { // 插入第一个元素时,head为空,就无法用head->prev
head->prev = node; // 双向
}
head = node;
size++;
}
void addAtTail(int val) {
DListNode* node = new DListNode(val);
if (head == nullptr) {
head = node;
size++;
return;
}
DListNode* cur = head;
while (cur) {
if (cur->next == nullptr) {
cur->next = node;
node->prev = cur; // 双向
size++;
break;
}
else {
cur = cur->next;
}
}
}
void addAtIndex(int index, int val) {
if (index > size) return;
if (index == size) {
addAtTail(val);
return;
}
if (index == 0) {
addAtHead(val);
return;
}
DListNode* node = new DListNode(val);
DListNode* pre = head;
DListNode* cur = head->next;
index = index - 1;
while (index--) {
pre = cur;
cur = cur->next;
}
pre->next = node;
node->prev = pre; // 双向
node->next = cur;
cur->prev = node; // 双向
size++;
}
void deleteAtIndex(int index) {
if (index >= size) return;
if (index == 0) {
DListNode* tmp = head;
head = head->next;
// head->prev = nullptr; // 如果原本只有一个节点,head变成nullptr,这里就出错
if (head != nullptr) {
head->prev = nullptr; // 只有 head 不为空时才设置 prev
}
delete tmp;
size--;
return;
}
DListNode* pre = head;
DListNode* cur = head->next;
index = index - 1;
while (index--) {
pre = cur;
cur = cur->next;
}
pre->next = cur->next;
// cur->next->prev = pre; // 如果 cur 是最后一个节点,cur->next 为 nullptr,访问它就崩溃
if (cur->next != nullptr) {
cur->next->prev = pre;
}
delete cur;
size--;
}
private:
DListNode* head;
int size;
};
需要注意:避免空指针访问的情况。
需要熟悉类的定义,构造函数是初始化成员变量的。
使用一个虚拟头节点写这道题,代码会稍微简洁一些。
206.反转链表
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* dummy = new ListNode();
dummy->next = head;
ListNode* cur = head;
ListNode* pre = dummy;
while (cur) {
ListNode* temp = cur->next;
if (cur == head) {
cur->next = nullptr;
}
else {
cur->next = pre;
}
pre = cur;
cur = temp;
}
return pre;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
这道题竟然一次性通过!
我看题解还有另一种方法:递归法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* res = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return res;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
递归法着实难理解。。。。。。
但看了卡哥的视频讲解,好像懂了 " _ "
后续遇到递归还需结合着反复琢磨。
更多推荐


所有评论(0)