移除单向链表元素训练笔记

链表基础理论
(1)链表同数组一样,是一种能够表示一组数据的数据结构。其每个节点由数据域和指针域构成,其节点之间的链接是由指针完成。数组元素的存储空间是连续分布的,链表是通过指针域的指针链接内存中各个节点,所以链表中的节点在内存中不是连续分布的。这种结构将使得我们通过改变指针指向来实现链表元素的插入和移除。下面借用的是卡哥比较图。

(2)链表类型:单向链表(每个节点一个指针域,指向下一个节点)、双向链表(每个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。)、循环链表(尾指向首的链表)。
(3)链表节点结构体定义

struct ListNode{    //这行代码声明了一个名为ListNode的结构体。在 C++ 中,struct 和 class 类似,但struct 默认成员和成员函数的访问权限是 public,而 class 是 private。
    int val;        //声明整型数据成员;
    ListNode* next;   //声明指向ListNode的指针数据成员;
    ListNode(int x) : val(x), next(nullptr){}   //ListNode结构体的构造函数,构造函数是一种特殊的成员函数。
};

(4)在C++中,符号-> 是一个成员访问运算符,用于通过指针访问类或结构体的成员。例如,如果cur是一个指向ListNode结构体的指针,那么cur->val表示访问cur所指向的ListNodeval成员。

707设计链表:单链表中的节点应该具备两个属性:valnextval 是当前节点的值,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的节点。
(1)单链表

class MyLinkedList {
public:
    struct ListNode{
        int val;
        ListNode* next;
        ListNode (int x) : val(x), next(nullptr){}
    };
    MyLinkedList() {
        dummyhead = new ListNode(0);
        _size = 0;         //记录链表长度
    }

    int get(int index) {
        if (index > (_size - 1) || index < 0) {
            return -1;
        }
        ListNode* cur = dummyhead->next;
        while(index--){ // 如果--index就会陷入死循环
            cur = cur->next;
        }
        return cur->val;
    }
    
    void addAtHead(int val) {
        ListNode* newnode = new ListNode(val);
        dummyhead->next = newnode;
        newnode->next = dummyhead->next;
        _size++;
    }
    
    void addAtTail(int val) {
        ListNode* newnode = new ListNode(val);
        ListNode* cur = nullptr;
        ListNode* temp = dummyhead; 
        while (temp != nullptr) {
            cur = temp;
            temp = temp->next;
        }
        cur->next = newnode;
        _size++;
    }
    
    void addAtIndex(int index, int val) {
        if(index > _size) return;
        if(index < 0) index = 0;  
        ListNode* newnode = new ListNode(val);
        ListNode* cur = dummyhead;
        while (index--) {  
            cur = cur->next;  //cur到了第index个元素,但其下标为index-1
        }
        cur->next = newnode;
        newnode->next = cur->next;
        _size++;
    }
    
    void deleteAtIndex(int index) {
        if(index > _size - 1 || index < 0) return;  
        ListNode* cur = dummyhead;
        while (index--) {  
            cur = cur->next;  //cur到了第index个元素,但其下标为index-1,因此cur->next是要删除的节点。
        }
        ListNode* temp = cur->next;
        cur->next = cur->next->next;
        delete temp;
        //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针,之后的程序不小心使用了tmp会指向难以预想的内存空间。
        temp=nullptr;
        _size--;
    }
private:
    int _size;
    ListNode* dummyhead;
};

[203.移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements/):给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回新的头节点 。
(1)思路:使用循环移除多个目标值节点;若头节点为目标节点,可以直接向后移动头节点,也可以引入虚拟头节点,使得所有节点的操作一致。
(2)直接在原链表基础上求解

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while (head != nullptr && head->val == val){
            ListNode* temp = head;
            head = head->next;
            delete temp;  //若头节点为要删除的点
        }
        ListNode* cur = head;
        while (cur != nullptr && cur->next != nullptr){
            if (cur->next->val == val){
                ListNode* temp = cur->next;
                cur->next = cur->next->next; //cur作为要删除值的前一个节点
                delete temp;
            }
            else cur = cur->next;
        } 
        if (cur == nullptr) return head; //整个链表中都没有找到val值元素
        return head; 
    }
};

空间复杂度:O(n);时间复杂度:O(1)
(3)设置虚拟头节点(简化边界条件处理、统一操作接口、隐藏实际头结点)

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头节点
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        while (cur != nullptr && cur->next != nullptr){
            if (cur->next->val == val){ //直接比较下一个节点的值,将当前节点视为移除节点的前一个节点
                ListNode* temp = cur->next;
                cur->next = cur->next->next; //cur作为要移除值的前一个节点
                delete temp;
            }
            else cur = cur->next;
        } 
        head = dummyHead->next;
        delete dummyHead;
        return head; 
    }
};

空间复杂度:O(n);时间复杂度:O(1)
(5)练习过程出现的困难:① 最初思路为利用循环找到目标结点,再移除,这导致至多只能移除一个目标节点。② 无法准确表示目标节点的前一个节点

posted @ 2024-11-30 16:21  TomatoErn  阅读(25)  评论(0)    收藏  举报