代码随想录训练营第三天|27.707.206

本文详细介绍了链表的定义、不同类型(单链表、双链表和循环链表),以及链表的存储方式。重点讲解了链表的删除和添加操作,性能分析,并给出了LeetCode题目中移除元素的方法,包括使用C++和C实现的两种删除策略:常规删除和虚拟头节点法。
摘要由CSDN通过智能技术生成

一、链表

1.定义:

        链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。

如图所示: 

链表1

2.类型

1.单链表

               单链表中的指针域只能指向节点的下一个节点。

2.双链表

     双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

     双链表 既可以向前查询也可以向后查询。

如图所示: 

链表2

3.循环链表

循环链表,顾名思义,就是链表首尾相连。

循环链表可以用来解决约瑟夫环问题。

链表4

3.链表的存储方式

        数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

        链表是通过指针域的指针链接在内存中各个节点。

        所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

如图所示:

链表3

        这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。

4.链表的定义

C/C++

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

        不定义构造函数行不行,答案是可以的,C++默认生成一个构造函数。

但是这个构造函数不会初始化任何成员变量,下面我来举两个例子:

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

ListNode* head = new ListNode(5);

使用默认构造函数初始化节点:

ListNode* head = new ListNode();
head->val = 5;

所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!

#链表的操作

#删除节点

删除D节点,如图所示:

链表-删除节点

只要将C节点的next指针 指向E节点就可以了。

那D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。

是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。

其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

#添加节点

如图所示:

链表-添加节点

可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。

但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

#性能分析

再把链表的特性和数组的特性进行一个对比,如图所示:

链表-链表与数据性能对比

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

相信大家已经对链表足够的了解,后面我会讲解关于链表的高频面试题目,我们下期见!

#其他语言版本

二、(27. 移除元素 - 力扣(LeetCode)

1.题目

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

示例 1:

2.思路

        因为链表的头节点前没有前一个结点,所以有两种解决办法:

1.将head头节点向后移动一位                                       2.设置一个虚拟头节点(dummy node)

  先删头节点,再删非头节点

        时间复杂度O(n)

        空间复杂度O(1)

第一种移除方法:

C++(delete释放空间)

/**
 * 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;     //创建一个临时指针 tmp,将当前节点 head 赋值给它。这样做是为了稍后删除当前节点。
            head = head->next;        //将当前节点的下一个节点赋值给 head,相当于移动 head 指针到下一个节点,准备继续检查下一个节点的值
            delete tmp;               //删除临时指针 tmp 指向的节点,即删除原来 head 指向的节点。这样就实现了删除当前节点的操作。
        }

        //删除非头节点
        ListNode* cur = head;  //创建一个指针 cur,将头节点 head 赋值给它。这样 cur 将用于遍历单链表。
        while(cur != NULL && cur->next != NULL){ //这是一个 while 循环的开始,条件部分包括两个条件:第一个条件是 cur != NULL,确保当前节点不为空;第二个条件是 cur->next != NULL,确保当前节点的下一个节点不为空。这样做是为了在遍历链表时能够安全地访问下一个节点。
            if(cur->next->val == val){//检查当前节点的下一个节点的值是否等于给定的值 val。
                ListNode* tmp = cur->next;     //创建一个临时指针 tmp,将当前节点的下一个节点赋值给它。这样做是为了稍后删除当前节点的下一个节点。
                cur->next = cur->next->next;   //将当前节点的下一个指针指向下下个节点,即跳过当前节点的下一个节点,实现删除操作。
                delete tmp;                    //删除临时指针 tmp 指向的节点,即删除原来当前节点的下一个节点。
            }
            else{                              //如果当前节点的下一个节点的值不等于 val,则将指针 cur 移动到下一个节点,继续遍历。
                cur = cur -> next;
            }
        }
         return head;    //返回头节点 head,表示删除操作完成后的新链表。
    }
};

C(free释放空间)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    //删除头节点
    while(head != NULL && head->val == val){ //判度头节点非空。并且头节点的值等于val
        struct ListNode* tmp = head;
        head = head->next;
        free(tmp);
    }

    //删除非头节点
    struct ListNode* cur = head;
    while(cur != NULL && cur->next != NULL){
        
        if(cur->next->val == val){
            struct ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            free(tmp);
        }
        else{
            cur = cur->next;
            }
    }
    return head;

}

第二种虚拟节点(dummy node):

C++

/**
 * 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* dummynode = new ListNode(0);
        dummynode->next = head;      
        ListNode* cur = dummynode;

        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 = dummynode->next;
        delete dummynode;
        return head;
    }
};

C

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* dummyNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyNode->next = head;
    struct ListNode* cur = dummyNode;

    while (cur->next != NULL) {
        if (cur->next->val == val) {
            struct ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            free(tmp);
        } else {
            cur = cur->next;
        }
    }
    head = dummyNode->next;
    free(dummyNode);
    return head;
}

三、(707. 设计链表 - 力扣(LeetCode))(还在学习)

四、(206. 反转链表 - 力扣(LeetCode)

1.双指针法

C++

/**
 * 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* temp ;      //创建临时指针存放cur的下一个节点
        ListNode*cur = head;  //初始化头节点为head 
        ListNode*pre = NULL;  //初始化cur反转的下一个节点的

        while(cur){ //但cur指向NUlL跳出循环

            temp = cur->next; //存放cur的下一个节点
            cur->next=pre;    //第一个节点反转指向NULL
            pre=cur;          //pre指针向后移动
            cur=temp;         //cur指针向后移动指向NULL

        }

        return pre; //返回反转链表之后的头节点
    }
};

C

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* tmp; //创建存放cur下一个节点的临时指针
    struct ListNode* cur = head; //初始化头节点给cur指针
    struct ListNode* pre = NULL; //初始化尾节点为NULL

    while(cur){//当cur为NULL时跳出循环
        tmp = cur->next;//存放cur下一个节点的临时指针
        cur->next = pre;//翻转
        pre = cur;  //pre向后移动
        cur = tmp;  //cur向后移动
    }
    return pre; //pre为新链表的头节点
}

2.递归方法

C++

/**
 * 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:
    // 反转链表的递归函数,pre表示当前节点的前一个节点,cur表示当前节点
    ListNode* reverse(ListNode* pre,ListNode* cur){ 
        if(cur==nullptr) return pre;  // 如果当前节点为空,说明已经到达链表末尾,返回pre作为新链表的头节点

        ListNode* temp = cur->next; // 如果当前节点为空,说明已经到达链表末尾,返回pre作为新链表的头节点
        cur->next = pre;                  // 将当前节点指向前一个节点,实现反转
       
        return reverse(cur,temp);         // 递归反转下一个节点
    }

    // 反转链表的入口函数,head为原始链表的头节点
    ListNode* reverseList(ListNode* head) {
        return reverse(nullptr,head);// 调用反转函数
    }
};

C

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 struct ListNode* reverse(struct ListNode* pre,struct ListNode* cur) {
     
    if(cur == NULL) return pre;

    struct ListNode* temp = cur->next;
    cur->next = pre;
    
    return reverse(cur,temp);
}

struct ListNode* reverseList(struct ListNode* head) {
        return reverse(NULL,head);

}

3.虚拟头节点法

4.栈法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值