第三天继续刷题,链表已经忘得差不多了,手比较生。做完第二题后感觉重新熟悉链表了(已老实
203.移除链表元素
解题过程
- 尝试用虚拟头节点做这个题,以为做过就手到擒来,但还是轻敌了,在删除
cur->next
节点时,不需要更新cur
,只需更新cur->next
,不然会少判断更新后的cur节点
知识点
-
单链表的节点定义方式
// 单链表 struct ListNode { int val; // 节点上存储的元素 ListNode *next; // 指向下一个节点的指针 ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 };
-
可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。
-
时间复杂度: O(n)
-
空间复杂度: O(1)
虚拟头节点方式
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyHead = new ListNode();
dummyHead->next = head;
ListNode* cur = dummyHead;
while (cur->next) {
if (cur->next->val == val) {
ListNode* tmp = cur->next;
cur->next = tmp->next;
delete tmp;
} else {
cur = cur->next;
}
}
head = dummyHead->next;
delete dummyHead;
return head;
}
}
707.设计链表
解题过程
- 做的时候没定义
ListNode
类,做的时候很费劲感觉很抽象,不能AC后就看讲解,想了想确实应该定义一个嵌套类比较方便,并为了统一定义了dummyHead
和size
成员变量。 - 第二次做有一个测试用例一直通过不了,debug发现是在特定位置添加元素时
index
可以等于链表大小,之前都是写习惯了index>=size;
应该更细心些。
知识点
- 设置一个虚拟头结点更方便
- 时间复杂度: 涉及
index
的相关操作为 O(index), 其余为 O(1) - 空间复杂度: O(n)
设计链表
class MyLinkedList {
public:
MyLinkedList()
: _size(0)
, _dummyHead(new ListNode)
{}
int get(int index) {
if (index < 0 || index >= _size) 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) cur = cur->_next;
cur->_next = newNode;
_size++;
}
void addAtIndex(int index, int val) {
if (index < 0 || index > _size) return;
ListNode* newNode = new ListNode(val);
ListNode* cur = _dummyHead;
while (index--) 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;
tmp = nullptr;
_size--;
}
private:
struct ListNode {
int _val;
ListNode* _next;
ListNode(int val = 0)
: _val(val)
, _next(nullptr)
{}
};
int _size;
ListNode* _dummyHead;
};
206.反转链表
解题思路
- 双指针从前向后反转链表
- 看了讲解后,用递归从前向后反转链表
- 用递归从后向前反转链表没有看懂,XD
知识点
- 双指针法的时间复杂度: O(n),空间复杂度: O(1)
- 递归法的时间复杂度: O(n),要递归处理链表的每个节点;空间复杂度: O(n),递归调用了 n 层栈空间。
双指针
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* tmp;
while (cur) {
tmp = cur;
cur = cur->next;
tmp->next = pre;
pre = tmp;
}
return pre;
}
};
递归
class Solution {
public:
ListNode* reverse(ListNode* pre, ListNode* cur) {
if (cur == nullptr) return pre;
ListNode* tmp = cur;
cur = cur->next;
tmp->next = pre;
return reverse(tmp, cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(nullptr, head);
}
};
从后往前翻转指针指向递归 (没看懂)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};