代码随想录算法训练营第三天| LeetCode|C++| 203.移除链表元素 、707.设计链表、206.反转链表

链表的定义

// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

不定义构造函数行不行?答案是可以的,C++默认生成一个构造函数,但是这个构造函数不会初始化任何成员变量。

通过自己定义构造函数初始化节点:

ListNode* head = new ListNode(5);

使用默认构造函数初始化节点:

ListNode* head = new ListNode();
head->val = 5;

所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!

链表的存储方式

数组是在内存中是连续分布的,但是链表在内存中并不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

性能分析

把链表的特性和数组的特性进行一个对比,如图所示:
在这里插入图片描述

203.移除链表元素

解法一: 一般解法,头节点和非头节点分开操作
注意:
1、移除头节点也可能是持续移除,因此用while循环,不用if
2、临时指针cur要从head开始,才可能把head的下一个删了
3、第二个while循环的循环条件不需要判断next->next是否为空,因为为空是可以的,链表的最后一个结点的next本来就是空(画两个节点就知道了)
4、最后return head即可得到整个链表
5、要学会清理内存

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        /*
            1、采用一般解法,头节点和非头节点分开操作
            2、头节点可能需要连续删除,因此用while循环,不用if
            3、非头节点的删除则创建临时指针cur操作

            要学会清理内存!!!
        */
        while(head != NULL && head->val == val){
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }
        ListNode* cur = head;
        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;
    }
};

解法二: 虚拟头节点(统一操作头节点和非头节点)
注意:
1、链表中节点的创建方式:ListNode* dummy_head = new ListNode(0);
2、只有开辟新节点的时候才需要new!cur啊tmp啊只是指针而已,所以不用new
3、虚拟头节点也可以在最后释放掉
自己写的时候出现的问题:
没有new节点,而是直接写的ListNode* dummy_head;导致了以下错误
在这里插入图片描述
正确代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        /*
            1、采用虚拟头节点法,统一操作
            2、new一个虚拟头节点(咋new?
            3、
        */
        ListNode* dummy_head = new ListNode(0);
        dummy_head->next = head;
        ListNode* cur = dummy_head;
        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 dummy_head->next;
        head = dummy_head->next;
        delete dummy_head;
        return head;
    }
};

707.设计链表

思路: 采用虚拟头节点进行设计
注意:
1、不要忽视了链表节点的创建定义!记得写上,原题的模板没有给!
2、用类初始化链表,新建虚拟头节点以及初始化链表大小size
3、要对index进行有效性判断,以防止是负数和超出链表size的情况
自己写的时候出现的问题:
1、忽视了链表节点的创建定义
2、不会初始化链表
3、对类的使用不熟悉(在private里设置属性dummy_headsize
4、缺少对index的有效判断
5、addAtTail()函数中错用size--,导致其他地方逻辑错误,用cur->next != nullptr即可
收获:
真正尝试了用本地IDE去cout打日志,找出问题,有时候光看是真看不出来,在此感谢@卡哥助手_围城,没有直接丢给我答案,而是和我说去cout打印日志就明白了,不然我绝不可能自己动手hhh

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode{
        int val;
        LinkedNode* next;
        LinkedNode(int val) : val(val), next(nullptr) {}
    };

    // 初始化链表
    MyLinkedList() {
        dummy_head = new LinkedNode(0);
        size = 0; //虚拟头节点不算在size里
    }
    
    int get(int index) {
        /*
            1、先判断index是否有效
            2、有效后再根据index进行索引
        */
        if(index < 0 || index > (size - 1)){
            return -1;
        }
        LinkedNode* cur = dummy_head->next;
        while(index--){
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* node = new LinkedNode(val);
        node->next = dummy_head->next;
        dummy_head->next = node;
        size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* node = new LinkedNode(val);
        LinkedNode* cur = dummy_head;
        /* 此处用size--会把size的值改变,其他用到size的地方也就乱了(比如if(index <= size)这种),全部也就不合逻辑了
        while(size--){
            cout << "size = " << size << endl;
            cur = cur->next;
        }
        */
        while(cur->next != nullptr){
            //cout << "size = " << size << endl; 这句打开直接给你超时
            cur = cur->next;
        }     
        cur->next = node;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index < 0) index = 0;        
        if(index <= size){
            LinkedNode* node = new LinkedNode(val);
            LinkedNode* cur = dummy_head;
            while(index--){
                cur = cur->next;
            }
            node->next = cur->next;
            cur->next = node;
            size++;
        }else if(index > size){
            return;
        }
    }
    
    void deleteAtIndex(int index) {
        if(index < size && index >= 0){
            LinkedNode* cur = dummy_head;
            while(index--){
                cur = cur->next;
            }
            LinkedNode* tmp = cur->next;
            cur->next = cur->next->next;
            delete tmp;
            tmp = nullptr;
            size--;
        }else{
            return;
        }

    }

    //用于在本地IDE测试(测试了addAtTail函数里size--的问题)
    void printLinkedList() {
        LinkedNode* cur = dummy_head;
        while (cur->next != nullptr) {
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << endl;
    }

private:
    int size;
    LinkedNode* dummy_head;
};

206.反转链表

解法一: 双指针法(pre 和 cur)以及用tmp暂存cur->next,防止失去位置信息

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        /*
            1、双指针解法,cur和pre
            2、边翻转边移动,while循环
            3、利用tmp暂存cur->next,以防止丢失位置
        */
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur != NULL){
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

解法二: 递归法,和双指针法一一对应,建议先理解透双指针法再照着写
自己写的时候出现的问题: 对return的用法比较疑惑(虽然从最里面可以return到pre,但之后就没有return可用了,所以会停在倒数第二层,应该是这样,递归不会用打日志的方法验证…)

class Solution {
public:
    ListNode* reverse(ListNode* cur, ListNode* pre){
        if(cur == NULL) return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        //上面一次翻转已完成,于是进入下一次
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(tmp, cur);
    }

    ListNode* reverseList(ListNode* head) {
        return reverse(head, NULL);
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值