您现在的位置是:首页 >学无止境 >代码随想录算法训练营15期 Day 3(补)|203. 移除链表元素、707. 设计链表、206. 反转链表网站首页学无止境

代码随想录算法训练营15期 Day 3(补)|203. 移除链表元素、707. 设计链表、206. 反转链表

不让_i7 2024-07-01 11:59:55
简介代码随想录算法训练营15期 Day 3(补)|203. 移除链表元素、707. 设计链表、206. 反转链表

力扣 203.移除链表元素(简单)

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

题解1:原链表操作

1.删除头节点

先判断头节点不为空且头节点中val为target值,将头指针head转到head->next。但在c++编程中,链表残余内存需要手动删除,这里创建临时指针tmp,并在后面使用delete指令删除。

2.删除非头节点

从头节点下一节点开始判断,如判断确定是target值,则将该节点删除(同样需要手动清除内存),若判断下一节点val不是target值,那么临时指针从head后移,继续判断。

/**
 * 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) {
        //处理移除头节点
        while(head != NULL && head->val == val){
            ListNode* tmp = head ;//创建一个临时指针指向头节点
            head = head->next;//因为要删除头节点,所以要删除该节点,新的头节点为原头节点的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 ;
    }
};

题解2:虚拟头节点

由于考虑在原链表直接操作,每次都得需要做两次操作。

因为我们采用创立虚拟头节点的方法,那么处理后面原链表直接相当于直接不考虑头节点是目标节点的情况。(和题解1中解法大意相同)

但是最后在返回新的头节点时,注意新的头节点一定是虚拟节点的下一位。

/**
 * 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) {
        ListNode* dummyHead = new ListNode(0) ;//创立一个虚拟节点
        dummyHead -> next = head;//虚拟头节点指向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;//返回新的头节点
    }
};

力扣 707.设计链表(中等)

题目:

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

MyLinkedList() 初始化 MyLinkedList 对象。
int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

题解:链表常见的六个操作

注意:链表节点的结构体定义

class MyLinkedList {
public:
    //定义链表节点的结构体
    struct LinkedNode {
        int val;//节点上存储的元素
        LinkedNode* next;//指向下一节点的指针
        LinkedNode(int val):val(val), next(nullptr){} //节点的构造函数
    };//!!!!!!!这里要加;;;;;;;;
    //初始化链表
    MyLinkedList() {
        //这里定义的头节点是一个虚拟头节点,不是真正的链表头节点
        dummyHead = new LinkedNode(0);
        size = 0 ;
    }
    //获取第index个节点的数值,如果index是非法数值则直接返回-1
    //注意index是从0开始的,第0个节点就是头节点
    int get(int index) {
        if(index > (size - 1) || index < 0 ){
            return -1 ;
        }
        LinkedNode* cur = dummyHead->next;//创立一个指针指向头节点(第0个节点)
        while(index){//while(index--)
            cur = cur->next ;//指针指向下一个节点
            index--;//index自减,再次判断,也可以将index--直接放到while循环里
        }
        return cur->val;
    }
    //在链表最前面插入一个节点,插入完成后,新插入的节点为链表新的头节点
    void addAtHead(int val) {
        LinkedNode* newNode_0 = new LinkedNode(val);//先生成一个新节点
        newNode_0 -> next = dummyHead -> next;//这地方的一个重点,要先把原头节点的上一指针先赋值给newNode,防止后面丢失
        dummyHead->next = newNode_0;//上面的坑,在这里体现,如果前面的dummyHead->next不先赋值给newNode,丢失
        size++;//什么意思?--增节点
    }
    //在链表最后面插入一个节点,只需要找到最后一个节点位置,并插入新节点即可
    void addAtTail(int val) {
        LinkedNode* newNode_1 = new LinkedNode(val);//先生成一个新节点
        LinkedNode* cur = dummyHead;//创立一个指针指向头节点
        while(cur->next != nullptr){//这里是为了将指针指向最后一个节点  ! ! c++11刚定义的代表空指针 !!
            cur = cur->next;
        }
        cur->next = newNode_1;//找到最后一个节点,并使他指向新节点即可。
        size++;//什么意思?--增节点
    }
    //在链表中下标为index的节点之前插入新节点
    //特殊情况 1.若index为0 则为新插入头节点 2.若index为链表长度 则为新插入尾节点
    //如果index大于链表长度,则返回空
    void addAtIndex(int index, int val) {
        if(index > size){
            return;
        }
        LinkedNode* newNode_2 = new LinkedNode(val);//新建立一个节点
        LinkedNode*cur = dummyHead;//cur指针指向虚拟头节点
        while(index){//因为是在链表中下标为index之前插入新节点,因此我们要先找到这个节点。//这里如果不理解,可以带个特殊值试试,比如index=0.
            cur = cur->next;
            index--;
        }
        newNode_2->next = cur->next;//这个地方和上面的坑是一样的,不要丢失cur->next哦!
        cur->next = newNode_2;
        size++;
    }
    //删除第index个节点,若index大于或者等于链表长度,则直接返回;
    //注意index是从0开始的
    void deleteAtIndex(int index) {
        if(index >= size || index < 0){
            return;
        }
        LinkedNode* cur = dummyHead;
        while(index){
            cur = cur->next ;
            index--;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        size--;//链表长度减1
    }
    //打印链表
    /*void printLinkedList(){
        LinkedNode*cur = dummyHead;
        while(cur->next != nullptr){
            cout<<cur->next->val<<"";
            cur = cur->next;
        }
        cout<<endl;
    }*/
    private:
        int size;
        LinkedNode* dummyHead;//类MyLinkedList的私有部分(private)声明了两个成员变量
};

/**
 * 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.反转链表

题解1:双指针

注意两个指针,pre在前指向nullptr,而cur指向的是头节点。

当cur出链表为空时,链表反转结束。

此处需要注意的是两个位置的提前保存,就是反转前的cur->next和pre后移前的cur。

/**
 * 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* cur = head;//定义指针指向头节点head
        ListNode* pre = nullptr;//指针指向空节点
        while(cur){//判断什么时候结束遍历
            ListNode*tmp = cur->next;//防止节点丢失,先保存cur->next
            cur->next = pre;//链表反转,将cur->next指向pre
            pre = cur;//新的pre移动-后移
            cur = tmp;//新的cur移动-后移
        }
        return pre;//返回新链表头节点
    }
};

题解2:递归写法(还不太会)

/**
 * 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* reverse(ListNode* pre,ListNode* cur) {
        //递归写法
        if(cur == nullptr){
            return pre;//结束条件
        }
        ListNode* tmp = cur->next;//保存cur->next位置
        cur->next = pre;//翻转
        return reverse(cur,tmp);
    }
    ListNode* reverseList(ListNode* head){
        //和双指针初始化是一样的逻辑
        //ListNode* cur = head;
        //ListNode* pre = nullptr;
        return reverse(nullptr,head);
    }
};

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。