代码随想录算法训练营day3| 203.移除链表元素、707. 设计链表、206. 反转链表

本文介绍了LeetCode中的两个链表题目,分别是移除指定值的节点和反转链表,分别提供了暴力递归和迭代解决方案,并讨论了使用虚拟头结点的场景和注意事项。
摘要由CSDN通过智能技术生成

LeetCode 203 移除链表元素

方法一:暴力

1、错误:

(1)首先虚拟头结点作用域仅在当前函数,因此想到了不用malloc动态分配,因此使用了如下代码:

ListNode* dummyNode;

dummyNode->next = head;

...

free(dummyNode);

然后报错:“heap-use-after-free”

分析:没有malloc动态分配的指针会在程序后自动释放,因此不需要free。

(2)然后,将free一行删除,报错“runtime error: member access within null pointer of type 'struct ListNode'”。

分析:?

(3)通过malloc初始化虚拟头结点后通过

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
//  暴力
struct ListNode* removeElements(struct ListNode* head, int val) {
    typedef struct ListNode ListNode;
    // 定义哨兵结点
    ListNode* dummyNode = (ListNode*)malloc(sizeof(ListNode));
    dummyNode->next = head;

    ListNode*cur = dummyNode;
    while (cur->next != NULL) {
        if (cur->next->val == val) {
            ListNode* temp = cur->next;
            cur->next = cur->next->next;
            free(temp);
        } else {
            cur = cur->next;
        }
    }
    //释放虚拟头结点 
    head = dummyNode->next;
    free(dummyNode);

    return head;
}

方法二:递归

递归三步法:

一、函数主体

1、首先将链表想成两部分,一部分是头结点,另一部分是其他结点;

2、假设另一部分已通过函数解决问题,那么这个时候需要对头结点和剩余部分做什么操作来使得整个链表解决问题?

二、返回值

返回什么?返回的就是最后要的结果,即头结点

三、退出递归的判断条件

退出递归的条件应该是:需要处理的规模为0

对于本题:

1、需要将head和处理好的后面的结点连接:head->next = removeElements(head->next,val);,然后处理头结点,即:“head = head->val == val ? head->next : head;”;

2、返回头结点 “return head”;

3、考虑退出的规模,即一个结点都不剩,因为只要剩一个,就需要判断是否需要删除,并且将退出判断写在整个函数最前面,即“if(head == NULL)”。

struct ListNode* removeElements(struct ListNode* head, int val) {
    if (head == NULL) {
        return head;
    }
    head->next = removeElements(head->next,val);
    head = head->val == val ? head->next : head;
    return head;
}

LeetCode 707 设计链表

一遍过

思考:

1、什么时候使用虚拟头结点?当对链表的处理涉及头结点的时候,均可以设置虚拟头结点;

2、关于index超出范围的判断需要想清楚,在这个题中,因为涉及对于index的超范围的判断,因此循环不好用for,然后用index作为退出条件。使用while+if更清楚。




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


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

int myLinkedListGet(MyLinkedList* obj, int index) {
    MyLinkedList* cur = obj;
    int i =0;
    while (index >= 0 && cur->next != NULL) {
        if (i == index) {
            return cur->next->val;
        } else {
            i++;
            cur = cur->next;
        }
    }
    return -1;
}

void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
    MyLinkedList* newHead = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    newHead->val = val;
    newHead->next = obj->next;
    obj->next = newHead;
    return;
}

void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
    MyLinkedList* newTail = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    newTail->val = val;
    newTail->next = NULL;

    MyLinkedList* cur = obj;
    while (cur->next != NULL) {
        cur = cur->next;
    }
    cur->next = newTail;
    return;
}

void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
    MyLinkedList* newNode = (MyLinkedList*)malloc(sizeof(MyLinkedList));
    newNode->val = val;

    MyLinkedList* cur = obj;
    int i = 0;
    while (index >= 0 && cur != NULL) {
        if (i == index) {
            newNode->next = cur->next;
            cur->next = newNode;
            return;
        } else {
            i++;
            cur = cur->next;
        }
    }
    free(newNode);
    return;
}

void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
    MyLinkedList* cur = obj;
    int i = 0;
    while (index >= 0 && cur->next != NULL) {
        if (i == index) {
            MyLinkedList* temp = cur->next;
            cur->next = cur->next->next;
            free(temp);
            return;
        } else {
            i++;
            cur = cur->next;
        }
    }
    return;
}

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

/**
 * 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);
*/

LeetCode 206. 反转链表

方法一:递归

错误代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    struct ListNode* reversed = reverseList(head->next);
    head->next = NULL;
    reversed->next = head;
    return reversed;
}

问题:不是reversed->next = head

分析:将后面所有的结点都想成了一个结点,其实后面是一串,所以应该接到一串结点的最后面,即head->next->next = head,这里head->next就是reversed链表结尾的结点。

正确代码:

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    struct ListNode* reversed = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;
    return reversed;
}

方法二:迭代(双指针)

分析:1、一开始上手看到需要修改头结点,二话不说就直接建立了虚拟头结点,但是实际操作并没有简单多少,反而浪费了时间。建立虚拟头结点的意义在于统一对结点的操作,但是这个题无论如何都需要操作头结点,因此没有引入的意义;

2、对于这个题,对于空链表或者仅有一个结点的链表可以直接输出结果,因此可以在一开始将这两种情况排除,从而减少后续分析的难度;

3、对于整个链表的设置一定要完整,头结点作为最后的尾结点,要将next域指向NULL。

设置了虚拟头结点的代码如下:

struct ListNode* reverseList(struct ListNode* head) {
    typedef struct ListNode ListNode;
    ListNode* dummyNode = (ListNode*)malloc(sizeof(ListNode));
    dummyNode->next = head;
    if (head == NULL) {
        return NULL;
    } else if (head->next == NULL) {
        return head;
    }
    ListNode* cur = dummyNode->next, *pre = dummyNode ;
    while (cur != NULL) {
        ListNode* temp =  cur->next;
        cur->next = pre;
        pre = cur;
        cur = temp;
    }
    head->next = NULL;
    return pre;
}

没有设置虚拟头结点的代码如下:

struct ListNode* reverseList(struct ListNode* head) {
    typedef struct ListNode ListNode;
    ListNode* cur = head, *pre = NULL;
    if (head == NULL) {
        return head;
    }
    while (cur != NULL) {
        ListNode* temp = cur->next;
        cur->next = pre;
        pre = cur;
        cur = temp;
    }
    return pre;
}

DAY3思考:

1、对于链表的迭代,可以先写核心代码,然后再通过核心代码去判断边界。然后再验证特殊情况是否适用,或者直接用条件判断特殊情况。

2、对于链表的迭代,核心代码一定要注意逻辑顺序,指针前后顺序很重要。一种超时就可能是指针前后顺序错误,逻辑死掉了。比如206的关键4行

  • 50
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值