【代码随想录训练营第42期 Day3打卡 LeetCode 203.移除链表元素,707.设计链表,206.反转链表】

目录

一、做题感受

二、链表相关知识

1.常见链表的种类:

2.链表的定义(一般默认单链表):

3.虚拟头节点(哑节点,在这里我们记作dummyHead)的作用:

4.虚拟头节点(哑节点,dummyHead)的定义和初始化:

三、题目及题解

203. 移除链表元素 - 力扣(LeetCode)

707. 设计链表 - 力扣(LeetCode)

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

四、今日小结

一、做题感受

今天是打卡的第三天,前两天题目主要考察数组相关知识,现在已经来到了链表的学习。题目共有三道,都是以考察单链表为主,整体来说难度不大,但是思路很灵活,尤其是反转链表的考察,双指针的新用法。今天做题总体感觉不错,能有自己的思考和理解。

二、链表相关知识

1.常见链表的种类:

单向链表,双向链表,循环链表,静态链表。

2.链表的定义(一般默认单链表):

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

3.虚拟头节点(哑节点,在这里我们记作dummyHead)的作用:

(1)统一插入操作:在没有虚拟头节点的情况下,插入一个节点需要分别处理在链表头部插入和在其他位置插入两种情况。而有了虚拟头节点后,所有的插入操作都可以看作在虚拟头节点之后的位置插入,这样就统一了插入操作的处理逻辑。

(2)避免空链表判断:在没有虚拟头节点的情况下,如果链表是空的,那么在进行操作时需要特殊处理,判断头指针是否为空。而有了虚拟头节点后,虚拟头节点始终存在,它代表了链表的起始位置,不会为空,因此不需要对空链表进行额外的判断。

(3)简化删除操作:删除一个节点通常需要修改被删除节点的前一个节点的指针,但如果被删除节点是头节点,就没有前一个节点可供修改。而有了虚拟头节点后,它将永远指向第一个节点,可以作为前一个节点的角色,使得删除操作变得简单统一。

4.虚拟头节点(哑节点,dummyHead)的定义和初始化:

C语言:

struct ListNode* dummyHead = malloc(sizeof(struct ListNode));
    dummyHead->next = head;

c++

struct ListNode* dummyHead = new ListNode(0, head);

三、题目及题解

203. 移除链表元素 - 力扣(LeetCode)

这个题的思路很简单,但是有一点细节需要注意。

首先看看我最初的错误做法(相信有很多人会和我犯同样的错误)

这里的错误在于直接用temp指向头节点head的话while(temp->next != nullptr)就无法把head为空的情况考虑进去,为此,我们得引入虚拟头节点来解决这个问题。

正确代码如下:通过创建虚拟头节点dummyHead,使temp->next可以遍历整个链表,从而解决问题。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //创建哑节点dummyHead(虚拟头节点),其中dummyHead->next = head
        struct ListNode* dummyHead = new ListNode(0, head);     
        struct ListNode* temp = dummyHead;
        while(temp->next != nullptr)
        {
            if(temp->next->val == val)
                temp->next = temp->next->next;
            else
                temp = temp->next;
       }
       return dummyHead->next;
    };
};

707. 设计链表 - 力扣(LeetCode)

这个题的话看上去很繁琐,但是当你仔细看完题目后发现就是很多单链表基本操作的集合,只是在这里需要注意,obj是作为虚拟头节点来使用的,这样才能更简单的实现整个链表进行各种操作。还有就是cur指针,初始化指向obj->next(即head)作为临时节点的使用,用来遍历整个链表。

这里附上一份代码随想录的代码(注释的很详尽,可以照着一个函数一个函数看,顺带也当复习一下链表的基本操作:创建,插入,删除等等):

typedef struct MyLinkedList {
    int val;
    struct MyLinkedList* next;
}MyLinkedList;

/** Initialize your data structure here. */

MyLinkedList* myLinkedListCreate() {
    //这个题必须用虚拟头指针,参数都是一级指针,头节点确定后没法改指向了!!!
    MyLinkedList* head = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    head->next = NULL;
    return head;
}

