第三天打卡(链表的基本操作和反转)

本文介绍了如何在C语言中移除链表元素(203题),包括两种方法:直接处理头节点和使用虚拟头节点;同时详细讲解了设计链表(707题)和反转链表(206题)的实现,强调了头节点和实际节点的区别,以及递归和双指针法的应用。
摘要由CSDN通过智能技术生成

补day3,依旧C语言,因为链表掌握并不是很熟练,所以基本都是先看课学,然后进行总结,节省时间。

一、移除链表

1、力扣题目

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

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

示例:

输入:head = [1,2,6,3,4,5,6], val = 6

输出:[1,2,3,4,5]

2、视频学习

视频链接:手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili

主要有两个思路:

(1):分成头节点和普通节点进行处理

(2):由(1)衍生出来的,增加一个虚拟头节点使得都能够按照普通节点进行处理

第一个思路注意事项:cursor要等于head,如果cursor是head->next,那么无法找到前面的节点

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* temp;
    while(head&&head->val==val){
        temp=head;
        head=head->next;
        free(temp);
    }//头节点处理

    struct ListNode* cursor=head;
    //cursor必须为head是因为,如果是head->next,那么会找不到前面的节点,cursor也会丢失。

    while(cursor!=NULL &&cursor->next!=NULL){
    //第一个条件是为防止head为空,第二个条件是为了防止处理节点为空
        if(cursor->next->val==val){
            struct ListNode* temp;
            cursor->next=cursor->next->next;
            free(temp);
        }
        else{
            cursor=cursor->next;
        }   
    }
    return head;
}

第二个思路:增加一个头节点

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    //创建一个虚拟头节点,并初始化
    struct ListNode *shead=(struct ListNode *)malloc(sizeof(struct ListNode));
    shead->next=head;
    
    struct ListNode* cursor=shead;
    while(cursor->next!=NULL&&cursor!=NULL){
        if(cursor->next->val==val){
            struct ListNode* temp;
            temp=cursor->next;
            cursor->next=cursor->next->next;
            free(temp);//释放该节点
        }
        else{
            cursor=cursor->next;
        }
    }//循环处理

    head=shead->next;
    free(shead);//释放虚拟头
    return head;
}

3、总结

①掌握两种思想,本质都是考虑对首部的处理。最好优先考虑使用第二个思路,即增加虚拟头节点,这样简化了后续的处理。

②在用C/C++来写的时候要注意释放,有的语言系统会自动释放,C/C++要手动释放。

③第一个思路,如果条件为while(cursor->next!=NULL),那么前面要先处理head为空的情况,可以加上一句if(head==null)return head;

④代码中可以增加typedef struct ListNode ListNode;语句来进行重命名(将struct ListNode重命名为ListNode),方便书写和阅读,不然就像上述代码一样每次都要struct ListNode。

二、设计链表

1、力扣题目

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

题目描述:

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

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

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

实现 MyLinkedList 类:

1、MyLinkedList() 初始化 MyLinkedList 对象。

2、int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。

3、void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。

4、void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。

5、void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。

6、void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入 ["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]

        [[], [1], [3], [1, 2], [1], [1], [1]]

输出 [null, null, null, null, 2, null, 3]

解释 MyLinkedList myLinkedList = new MyLinkedList();

        myLinkedList.addAtHead(1);

        myLinkedList.addAtTail(3);

        myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3

        myLinkedList.get(1); // 返回 2

        myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3

        myLinkedList.get(1); // 返回 3

该题主要是考察对链表的一些操作,加深对链表的认识。不过需要注意到,用C写的时候obj为头节点,index=0的时候代表的是首节点而非obj,两个概念不一样,需要注意区分

2、视频学习

视频链接:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili

代码如下:

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


MyLinkedList* myLinkedListCreate() {
    MyLinkedList *List=(MyLinkedList *)malloc(sizeof(MyLinkedList));
    List->next=NULL;
    return List;
}

int myLinkedListGet(MyLinkedList* obj, int index) {
    int i=0;
    MyLinkedList *search=obj->next;//头节点没有下标
    while(search!=NULL){//循环,当前节点不为空时
        if(i==index){
            return search->val;//当前下标=index,返回目标值
        }
        else{
            search=search->next;//search指针向后移动
        }
        i++;
    }
    free(search);
    return -1;//异常处理
}

void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    //创建新的节点
    MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
    node->val=val;

    //添加节点
    node->next=obj->next;
    obj->next=node;
}

