Studying-代码随想录训练营day3| 链表理论基础、203.移除链表元素、707设计链表、206反转链表

第三天💪(ง •_•)ง💪,编程语言:C++

目录

链表的理论基础

203.移除链表元素

707.设计链表

206.反转链表

总结


链表的理论基础

文档讲解:代码随想录链表理论基础

链表是一种通过指针将各节点串联在一起的线性结构。每一个节点一般由两部分组成,一个数据域,一个指针域(存放指向下一个节点的指针)。

特点:·1. 链表最后一个节点的指针域指向NULL(空指针)。2. 链表的入口节点称为链表的头节点head。

链表的类型:链表一般可分为三种类型,单链表、双链表、循环链表。

单链表:

双链表: 每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

循环链表(可用于解决约瑟夫环问题):链表首尾相连。

链表的存储方式:我们知道数组再内存中是连续分布的,但是链表在内存中不是连续分布的,而是散乱分布在内存中的某个地址上,分配机制取决于操作系统的内存管理

 

链表的代码定义(以单链表为例):

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

//初始化方式
//第一种
ListNode* head = new ListNode();
head->val = 5;

//第二种
ListNode* head = new ListNode(5);

//第三种
ListNode* tail = new ListNode(4);
ListNode* head = new ListNode(5, tail);

链表的操作: 

1.删除节点:链表可以通过截断要删除的节点和整个链表之间的联系,来实现节点的删除。要注意c++中被删除的节点空间不会被释放,需要手动进行空间释放,即delete删除节点。

2.插入节点:链表可以通增加对新节点的联系,来实现节点的插入。 

性能分析: 

注意:数组定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。但是链表的长度可以是不固定的,并且可以动态增删,适合数据量不固定,频繁增删,较少查询的场景。

 203.移除链表元素

文档讲解:代码随想录移除链表元素

视频讲解:手撕移除链表元素

 题目:

初看:有两个问题:1.如何遍历找到目标节点;2.如何删除目标节点。通过链表理论基础的学习,我们知道了如何删除目标节点,即让目标节点的前一个节点跳过目标节点指向下一个节点。遍历节点则可以通过给的头节点head依次向下遍历,但要注意,用这种方式如果头节点是目标节点,需要单独处理。

代码:

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头结点
        while (head != NULL && head->val == val) { // 注意这里不是if
            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;
    }
};

学习:使用头节点遍历的方式,需要单独处理头节点。通过视频学习,此题还可以通过设置一个虚拟头节点的方式,使得头节点变为第二个节点,实现对所有节点的统一处理。

代码:

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyhead = new ListNode();
        dummyhead->next = head;
        ListNode* cur = dummyhead;
        while (cur->next != nullptr) {
            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;
    }
};

收获:

  1. 链表中元素的遍历,不同于数组能够通过下标直接找到目标元素,而是需要从头节点开始依次遍历。当然如果是双链表,且设置了尾节点,也可以从后往前找。
  2. 如果需要处理链表中的头节点,也可以通过设置虚拟头节点的方式,使得头节点虚拟的变为第二节点,从而实现对所有节点的统一处理。

707.设计链表

文档讲解:代码随想录设计链表

视频讲解:手撕设计链表

 题目:

初看:第一想法是实际上就是需要设计一个类,并且类内需要实现添加,插入,查询,删除的操作。

学习:对于链表来说,链表的操作方式主要就分为两种:1.直接使用原来的链表来进行操作;2.设置一个虚拟头节点再进行操作。因此实际上就是先设计一个节点结构体,然后一一实现各操作。

节点设计:要注意还需要设置一个int型变量,统计链表内节点个数。

class MyLinkedList {
public:
    //创建一个单链表结构体
    struct ListNode {
        int val;
        ListNode* next;
        ListNode(int val) : val(val), next(nullptr) {}
    }; 
    //初始化
    MyLinkedList() {
        length = 0;
        dummyhead = new ListNode(0);
    }
private:
    //链表长度
    int length;
    //设置虚拟头节点
    ListNode* dummyhead;   
};

最终实现:

class MyLinkedList {
public:
    //创建一个单链表结构体
    struct ListNode {
        int val;
        ListNode* next;
        ListNode(int val) : val(val), next(nullptr) {}
    }; 

    MyLinkedList() {
        length = 0;
        dummyhead = new ListNode(0);
    }
    
    int get(int index) {
        if (index > length - 1 || index < 0 ) {
            return -1;
        }
        ListNode* cur = dummyhead;
        while(index--) {
            cur = cur->next;
        }
        return cur->next->val;
    }
    
    void addAtHead(int val) {
        ListNode* newNode = new ListNode(val);
        newNode->next = dummyhead->next;
        dummyhead->next = newNode;
        length++;
    }
    
    void addAtTail(int val) {
        ListNode* newNode = new ListNode(val);
        ListNode* cur = dummyhead;
        //不能写while(length--)会改变length的值
        while(cur->next != nullptr) {
            cur = cur->next;
        }
        cur->next = newNode;
        length++;
    }
    
    void addAtIndex(int index, int val) {
        if (index > length) {
            return;
        }
        if(index < 0) index = 0;
        ListNode* newNode = new ListNode(val);
        ListNode* cur = dummyhead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        length++;
    }
    
    void deleteAtIndex(int index) {
        if (index > length - 1 || index < 0) {
            return;
        }
        ListNode* cur = dummyhead;
        while(index--) {
            cur = cur->next;
        }
        ListNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp = nullptr;
        length--;
    }
private:
    //链表长度
    int length;
    ListNode* dummyhead;   
};

收获:

  1. 重点考察对于链表遍历,删除,插入的熟悉程度。以及对c++类设计的认识。

206.反转链表

文档讲解:代码随想录反转链表

视频讲解:手撕反转链表

题目:

初看:第一想法是从头节点开始遍历到最后一个节点,然后从最后一个节点开始往前进行处理,但发现由于是单链表的原因,到达最后一个节点后无法回头遍历。而从头节点开始,当头节点指向NULL后,第二个

学习:学习后明白,由于需要反转节点,且该链表为单链表,因此需要两个指针来进行节点反转的处理,同时还需要一个中间指针tmp保存下一个处理节点,才能保证节点遍历下去,而不丢失。

代码:

//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur != nullptr) {
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
        
    }
};

收获:

  1. 处理链表同样有双指针的方式,当发现一个指针不够处理的时候,要注意是否能够通过双指针来进行节点的保存和处理。
  2. 在进行链表循环处理的同时,重点要注意链表内的节点不能丢失。与数组相同,同样是坚持循环不变量原则,保证循环能够正常持续下去。

总结 

 第三天,反转链表未能解出😔😔,但学习到了新方法,继续加油(ง •_•)ง。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值