203.移除链表元素
思路:
难度简单,但很久没用到链表了,具体的语句有点忘记了
通过循环的方式,直到next为空:单指针,next.val == val时,head -> next = head.next -> next(大概是这样达到删除节点的操作
看完文档后:
发现递归在代码层面更加简单,但是需要理解。
1.递归题解
ListNode* head = nullptr; // 推荐写法
/*
nullptr:C++11 引入的关键字,表示空指针,类型安全。
它有专门的类型 std::nullptr_t,可以隐式转换成任意指针类型。
推荐在现代 C++ 中使用。
*/
ListNode* head = NULL; // 老写法,还能用,但不安全
//递归方法具体代码
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head == nullptr){
return head;
}
head->next = removeElements(head->next, val);
return head->val == val? head->next : head;
}
};
递归好绕,从尾节点往前推导,2 -> 6 -> 3,val = 6情况下:
先判断3,head.val == 3 != val,返回head节点(节点3)
head->next = removeElements(head->next, val); //(6->3)
在判断6,head.val == 6 == val,返回head->next(节点3)把自身跳过
head->next = removeElements(head->next, val);//(2->3)
最后判断2,head.val == 2 != val,返回head节点(节点2->节点3)
每一层函数(ListNode类的函数)返回时,都会确保自己返回的是一个链表头(ListNode类)
2.循环题解
自己写出现的问题:
没有返回值
函数签名返回ListNode*,但你没return。
head++是错误的
链表不是数组,ListNode*不能做指针算术来“下一个节点”。应当用head = head->next;。可能空指针解引用
当head->val == val时,你直接用head->next->val和head->next->next,但如果head->next == nullptr就会崩掉。并没有“删除节点”,只是改了值并改指针
你把当前节点的val改成了下一节点的值,再把next跳过下一节点。这种“覆盖+跳指针”的技巧只在非常受限的场景(已知要删的不是尾节点,且只给你该节点指针)才勉强可用;但在常规遍历中:
如果要删的是尾节点,这招完全不适用(没有
head->next)。会破坏链表中其它节点的值,语义不对。
造成内存泄漏(被跳过的那个节点没被释放)。
更正:
设置一个哨兵,指向头节点
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
ListNode* cur = head;
//prev 是指针,需要一个地址来初始化 → 所以用 &dummy。
//cur 也是指针,但 head 已经是个指针了,不需要再取地址。
while (cur != nullptr) {
if (cur->val == val) {
prev->next = cur->next;
// 手动释放内存
delete cur;
cur = prev->next;
} else {
prev = cur;
cur = cur->next;
}
}
return dummy.next;
}
};
思考:
Q:为什么声明哨兵的时候,声明的是对象而不是指针?
A:因为 哨兵节点只在函数内部使用,函数结束后也不需要它。所以放在栈上最自然:不需要管理内存,也不会泄漏。
Q:所以声明指针的时候存放的地方是栈?
A:
1.对象直接声明 → 对象在栈上,函数结束自动销毁。
2.指针声明 → 指针在栈上,它指向哪里取决于你是否new。
3.new出来的对象 → 永远在堆上,必须手动释放。
707.设计链表
思路:
难度中等,第一眼不是很难(先写写试试
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
看完文档后:
主要难点在于链表大小和index的判断问题上
206.反转链表
思路:
直接new一个新的,然后顺着塞到新的链表里
看完文档后:
不需要对新结点进行赋值,直接更改next的指向即可
递归法
/**
* 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) {
// 递归终止条件:空节点 或 只剩最后一个节点
if (head == nullptr || head->next == nullptr) {
return head;
}
// 递归:翻转 head->next 之后的链表
ListNode* newHead = reverseList(head->next);
// 回溯阶段:翻转当前节点
head->next->next = head; // 下一个节点指向自己
head->next = nullptr; // 当前节点指向空,避免成环
return newHead; // 返回新的头节点(最终是原来的尾节点)
}
};

被折叠的 条评论
为什么被折叠?



