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

链表理论要点

  • 链表中的节点区别于数组,是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理,并通过指针串联在一起
  • C++中链表的构造:
    // 单链表
    struct ListNode {
        int val;  // 节点上存储的元素
        ListNode *next;  // 指向下一个节点的指针
        ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
    };
  • 如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值 
    //使用自定义构造函数
    ListNode* head = new ListNode(5);
    
    //使用默认构造函数
    ListNode* head = new ListNode();
    head->val = 5;
  • 链表中,删除的节点仍然保存在内存中。C++需要手动释放节点的这块内存;而Java、Python等语言有自己的内存回收机制,不用手动释放了
  • 链表和数组的性能对比

203. 移除链表元素

题目链接:Leecode203. 移除链表元素 

文章讲解:代码随想录—203. 移除链表元素

思路:

移除元素迅速想到可能移除的是头节点,因此需要设置虚拟头结点,然后按正常移除链表其他节点一样移除头结点

想到要设置两个指针,一个指向当前节点位置,一个搜寻不需要移除元素的指针,然后把当前指针节点的next指针指向不需要移除元素的指针即可

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
            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 = dummyHead->next;
            delete dummyHead;
            return head;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

707. 设计链表

题目链接:Leecode707. 设计链表

文章讲解:代码随想录—707. 设计链表

思路:就是链表的所有基本操作,个人认为掌握了这道题,链表这个数据结构基本没有问题了

class MyLinkedList {
public:
    struct ListNode {
        int val;
        ListNode* next;
        ListNode(int x) : val(x), next(nullptr){}
    };

    MyLinkedList() {
        dummyHead = new ListNode(0);
        size = 0;
    }
    
    int get(int index) {
        if (index > (size - 1) || index < 0) return -1;
        ListNode* cur = dummyHead->next;
        while (index --) cur = cur->next;
        return cur->val;
    }
    
    void addAtHead(int val) {
        ListNode* tmp = new ListNode(val);
        tmp->next = dummyHead->next;
        dummyHead->next = tmp;
        size ++;
    }
    
    void addAtTail(int val) {
        ListNode* tmp = new ListNode(val);
        ListNode* cur = dummyHead;
        while (cur->next != NULL) cur = cur->next;
        cur->next = tmp;
        size ++;
    }
    
    void addAtIndex(int index, int val) {
        if (index > size) return;
        if (index < 0) index = 0;
        ListNode* cur = dummyHead;
        ListNode* tmp = new ListNode(val);
        while (index --) cur = cur->next;
        tmp->next = cur->next;
        cur->next = tmp;
        size ++;
    }
    
    void deleteAtIndex(int index) {
        if (index > (size - 1) || index < 0) return;
        ListNode* cur = dummyHead;
        while (index --) cur = cur->next;
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;      //delete只是释放了tmp的内存,并非指向NULL,会变成随机值成为野指针
        tmp = nullptr;   //如果不加这一句,以后用到这部分地址就会变成乱指的野指针,指向任意地方
        size --;
    }

private:
    int size;
    ListNode* dummyHead;
};

时间复杂度:index相关操作为O(index),其余为O(1)

空间复杂度:O(n)

206. 反转链表

题目链接:Leecode206. 反转链表

文章讲解:代码随想录—206. 反转链表

双指针法

思路:我能想到的第一想法就是设置三个指针,代码随想录里叫双指针,其实是设置了一个 cur 指针,cur 的前一个指针 pre,以及 cur 的 nex t指针 tmp。之所以设置 tmp 指针是因为 cur 指针反转后就找不到反转前的 next 指针了,因此需要保存一下反转前 cur 的 next 指针

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = nullptr;
        ListNode* tmp;
        while (cur != NULL) {
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

时间复杂度:O(n)

空间复杂度:O(1)

遇到的问题:不能一开始就设置 ListNode* tmp = cur->next,因为 cur 指向的头指针可能是空指针,此时的 tmp 就没有意义了

递归法

思路:和双指针法思路完全一样,只是把 pre 和 cur 指针作为参数传入到递归函数中了。递归法总结了从前往后反转和从后往前反转两种解法,个人觉得从后往前反转不是很好理解,且复杂度和从前往后反转一样,还是先记住前一种解法吧

从前往后递归

想法很好,只是脑袋总是转不过来,对递归不习惯

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        return reverse(NULL, head);
    }

    ListNode* reverse(ListNode* pre, ListNode* cur) {
        if (cur == NULL) return pre;
        ListNode* tmp = cur->next;
        cur->next = pre;
        return reverse(cur, tmp);
    }
};

时间复杂度:O(n)

空间复杂度:O(n)

从后往前递归

这个思想非常巧妙,但真的不好理解,画了个图才弄明白,递归思想真的神奇

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;
        return last;
    }
};

时间复杂度:O(n)

空间复杂度:O(n) 

总结

链表的题还是很基础的,只要掌握了理论就基本都可以写出来

有一点注意的是,删除节点时,C++需要手动释放内存,不释放依然会通过,但这是一个好的习惯,能够节省空间、减少野指针带来的麻烦

此外,善用虚拟头结点能让思路更清晰、代码更简洁,考虑情况时不用非常麻烦 

希望有空能把递归做法深入一下,很好玩

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我知道了。以下是对您的问题的回答: a. 动态分配内存的方法有很多,可以使用C语言的malloc函数,也可以使用C++中的new操作符,具体方法如下: C语言的malloc函数: ```c struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode)); ``` C++中的new操作符: ```c++ ListNode* node = new ListNode(); ``` b. 初始化单链表需要对链表节点的成员变量进行赋值,以下是一个示例: ```c++ struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {} }; ListNode* head = NULL; ``` c. 头插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = head->next; head->next = node; } ``` 尾插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; ListNode* tail = head; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = NULL; tail->next = node; tail = node; } ``` d. 取链表某一位的值可以通过遍历链表来实现,以下是一个示例: ```c++ ListNode* p = head->next; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { cout << p->val << endl; } ``` e. 在链表的某一位进行插入操作可以通过遍历链表找到插入位置,然后插入一个新节点来实现,以下是一个示例: ```c++ ListNode* p = head; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { int x = 100; ListNode* node = new ListNode(x); node->next = p->next; p->next = node; } ``` f. 遍历链表可以通过循环遍历链表节点来实现,以下是一个示例: ```c++ ListNode* p = head->next; while (p != NULL) { cout << p->val << " "; p = p->next; } cout << endl; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值