链表
| 插入/删除(时间复杂度) | 查询(时间复杂度) | 适用场景 | |
|---|---|---|---|
| 数组 | O(n) | O(1) | 数据量固定,频繁查询,较少增删 |
| 链表 | O(1) | O(n) | 数据量不固定,频繁增删,较少查询 |
- 注意要是删除第五个节点,链表需要从头结点查找到第四个,O(n)复杂度。
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
ListNode* head = new ListNode(5);
// 不使用构造函数不能直接赋值
ListNode* head = new ListNode();
head->val = 5;
203 .移除链表元素
三种方法:
- 普通,
while current->next != NULL:删除的都是next,而不是对当前node操作。并且得独立处理head节点的case - my own method:
while current != NULL: 删除的都是current,所以需要一个previous pointer来把两端连接起来,不需要独立处理head case,只需要while里多一个if - 虚拟头结点:
我自己的previous method
/**
* 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* current = head;
ListNode* previous = NULL;
while (current != NULL){
if (current->val == val){
if (current == head){
head = current->next;
ListNode* temp = current;
current = current->next;
delete temp;
}else{
ListNode* temp = current;
current = current->next;
previous->next = current;
delete temp;
}
}else{
previous = current;
current = current->next;
}
}
return head;
}
};
虚拟头结点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
ListNode* cur = dummyHead;
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 = dummyHead->next;
delete dummyHead;
return head;
}
};
-
注意点:
- 如果
while current->next != NULL: 永远对下一个节点进行操作的话,删除以后,不需要将current推动到下一位,因为我们的current->next是在变的,所以把持原样就行。只在没有删除节点时,手动更新current为current->next
- 如果
707
- 经过我的不断探索,发现还是得有虚拟头结点,不然有很多case很难处理
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
// 初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
_size = 0;
}
// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在链表最后面添加一个节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur ->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
总结:
- 虚拟头结点很重要 加单链表
- 不用tail的话,delete at tail 和 add at tail复杂度会高,但是没办法
- 用tail就得双链表
- 然后删除节点时,要删
temp,不能把原本还要用的指针给删了。
206
递归法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == NULL){ //边界判断
return head;
}else if (head->next == NULL){
return head; // 此时head为last节点,return base case
}
// last node保持不变,一直传递回去
ListNode* last_node = reverseList(head->next);
// reverse order
head->next->next = head;
head->next = NULL;
return last_node;
}
};
想出了递归的架构,但是在处理last_node上没琢磨出来,最后发现直接return最后一个个就可以了
- 解题过程:最终一定是
return最后的节点的,这个在一开始就要确立好,然后再想,在此基础上怎么做才能又return了last又能让递归进行下去,我之前想要return head是想next_node = reverseList(head->head);这样一个比较帅的操作,在该节点call function时同时还获取了next的节点,但是这就是脱裤子放屁啊,虽然你是通过递归获得了,但是直接的head->next不就搞定了吗。这次的矛盾就是在一旦head->next->给到了其他地址,在普通的循环里,就无法往下迭代推进了。但是利用递归这个反向操作,我应该明白递归最后函数的收束是从结尾开始的,那么只要在call递归的那一行之后进行操作就没问题了。 最后就是头结点的next得接到null上,这个通过思考应该能知道head->next = NULL在除了首节点以外的节点都会被递归里的覆盖的。应该多画图理解的。
- 注意第6行,一开始是把两个if case 放一块判断的,但是当
head == NULL时,先判断了head->next == NULL就会出现undefinedBehavior,在null上进行操作了。
循环
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL; // 不能设为 head->pre,struct里未定义
while(head != NULL){ // 判断的都是当前的case
ListNode* temp = head->next;
// 因为调换了,用temp记录下一个iterator
if (pre != NULL){
head->next = pre; // 否则就调换为pre,所以在下面需要更新pre
}else{ // 只有head的pre为NULL
head->next = NULL;
}
if (temp == NULL){
return head;
}
// pre永远记录上一个循环的指针
pre = head;
// iterator 更新为 head->next
head = temp;
}
return head;
}
};
总结:链表还是掌握的可以的,两个都写出来了。