203.移除链表元素
思路:引入虚拟头结点便于统一计算步骤,简化算法结构。
不引入虚拟头结点
// 直接在原链表上操作,头结点和其他结点的操作不一致,需要单独处理
// c++记得处理释放删除结点所占用内存,避免内存泄漏
// 时间复杂度O(n)、空间复杂度O(1)
ListNode* removeElements1(ListNode* head, int val) {
// 处理头结点,注意用while而不是if(可能新头结点也符合删除条件)
while (head != NULL && head->val == val) {
ListNode* tmp = head;
head = head->next;
delete tmp;
}
// 处理其他结点
// 当前指针从head出发
ListNode* cur = head;
// 检查cur指针的下一个结点是否符合条件,符合则删除,否则向后移动
// 末尾结点的判断条件为cur->next == NULL
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;
}
不引入虚拟头结点
// 设置虚拟头结点dummyHead,以此统一头结点和其他结点的操作方式,简化代码逻辑
// 时间复杂度O(n)、空间复杂度O(1)
ListNode* removeElements2(ListNode* head, int val) {
// 用dummyHead替代原始头结点
ListNode* dummyHead = new ListNode(0);
dummyHead->next = 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可能被删除了
head = dummyHead->next;
delete dummyHead;
return head;
}
707.设计链表
思路:本题难点居然是在于index的取值问题,搞了半天很坑,题本身就是抄书级别。
class MyLinkedList {
public:
// 定义内部类:链表结点结构体
struct LinkedNode{
int val;
LinkedNode* next;
// 构造函数
LinkedNode(int x)
: val(x)
, next(nullptr){
cout << "LinkedNode(int x)" << endl;
}
};
//无参构造函数,初始化链表,创建虚拟头结点
MyLinkedList() {
_dummyHead = new LinkedNode(0);
_size = 0;
}
// 获取下标为index的结点值,非法返回-1,头结点的index为0
int get(int index) {
// 先处理index的非法取值
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
// 如果是--index 可能会出现负数,导致死循环
while(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++;
}
// 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。
// index为0,则变为新头结点
// 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大于链表的长度,返回空
// 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;
// 避免tmp变野指针
tmp = nullptr;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
206.反转链表
思路:来到典中典之翻转链表,递归法确实难以直接想到,建议先写出双指针法,再根据其推出递归解法。建议观看carl的讲解视频,精髓! 好图直接引:
双指针法
// 双指针法,pre指针和cur指针一前一后,
// 在原链表上只改变next指针方向
// 时间复杂度: O(n)
// 空间复杂度: O(1)
ListNode* reverseList1(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
// tmp记录当前结点的next,因为next换向后,会丢失下一个结点
ListNode* tmp;
// 循环到末尾结点
while (cur) {
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
递归解法
// 递归法,思想和双指针法相同
// 时间复杂度: O(n), 要递归处理链表的每个节点
// 空间复杂度: O(n), 递归调用了 n 层栈空间
// 递归函数
ListNode* reverse(ListNode* pre, ListNode* cur) {
// 递归的出口:pre指向新头结点(原末尾结点),cur指向空
if (cur == NULL) {
// 返回新头结点
return pre;
}
ListNode* tmp = cur->next;
cur->next = pre;
// 递归的入口:类比于双指针法的
// pre = cur;
// cur = temp;
return reverse(cur, tmp);
}
ListNode* reverseList2(ListNode* head) {
// reverse()返回的是翻转后的头结点
return reverse(NULL, 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;
}
};