代码随想录day03| 203.移除链表元素、707.设计链表、206.反转链表

提示:DDU,供自己复习使用。欢迎大家前来讨论~


链表

进入链表的学习

一、链表的基础理论

1. 链表的分类

  • 单链表
  • 双链表
  • 循环链表

链表与数组的区别:链表在内存中是不连续的,数组在内存中连续存放

2. 链表的定义

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

如果使用默认的构造函数,在初始化的时候不能直接给变量赋值

//采用自己的构造函数初始化节点
ListNode* head = new ListNode(6);

//采用默认的构造函数
ListNode* head = new ListNode();
head->val = 6;

3. 链表的操作

二、题目

题目一:203.移除链表元素

1.在原链表上进行操作

在原始的链表中进行删除,要==区分是否头节点==两种情况,分别进行操作。

使用 tmp 的原因:

  • 安全删除:在删除节点之前,需要先保存节点的地址,因为一旦执行 head = head->next;,原始头结点的地址就会丢失,无法进行删除操作。
  • 避免内存泄漏:删除节点是为了释放不再使用的内存,如果不使用 tmp 来保存节点的地址,直接执行 delete head; 会导致在更新 head 指针后无法释放原头结点的内存,从而造成内存泄漏。
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //删除头节点
        while(head != NULL && head->val == val){
            ListNode* tmp = head;
            head = head->next;
            delete tmp;
        }    

        //删除非头节点
        //cur 是遍历链表的中所有位置(区别head),帮助定位和删除链表中特定值的节点
        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;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

2.使用虚拟头节点(dummyHead)

使用虚拟头节点(dummyHead),可以统一删除头节点和非头节点的操作。
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;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

题目二:707.设计链表

练习使用虚拟头节点。

链表的增删改查,需注意:

  1. 所有的操作都要定义虚拟节点
  2. 节点的index是从0开始,判断index的合法性。
  3. 添加和删除节点的时候,因为单链表只能往后,不能往前,所以定义一个curent节点,添加和删除都是对应current->next这个节点的位置。
class MyLinkedList {

public:
    //定义链表节点结构体
    struct LinkedNode {
        int val;  //这是一个放数值的变量,要和下面的构造保持一致。
        LinkedNode *next;
        LinkedNode(int x) : val(x), next(nullptr){}
    };

    //初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0);
        _size = 0;
    }

    //获取第n个节点的数值
    int get(int n){
        if(n<0 || n>_size-1){
            return -1;
        }
        LinkedNode* cur = _dummyHead->next;
        while(n--){
            cur=cur->next;
        }

        return cur->val; 
    }

    //在头节点前插入新的节点
    void addAtHead(int x){
        LinkedNode* newNode = new LinkedNode(x);
        newNode->next = _dummyHead->next;
        _dummyHead->next = newNode;
        _size++;
    }

     // 在链表最后面添加一个节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
        _size++;
    }


    //在第n个位置插入新的节点
    void addAtIndex(int index, int x){
        if(index > _size) return;
        if(index<0) index = 0;
        LinkedNode* newNode = new LinkedNode(x);
        LinkedNode*  cur = _dummyHead;

        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        _size++;
    }

    //删除第n个位置的节点
    void deleteAtIndex(int index){
        if(index>=_size || index<0){
            return ;
        }

        LinkedNode* cur = _dummyHead;
        while(index--){
            cur = cur->next;
        }
        LinkedNode* tmp = cur->next;
        cur->next = cur->next->next;
        delete tmp;
        tmp=nullptr; //tmp不主动赋值,他会是一个随机值,可能是乱指的野指针。
        _size--;
    }
    
    // 打印链表
    void printLinkedList() {
        LinkedNode* cur = _dummyHead->next; // 从虚拟头结点的下一个节点开始遍历
        while (cur != nullptr) {
            cout << cur->val << " "; // 打印当前节点的值
            cur = cur->next;
        }
        cout << endl;
    }

private:
    int _size;
    LinkedNode* _dummyHead;
};

题目三:206.反转链表

思路:不需要重新定义一个链表,改变链表next的指向就可以了。
  1. 双指针操作,必须要掌握。
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;
        ListNode* pre = NULL;
        ListNode* tmp; //需要一个临时的节点记录后移。
        while(cur){
            tmp = cur->next;
            cur->next = pre;
            pre = cur; //顺序不能反
            cur = tmp; 
        }
        return pre;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
  1. 递归的写法,有些抽象,但和双指针的思想是一致的。
class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur){
        if(cur==NULL) return pre;
        ListNode* tmp;
        tmp = cur->next;
        cur->next = pre;
        return reverse(cur, tmp);//递归下一轮
    }
    ListNode* reverseList(ListNode* head) {
        //和双指针的思路一致    
        return reverse(NULL, head);
    }
};
  • 时间复杂度: O(n), 要递归处理链表的每个节点
  • 空间复杂度: O(n), 递归调用了 n 层栈空间

总结

  • 使用虚拟头节点
  • 反转链表中的双指针思想和递归方法
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值