代码随想录训练营打卡第三天|203.移除链表元素, 707.设计链表, 206.反转链表

203.移除链表元素

题目链接: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.设计链表

题目链接:707.设计链表

思路:
难度中等,第一眼不是很难(先写写试试

    // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    // 如果index小于0,则在头部插入节点

看完文档后:
主要难点在于链表大小和index的判断问题上

206.反转链表

题目链接: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; // 返回新的头节点(最终是原来的尾节点)
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值