【C++编程能力提升】

代码随想录训练营Day3 | Leetcode203、707、206

  • 一、203 移除链表元素
  • 二、707 设计链表
  • 三、206 反转链表
    • 1、方法1:双指针法
    • 2、递归法

一、203 移除链表元素

题目链接:203 移除链表元素

核心:使用虚拟节点dummyHead,即head前增加一个val=0的节点,目的是从第一个节点开始查找元素;要求移除某元素,意味着需要遍历到该移除元素的前一个节点(cur),而不是当下要移除的节点(cur->next),移除时令前一个节点指向该移除节点的下一个节点,移除之后需delete该节点。
方法:初始化时令cur指向dummyHead,遍历链表节点时从cur->next开始,就能从第一个节点开始遍历。
若不使用虚拟节点dummyHead,此时遍历时实际是从第二个节点开始,即跳过了第一个节点,因此需要在循环结束后单独对第一个节点判断;而且也需要在程序最开始处判断当前链表是否为空。

    ListNode* removeElements(ListNode* head, int val) {
    //使用虚拟头节点dummyHead,位于当前head前一个,目的是能遍历所有元素
    ListNode* dummyHead=new ListNode(0);    //注意如何初始化
    dummyHead->next=head;   
    ListNode* cur=dummyHead;    //由cur->next遍历该链表
    while(cur->next)
    {//实际从第一个节点开始遍历,无需对第一个节点特殊处理
        if(cur->next->val==val)
        {
            ListNode* temp=cur->next;
            cur->next=temp->next;
            delete temp;
        }
        else 
            cur=cur->next;
    }
    return dummyHead->next; //返回的是虚拟节点的下一个,而不再是head!

    /*
    //未使用虚拟头节点
    if(head==NULL)
        return head;
    ListNode* cur=head;//记录原链表的头结点
    while(cur->next)
    {//实际从第二个节点开始遍历
        if(cur->next->val==val)
        {
            ListNode* temp=cur->next;   //记录待删除的节点
            cur->next=temp->next;       //当前节点的下一个节点跳过待删除节点
            delete temp;                //必须删除该节点
        }
        else 
            cur=cur->next;      
    }
    if(head->val==val)
    {//判断第一个head节点是否为val
        ListNode* temp=head;
        head=head->next;
        delete temp;   
    }
    return head;
    */
    }

二、707 设计链表

题目链接:707 设计链表

核心:
第一,定义链表节点的结构体;
第二,引入虚拟节点,方便插入函数、查找函数、删除函数的实现;
第三,熟悉类的编写,成员变量应在private里,public里的成员函数无需定义成员变量。
第四,删除函数在删除某节点后,先使用delete释放该节点的内存,再令该节点赋值为nullptr,因为释放内存后该节点是一个野指针,可能为任意值。

class MyLinkedList {
public:
    //定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val),next(nullptr) {}   //构造函数
    };
    //引入虚拟节点
    //初始化链表
    MyLinkedList() {
        dummyHead=new LinkedNode(0);    //记住:dummyHead的类型不在此定义!
        size=0;     //size的类型也不在此定义,都在private里单独定义
    }
    //获取下标为index的节点元素
    int get(int index) {
        if(index<0 || index>=size)
            return -1;
        LinkedNode* cur=dummyHead->next;
        while(index--)
        {//直到index==0时cur指向元素即index节点
            cur=cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        LinkedNode* newNode=new LinkedNode(val);
        newNode->next=dummyHead->next;
        dummyHead->next=newNode;
        size++;
    }
    
    void addAtTail(int val) {
        LinkedNode* newNode=new LinkedNode(val);
        LinkedNode* cur=dummyHead;//注意此时cur初始化为虚拟节点,而不再是第一个节点
        while(cur->next)
        {//由于退出循环时要求cur必须指向最后一个节点,而不是nullptr
            cur=cur->next;
        }
        cur->next=newNode;
        size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index>size) 
            return;//index大于长度则return,表示不会插入节点
        if(index<0)
            index=0;   //特殊情况特殊处理
        LinkedNode* newNode=new LinkedNode(val);
        LinkedNode* cur=dummyHead; //必须初始化为虚拟节点
        while(index--)
        {//退出循环时要求cur指向待插入节点的前一个,否则无法插入新节点
            cur=cur->next;
        }
        newNode->next=cur->next;
        cur->next=newNode;
        size++;
    }
    
    void deleteAtIndex(int index) {
        if(index<0 || index>=size)
            return;
        LinkedNode* cur=dummyHead;
        while(index--)
        {//退出循环时要求cur指向index前一个节点
            cur=cur->next;
        }
        LinkedNode* temp=cur->next;
        cur->next=temp->next;
        delete temp;
        temp=nullptr;   //注意:需令释放内存后的temp赋值为空
        size--;
    }
private:
    LinkedNode* dummyHead;  
    int size;
};

/**
 * 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);
 */

三、206 反转链表

题目链接:206 反转链表

核心:反转链表实质是改变各节点的next指向,具体而言,原链表第一个节点反转后应指向nullptr,第二个节点指向第一个节点,以此类推,最后一个节点指向倒数第二个节点,反转后的头结点head即原链表的最后一个节点。
第一,cur指向原链表的head,目的是遍历各节点,直到最后一个节点;
第二,pre初始化指向nullptr,实质是与cur实现反转,即cur->next=pre;
第三,完成一次反转需立即更新pre和cur,先pre后cur,因为pre是更新到此时cur所在节点,而cur是更新到反转前的下一个节点,最后一次更新后,cur指向nullptr,pre指向原链表的最后一个节点,即反转后链表的头结点。

1、方法1:双指针法

令pre和cur分别为两个双指针,cur遍历当前链表元素,直至最后一个元素结束遍历;pre为反转前cur的前一个元素,即在pre和cur之间实现两个节点的反转;一次反转之后需更新pre和cur指针,均向前移一个节点,即pre=cur,cur=cur->next。

    ListNode* reverseList(ListNode* head) {
        ListNode* cur=head;
        ListNode* pre=nullptr;  //记录反转后cur的下一个节点
        ListNode* temp; //记录反转前cur的下一个节点
        while(cur)
        {//cur从head遍历链表各节点
            temp=cur->next; 
            cur->next=pre;  //reverse
            //更新cur和pre,必须先更新pre后cur 
            pre=cur;    //最后一次更新后pre指向反转前链表的最后一个节点
            cur=temp;   //最后一次更新后cur指向nullptr,          
        }
        return pre;
    }

2、递归法

实质与双指针相同,首先需构造一个实现两节点反转的函数,即传入参数是双指针pre和cur;
(1)基底条件是cur遍历完链表所有元素,即cur为空时返回此时cur的前一个节点pre;
(2)递归条件是cur非空时(cur指向节点有效),需与前一个节点pre完成反转,反转之后进入下一次递归,传入参数需更新,即pre=cur,cur=cur->next。

    ListNode* reverse(ListNode* pre, ListNode* cur)
    {//构造一个节点反转的函数,传入的参数即双指针法的pre和cur
        if(!cur)
            return pre; //base case
        ListNode* temp=cur->next; //recursion case 
        cur->next=pre;  //reverse
        return reverse(cur,temp);
    }

	ListNode* reverseList(ListNode* head) {
        //递归法,实质与双指针相同
        ListNode* cur=head;
        ListNode* pre=nullptr;
        return reverse(pre,cur);
    }

你可能感兴趣的:(C++编程,c++,链表,leetcode)