/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
//获取链表中下标为 index 的节点的值
int myLinkedListGet(MyLinkedList* obj, int index) {     //obj相当于一个虚拟头指针
    MyLinkedList *cur = obj->next;
    for (int i = 0; cur != NULL; i++){
        if (i == index){
            return cur->val;
        }
        else{
            cur = cur->next;
        }
    }
    return -1;
}

/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
//头插法:链表末尾插入值为val的一个节点(这里记作nhead)
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    MyLinkedList *nhead = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    nhead->val = val;
    nhead->next = obj->next;
    obj->next = nhead;
}

/** Append a node of value val to the last element of the linked list. */
//尾插法:链表末尾插入值为val的一个节点(这里记作ntail)
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    MyLinkedList *cur = obj;       //创建cur指针遍历整个链表到达tail
    while(cur->next != NULL){
        cur = cur->next;
    }
    MyLinkedList *ntail = (MyLinkedList *)malloc(sizeof (MyLinkedList));
    ntail->val = val;
    ntail->next = NULL;
    cur->next = ntail;
}

/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
//将一个值为 val 的节点插入到链表中下标为 index 的节点之前
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    if (index == 0){
        myLinkedListAddAtHead(obj, val);
        return;
    }
    MyLinkedList *cur = obj->next;
    for (int i = 1 ;cur != NULL; i++){
        if (i == index){
            MyLinkedList* newnode = (MyLinkedList *)malloc(sizeof (MyLinkedList));
            newnode->val = val;
            newnode->next = cur->next;
            cur->next = newnode;
            return;
        }
        else{
            cur = cur->next;
        }
    }
}

/** Delete the index-th node in the linked list, if the index is valid. */
//删除链表中下标为 index 的节点
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    if (index == 0){
        MyLinkedList *tmp = obj->next;
        if (tmp != NULL){
            obj->next = tmp->next;
            free(tmp);     
        }
        return;
    }
    MyLinkedList *cur = obj->next;
    for (int i = 1 ;cur != NULL && cur->next != NULL; i++){
        if (i == index){
            MyLinkedList *tmp = cur->next;
            if (tmp != NULL) {
                cur->next = tmp->next;
                free(tmp);
            }
            return;
        }
        else{
            cur = cur->next;
        }
    }
    
}

void myLinkedListFree(MyLinkedList* obj) {      //清理obj
    while(obj != NULL){
        MyLinkedList *tmp = obj;
        obj = obj->next;
        free(tmp);
    }
}

/**
 * Your MyLinkedList struct will be instantiated and called as such:
 * MyLinkedList* obj = myLinkedListCreate();
 * int param_1 = myLinkedListGet(obj, index);
 
 * myLinkedListAddAtHead(obj, val);
 
 * myLinkedListAddAtTail(obj, val);
 
 * myLinkedListAddAtIndex(obj, index, val);
 
 * myLinkedListDeleteAtIndex(obj, index);
 
 * myLinkedListFree(obj);
*/

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

这个题目是一道很经典的题了,以前就刷到过相关讲解的视频,其实整体思路就是双指针(不同于以往数组所用的双指针)来存储节点。pre指向先驱节点,cur指向当前节点,temp指向当前节点的下一个节点,通过操作使当前节点指向前一个节点,然后再往后依次进行同样操作。

这道题建议多画画图,这样思路就明确了。

代码如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        struct ListNode* pre = NULL;
        struct ListNode* cur = head;
        while(cur)
        {
            struct ListNode* temp = cur->next;      //临时指针temp存储cur的下一位,用于遍历整个链表
            cur->next = pre;        
            pre = cur;
            cur = temp;     
        }
        return pre;     //此时pre指向原链表的最后一位,也是反转后新链表的head
    }
};

当然也可以用递归的做法(其实思路是一样的),这里就不过多描述了。如果这道题还有不理解的地方,可以看看代码随想录公开课,相信你会有所收获:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili

四、今日小结

今天的打卡到此也就结束了。今天回顾了链表的相关知识,也掌握并理解了虚拟头节点的使用,算是收获满满。我是算法小白,也希望终有所成。

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值