代码随想录算法训练营:3/60

非科班学习算法day3 | LeetCode203: 移除链表元素,Leetcode707: 设计链表,Leetcode206: 反转链表

目录

介绍

一、基础概念补充:

1.c++中的new

基本数据类型的动态分配:

类类型的动态分配:

new与数组的动态分配:

类类型的动态分配数组:

new与有参构造函数的结合使用:

2.链表

C++链表概述

C++单向链表构建

 C++链表增删节点思路

二、LeetCode题目

1.LeetCode203: 移除链表元素

题目解析

2.Leetcode707: 设计链表

题目解析

定义结构体

 获取下标为index的节点的值

 头部添加节点

  在尾部添加节点

 在index插入节点val

 删除下标为index的节点

3.Leetcode206:反转链表

题目解析

 

总结


介绍

包含LC的两道题目,还有相应概念的补充。

相关图解和更多版本:

代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF


一、基础概念补充:

1.c++中的new

        在C++中,new运算符用于动态地分配内存。它执行两个主要操作:首先,它从堆区域中分配一块内存,这块内存的大小足以容纳特定类型(或对象)的数据;其次,它调用该类型的构造函数来初始化这块内存中的对象(如果是类类型)

基本数据类型的动态分配:

int* p_int = new int; // 分配一个int大小的内存,并返回指向它的指针

类类型的动态分配:

// 假设有一个类MyClass
class MyClass {
public:
    MyClass() { std::cout << "Constructor called\n"; }
    ~MyClass() { std::cout << "Destructor called\n"; }
};

MyClass* p_myclass = new MyClass(); // 分配MyClass对象的内存,并调用构造函数

new与数组的动态分配:

int* arr = new int[10]; // 分配一个包含10个int的数组

类类型的动态分配数组:

// 假设有一个类MyClass
MyClass* arr_myclass = new MyClass[5]; // 分配一个包含5个MyClass对象的数组

new与有参构造函数的结合使用:

// 假设有一个类MyClass,它有一个带参数的构造函数
class MyClass {
public:
    MyClass(int value) : data(value) { std::cout << "Constructor with value: " << data << '\n'; }
    ~MyClass() { std::cout << "Destructor called\n"; }
private:
    int data;
};

MyClass* p_myclass = new MyClass(42); // 分配MyClass对象的内存,并调用带参数的构造函数

new必须delete的配对使用,否则可能造成内存泄漏

释放单个对象的内存:

delete p_int; // 释放p_int指向的int内存
delete p_myclass; // 释放p_myclass指向的MyClass对象的内存

释放数组的内存:

delete[] arr; // 释放arr指向的int数组的内存
delete[] arr_myclass; // 释放arr_myclass指向的MyClass数组的内存

2.链表

C++链表概述

        链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。在C++中,链表通常通过结构体或类来实现,每个节点包含数据部分和指针部分。链表的特点是节点在内存中不连续存储,而是通过指针相互链接. 

        基本操作包括创建链表、插入节点、删除节点、查找节点和遍历链表等。插入和删除节点的时间复杂度通常为O(1),而查找节点的时间复杂度为O(n),因为需要从头节点开始逐个遍历。可以看出来频繁的插入和删除的操作用链表是有优势的。

C++单向链表构建

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

 C++链表增删节点思路

强烈推荐统一养成虚拟头节点的方法解决问题。

代码随想录 (programmercarl.com)icon-default.png?t=N7T8https://programmercarl.com/%E9%93%BE%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E9%93%BE%E8%A1%A8%E7%9A%84%E7%B1%BB%E5%9E%8B

二、LeetCode题目

1.LeetCode203: 移除链表元素

题目链接:203. 移除链表元素 - 力扣(LeetCode)

题目解析

        首先,采用虚拟头节点的方法。创建一个新的节点,将节点的尾部指向现在的头部(head),并且创建遍历指针。

        寻找目标值,调整链表指向。

 c++代码如下:

/**
 * 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* removeElements(ListNode* head, int val) 
    {
        //创建虚拟头节点,并设置为0
        ListNode* dummyhead = new ListNode(0);
        //将虚拟头节点添加到头部
        dummyhead->next = head;
        //设置循环指针指向虚拟头节点
        ListNode* cur = dummyhead;

        while(cur->next != nullptr)
        {
            if(cur->next->val == val)
            {
                //记录需要删除的节点位置
                ListNode* temp = cur->next;

                //更新链表顺序
                cur->next = cur->next->next;

                //手动释放内存
                delete temp;
            }
            else
            {
                //移动检索指针
                cur = cur->next;
            }
        }
        //返回头部节点
        head = dummyhead->next;
        delete dummyhead;
        return head;   
    }
};

注意点1:对于new出来的空间,要手动管理,所以在最后将dummyhead单独删除,防止内存泄漏;同样的,对于不需要的空间,手动删除不需要的节点temp

注意点2:关于边界的设置cur->next != nullptr,这里要注意检查的是下一个节点是否存在,cur != nullptr是检查指针指向是不是空,是完全不一样的概念

注意点3:关于cur指针,首先最后是需要返回头节点,也就是head,而head现在依靠的是dummyhead的指向,所以不要直接移动头节点,而是选择设立新的指针;其次指针重新指向的过程需要的是将cur节点的下一位替换为cur节点的下一位的下一位。

2.Leetcode707: 设计链表

题目链接:

题目解析

       主要是借助虚拟头节点,将操作统一化

 C++代码如下: 

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

    //初始化函数
    MyLinkedList() 
    {
        dummyhead = new LinkedNode(0);
        size = 0;
    }
    

    //获取下标为index的节点的值
    int get(int index) 
    {
        if(index < 0 || index >= size)
        {
            return -1;
        }

        //设置检索指针
        LinkedNode * cur = dummyhead->next;
        
        //搜索!
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        return cur->val; 
    }
    
    //头部添加节点-ok
    void addAtHead(int val) 
    {
        LinkedNode * newnode = new LinkedNode(val);
        newnode->next = dummyhead->next;
        dummyhead->next = newnode;
        size++;
    }
    
    //在尾部添加节点-ok
    void addAtTail(int val) 
    {
        LinkedNode * cur = dummyhead;
        while(cur->next != nullptr)
        {
            cur = cur->next;
        }
        LinkedNode * newnode = new LinkedNode(val);
        newnode->next = nullptr;
        cur->next = newnode;
        size++;
    }
    
    //在index插入节点val-ok
    void addAtIndex(int index, int val) 
    {
        if(index > size)
        {
            return;
        }
        LinkedNode * newnode = new LinkedNode(val);
        LinkedNode * cur = dummyhead;
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        newnode->next = cur->next;
        cur->next = newnode;
        size++;
    }
    
    //删除下标为index的节点 - ok
    void deleteAtIndex(int index) 
    {
        if(index >= size || index < 0)
        {
            return;
        }
        LinkedNode * cur = dummyhead;
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        LinkedNode* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
        temp = nullptr;
        size--;
    }
private:
    int size;
    LinkedNode* dummyhead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

定义结构体

属于常规操作

 struct LinkedNode
    {
        int val;
        LinkedNode* next;
        LinkedNode(int x):val(x),next(nullptr){}        
    };

 获取下标为index的节点的值

先对输入进行判定!如果不符合检索条件,直接返回-1;接下来首先将指针指向链表头部,因为这里的要求是,首位是0开始检索index(其实定义在dummyhead也可以写,不过个人感觉不够工整,和后面的条件也不够统一,书写过程中容易搞混乱),接下来借助于index--来进行链表的检索。

 //获取下标为index的节点的值
    int get(int index) 
    {
        if(index < 0 || index >= size)
        {
            return -1;
        }

        //设置检索指针
        LinkedNode * cur = dummyhead->next;
        
        //搜索!
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        return cur->val; 
    }

 头部添加节点

先设置虚拟头节点,然后将新增节点的尾部指向头节点,再将虚拟头节点的尾部指向新增节点,构成新的链表,这样子做是为了防止next无法准确找到,所以一定要注意顺序。不要忘记调整链表长度便于维护。

    //头部添加节点-ok
    void addAtHead(int val) 
    {
        LinkedNode * newnode = new LinkedNode(val);
        newnode->next = dummyhead->next;
        dummyhead->next = newnode;
        size++;
    }

  在尾部添加节点

和头部添加节点的思路是一样的,不过这里显示要进行链表的遍历,先找到需要的位置,即尾部;然后再将新建节点的尾部指向空,最后添加到原有的链表中。不要忘记调整链表长度便于维护。

    //在尾部添加节点-ok
    void addAtTail(int val) 
    {
        LinkedNode * cur = dummyhead;
        while(cur->next != nullptr)
        {
            cur = cur->next;
        }
        LinkedNode * newnode = new LinkedNode(val);
        newnode->next = nullptr;
        cur->next = newnode;
        size++;
    }
    

 在index插入节点val

这一步类似于之前的总结。首先检查条件是否满足检索要求,不满足直接返回;之后遍历链表直到检索位置,新建节点,将新节点插入。同样的,插入时候注意指向的顺序。不要忘记调整链表长度便于维护。

    //在index插入节点val-ok
    void addAtIndex(int index, int val) 
    {
        if(index > size)
        {
            return;
        }
        LinkedNode * newnode = new LinkedNode(val);
        LinkedNode * cur = dummyhead;
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        newnode->next = cur->next;
        cur->next = newnode;
        size++;
    }
    

 删除下标为index的节点

    //删除下标为index的节点 - ok
    void deleteAtIndex(int index) 
    {
        if(index >= size || index < 0)
        {
            return;
        }
        LinkedNode * cur = dummyhead;
        while(index > 0)
        {
            cur = cur->next;
            index--;
        }
        LinkedNode* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
        temp = nullptr;
        size--;

3.Leetcode206:反转链表

题目链接:206. 反转链表 - 力扣(LeetCode)

题目解析

       首先基于链表的数据结构,可以知道,反转链表可以采用逐个,更改指向。那么问题是如果我定义一个指针cur,当我用cur->next指向了cur,因为指向发生了变化,那么cur->next->next就会造成丢失或者错位!所以可以采用两个指针来进行分别的检索和保存。下面具体实现一下。

C++代码如下:

/**
 * 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) 
    {
        //定义顺序链表的头部
        ListNode * cur1 = head;

        //定义反向链表的尾部
        ListNode * cur2 = nullptr;

        //定义临时变量
        ListNode * temp;

        while(cur1 != nullptr)
        {
            //保存顺序链表下一位
            temp = cur1->next;

            //重新指向
            cur1->next = cur2;

            //移动指针
            cur2 = cur1;
            cur1 = temp;
        }
        //返回新链表的尾部
        return cur2;
    }
};

注意点1:补充一下,这里定义temp应该指向空,防止造成野指针。

//定义临时变量
ListNode * temp = nullptr;

注意点2:cur1指向起始位置,进行链表的正向检索;cur2指向空,用来作为新链表的尾部来接受指向;想移动cur1要借助提前保存好下一位置的指针。

注意点3:最后的返回条件是cur2,因为此时,cur1已经指向了链表原来顺序的尾部(null)

更为详细的图解见:代码随想录 (programmercarl.com)

 

总结


打卡第三天,坚持!!!

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值