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

链表视频链接

链表基础:

注意链表需要初始化,力扣中已经初始化了,自己写需要加上。

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

203.移除链表元素

删除链表中等于给定值 val 的所有节点。

题目链接

视频链接

思路:

看完代码随想录之后的思路:

普通方法:头节点和其他节点有不同的处理方式:

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

虚拟头节点法:在开头加一个虚拟的头节点,这样就不用区分头节点是否需要删除,主要删除的部分可以统一为一种。注意开头和结尾,需要把虚拟头节点和实际头节点相互转换:实际的头节点可能会发生变化,所以需用使用head=dummyhead->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;
    }
};

遇到的困难:

普通方法:

其他节点的循环中,cur->next->val==val不能写在while中,需要写在里面的一层 if 中,否则头节点没找到val值直接就结束了。

虚拟头结点法:

虚拟头节点定义式,不能忘记 * 号

开头和结尾需要将虚拟头结点和实际头节点相互转换,且因为实际头节点可能发生变化,所以只能通过虚拟头节点的下一个来执行。

收获:

C++中链表节点的定义(ListNode* tmp=cur->next;)

链表单个元素指针的用法(->)

C++中内存的释放(delete tmp)

重点:

一定要写一个临时指针指向头节点

707.设计链表

设计链表中的增删功能

题目链接

视频链接

思路:

看完代码随想录之后的思路:

1.先判断输入参数(链表元素位置)的合法性

2.找到题目所需链表的位置,然后进行操作

class MyLinkedList {
public:
    //定义链表的结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    };
    //初始化链表,还需要末尾的私有才能使用,否则会报错:私有定义成员变量,只能类内使用
    //和类名相同的函数是构造函数,用于初始化变量,若不写系统自动生成一个构造函数
    MyLinkedList() {
        _dummyHead=new LinkedNode(0);
        _size=0;
    }
    
    int get(int index) {//这里链表的序号index从0开始取
        if(index<0||index>_size-1)
            return -1;
        LinkedNode* cur=_dummyHead->next;
        while(index--)
        {
            cur=cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* newNode=new LinkedNode(val);
        newNode->next=_dummyHead->next;//不是给newnode,是给newnode->next
        _dummyHead->next=newNode;
        _size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* newNode=new LinkedNode(val);
        LinkedNode* cur=_dummyHead->next;//提交时必须使用_dummyHead才可以,不太懂,不应该都一样吗
        while(cur->next!=nullptr)
        {
            cur=cur->next;
        }
        cur->next=newNode;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index>_size)return;//因为是void,所以返回不了-1
        if(index<0)index=0;
        LinkedNode* newNode=new LinkedNode(val);
        LinkedNode* cur=_dummyHead;//要在前面插入,需要知道前一个指针的地址(作为cur),所以不是dunmmyNode->next
        while(index--)
        {
            cur=cur->next;
        }
        newNode->next=cur->next;//index=链表长度在末尾加入,相当于把null向后推了一位
        cur->next=newNode;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if(index<0||index>_size-1)return;
        LinkedNode* cur=_dummyHead;
        
        while(index--)
        {
            cur=cur->next;
        }
        LinkedNode* tmp=cur->next;
        cur->next=cur->next->next;//空指针也可以直接赋值,这样末尾直接删掉
        delete tmp;
        _size--;
    }
    
private:
    int _size;
    LinkedNode* _dummyHead;
};

遇到的困难:

定义链表的结构体

使用了自引用结构体进行定义

在结构体内部引用这个结构体的名称,但是注意一定是使用指针的形式,否则这个结构体就构成了代码死循环。

struct LinkedNode {
        int val;  //链表节点上的函数
        LinkedNode* next;  //链表指向下一个节点的指针(自引用结构体)
        //这句式构造函数,自己可以不定义,C++默认生成一个构造函数
        LinkedNode(int val):val(val), next(nullptr){}
    };

结构体定义完成后进行初始化

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

ListNode* head = new ListNode(5);

默认构造函数初始化:

ListNode* head = new ListNode();  //初始化
head->val = 5;  //给变量赋值

公有和私有:

  • 类中:变量为属性,函数为方法
  • 为什么一定要用私有才能正常定义_dummyHead:私有中进行定义,这部分定义只有类内可以使用,两个优点:1.可以自己控制读写权限 2. 可以检测数据的有效性
  • 和类名相同的函数是构造函数,用于初始化变量,若不写系统自动生成一个构造函数

提交时在末尾加必须使cur等于_dummyHead,才会不报错

提交时的输入:

["MyLinkedList","addAtTail","get"]
[[],[1],[0]]

考虑特例:若原始链表是个空链表,那么cur定义为_dummyHead->next时,cur已经是个空指针了,没有cur->next可寻。

收获:

C++的结构体

类的属性和方法,公有和私有,构造函数

重点:

一定要注意边界的特殊条件:没有元素时、只有一个元素时

操作的第n个点,一定是cur->next,这样才能利用cur来增加或者删除。否则在增加或者删除时,会操作到n-1或者n+1。

添加节点时,一定先把后一个节点的地址写在新节点的指针上,再将新节点的地址写在前一个节点的指针上,否则顺序反了之后,后一个节点的地址就找不到了。

206.反转链表

将输入的链表翻转后输出

题目链接

视频链接

思路:

看完代码随想录之后的思路:

双指针法:

三个变量,cur、pre(原链表中的前一个)、temp(临时存放cur的变量),三个互相转换。

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

递归方法:

只存临时地址和翻转链接方向,不做推进的步骤(交给递归做)

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur)
    {
        if(cur==NULL)return pre;
        ListNode* temp=cur->next;
        cur->next=pre;
        return reverse(cur,temp);
    }
    
    ListNode* reverseList(ListNode* head) {
        return reverse(NULL,head);
    }
};

遇到的困难:

没遇到啥困难,递归很牛逼,用一步递归节省了两步推进操作。递归每次循环少1步操作,总体循环次数多1次,很划算。

收获:

递归:

递归就是在函数内部调用本函数,一直执行下去,自己写条件来终止——一般用 if 语句来终止。

若是没有终止命令,会因为数据存满栈而不返回或取出,导致栈溢出错误。

ListNode定义函数时使用的结构体指针。

重点:

双指针法返回的是pre,因为最后cur指向了NULL,新的head为pre

双指针循环的条件,否则容易出现空指针异常、死循环的错误

双指针写法的推进步骤,注意先把cur给pre,再把temp给cur,否则连不起来。

递归中的初始参数的赋值、递归函数中的 if 意义、循环递归中的参数赋值 不能搞混。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值