void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    //创建新的节点
    MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
    node->val=val;
    node->next=NULL;
    
    //寻找尾部节点
    MyLinkedList *search=obj;
    while(search->next!=NULL){
        search=search->next;
    }

    //添加到尾部
    search->next=node;
    //不能free(research);因为初始化search时为obj,直接释放会出错。
}

void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    //创建节点
    MyLinkedList *node=(MyLinkedList *)malloc(sizeof(MyLinkedList));
    node->val=val;
    //在index为头节点时
    if(index==0){
        node->next=obj->next;
        obj->next=node;//更新头节点
    }

    //可以直接调用创建首元素函数
    //if (index == 0){
    //     myLinkedListAddAtHead(obj, val);
    //     return;
    // }

    MyLinkedList *search=obj->next;
    int i=1;
    while(search!=NULL){
        if(i==index){//在其他节点添加
            node->next=search->next;//注意是next
            search->next=node;
            return;
        }
        else{
            search=search->next;
        }
        i++;
    }
    free(search);

}

void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    if (index == 0){//删除0号元素需要改动头,所以单独处理
        MyLinkedList *tmp = obj->next;
        if (tmp != NULL){
            obj->next = tmp->next;
            free(tmp);     
        }
        return;
    }

    MyLinkedList *search = obj->next;
    int i=1;//search在下标为0那里
    while(search!=NULL){
        if(i==index){//当前元素要被删除
            MyLinkedList *tmp=search->next;
            if(tmp!=NULL){
                search->next=tmp->next;
                free(tmp);
            }
        }
        else{
            search=search->next;//后移
        }
        i++;
    } 
    free(search);
}

void myLinkedListFree(MyLinkedList* obj) {
    while(obj!=NULL){
        MyLinkedList *temp=obj;
        obj=obj->next;
        free(temp);
    }
}

3、总结

①要注意区分头节点和首节点,头节点是虚拟节点,注意index指的是哪一个节点

②在对链表进行操作时,要注意避免空指针问题,对某一节点操作时需注意前后节点

③注意对首节点的考虑,首节点的改动可能会影响头节点的next,需单独考虑

④养成free()的习惯,但是在free时需要注意能否free,如上述代码中myLinkedListAddAtTail函数便不能free(search)。

三、反转链表

1、力扣题目

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

题目描述:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

2、视频学习

视频链接:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili

(1)双指针法

该方法需注意,初始时pre=null,用临时指针保存cur的下一个位置,防止丢失。

代码如下:

struct ListNode* reverseList(struct ListNode* head) {
    typedef struct ListNode ListNode;
    ListNode *cur=head;
    ListNode *pre=NULL;
    while(cur!=NULL){
        ListNode *tmp=cur->next;//临时指针保存
        cur->next=pre;//换方向
        pre=cur;
        cur=tmp;   
    }
    return pre;//注意返回的是pre 
}

(2)递归调用

递归调用其实本质思想跟双指针的解法差不多,只不过cur和pre进行了递归调用来实现while循环。

不过需要注意递归调用时cur和pre的变化

struct ListNode* reverse(struct ListNode *cur, struct ListNode *pre) {  
    if (cur == NULL) return pre;  //终止条件,返回pre 
    struct ListNode *tmp;
    tmp= cur->next; 
    cur->next = pre; 
    return reverse(tmp, cur); // 递归调用,当前cur和pre分别为tmp,原先的cur  
}
struct ListNode* reverseList(struct ListNode* head) {  
    return reverse(head, NULL);  
}

3、总结

该题主要是掌握双指针的思想,递归不过是双指针的一种变形。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值