力扣203 - 移除链表元素【LeetCode转VS调试技巧教学】

简介: 对应力扣203 - 移除链表元素,LeetCode转VS调试技巧详细教学,带你学会如何分析力扣上的报错代码

一、题目描述

原题传送门🚪

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

示例 1:

在这里插入图片描述

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

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

二、思路分析

好,看完题目的描述,我们来分析一下如何去求解这道题目
  • 对于本题,题目描述很清晰,给你一个链表的头结点和待删除的元素,但是这里大家要注意,这里给的头结点并不是指的带头结点的链表,只是对于这个链表而言的头部结点,是为了让你可以找到这个链表的所有结点。这里一开始就指出这点的原因是在本题中我要讲解的两种方法就是一个带头,一个不带头,为了避免大家混淆,所以事先讲清楚
  • 然后来分析一下求解的思路,最简单直观的一种思路就是通过遍历去寻找待删结点,然后一一删除,但是这种做法在后期修改的过程中很是麻烦,有些测试案例需要你去不断测试修改,因此这种方法我不做讲解。
  • 说一下我要讲解的方法,也就是重新定义一个链表,然后也是一样去一一遍历原先链表中的结点,若是发现不等于val的结点,则将其使用尾插放入新的链表。若是发现了待删结点,就将其删除,然后继续向下做遍历,直到整个链表遍历完成为止

三、代码详解

然后我们通过这段代码来给大家分析一下

way1【不带头结点】

  • 首先应该要有一个指针指向原先的头结点,因为不能去移动原本的头结点,会将整个链表的结构损坏;接着就是定义这个新的链表头,用于返回,定义tail指针用于做结点的尾插操作,一开始要将他们置为空
在这里插入图片描述
struct ListNode* cur = head;
struct ListNode* tail, *newhead;
tail = newhead = NULL;
  • 接着,就要开始执行内部循环遍历的逻辑,当这个cur指针不为空时,也就是还没有遍历到链表结尾,一直进行一个遍历
  • 内部的判断逻辑分为两部分,一部分是【结点不为待删结点,拿过来尾插】,还有另一部分则是【结点为待删结点,实行删除结点操作】,我们通过图解来看一下
在这里插入图片描述
  • 可以看到,第一个结点的值为1,不为待删结点元素5,因此将其进行一个尾插,但是一开始新链表为空,这个逻辑我们在尾插的时候有讲到过,若是只有一个结点的时候,那待插入的结点就是首部结点,于是直接进行一个赋值即可
if(tail == NULL){       //第一次拿过来
   newhead = tail = cur;
  • 然后cur继续往后进行一个遍历,结点值为2 ≠ 6,因此将其插入到新链表中,此时需要实现的逻辑是一个尾插的逻辑,这个不复杂,我们看一下代码
在这里插入图片描述
  • 只需要让tail->next = cur,进行一个尾部的链接,然后让这个tail向下走即可,当然cur指针也要继续向后走
tail->next = cur;
tail = tail->next;
在这里插入图片描述
  • 接着我们可以看到此时的cur所指向的结点值为6,是我们需要的待删结点,此时就需要执行删除的逻辑,将其从链表中删除即可,然后的话既然它不是我们需要的元素,就不需要将其链接到新链表中,直接进行以下代码的操作即可
  • 有一点,不要忘了保存待删结点的下一个结点,否则free之后就找不到了
struct ListNode* nextNode = cur->next;         //首先保存下一结点
free(cur);
cur = nextNode;
  • 然后我们直接快进到这里:minidisc:
在这里插入图片描述
  • 此时3、4、5结点均被链接上,cur = cur->next来到了最后一个结点,而且当前结点也是待删结点,因此将其free,接着cur继续后移便跳出了循环,此时我们return newhead
在这里插入图片描述
  • 当我们提交之后可以看到,后台报出了【执行错误】,首先你要做的是去分析为什么会报出这个错误,报出这个错误是哪方面出问题了,而不是去盯着代码看来看去👀。这个时候你只会感觉到越加心烦意乱,那有同学问,我看这个保存看不出来怎么办呢?这时候就有一个很好的办法------》【==DeBug调试==】
在这里插入图片描述

DeBug调试教学

此处为大家以视频的形式展现,温馨提示:【微信手机端看不到】
链接

way2【带头结点】

  • 好,看完了不带头结点的情况,接下来我们看一种带头结点的,这种方法需要去手动开辟一个虚拟头结点,但是对于首次的尾部结点插入无需考虑【tail】的情况
  • 也就是最前面的这段逻辑需要进行一个修改,【guard】就是我们所说的哨兵位,首先第一步就是要将哨兵以及进行尾插的【tail】动态开辟出来
struct ListNode* cur = head;
struct ListNode* tail, *guard;
tail = guard = (struct ListNode*)malloc(sizeof(struct ListNode));
  • 因此在程序的最后,无需判断tail是否为空的情况,也就不会存在我们视频中所讲的对空指针的操作
  • 因为无需去判断是第一次尾插还是后面的尾插,因此当cur遍历到的结点不为待删结点时的内部逻辑就可以进行一个简化了,直接做一个链接然后让【tail】和【cur】后移即可
if(cur->val != val){
  tail->next = cur;
  tail = tail->next;
  cur = cur->next;
}
  • 具体的插入操作入下图所示,原本有了一个头结点,因此直接进行尾插即可
在这里插入图片描述

四、整体代码展示【需要自取】

给出两种方法的代码

方法一:不带哨兵位【无头结点】

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

    while(cur)
    {
        //1.结点不为待删结点,拿过来尾插
        if(cur->val != val){
            if(tail == NULL){       //第一次拿过来
                newhead = tail = cur;
            }else{      //后续的尾插
                tail->next = cur;
                tail = tail->next;
            }
            cur = cur->next;
        }else{      //2.结点为待删结点,实行删除结点操作
            struct ListNode* nextNode = cur->next;         //首先保存下一结点
            free(cur);
            cur = nextNode;
        }
    }
    if(tail)
        tail->next = NULL;
    return newhead;
}

方法二:带哨兵位【有头结点】

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    if(head == NULL){
        return head;
    }
    struct ListNode* cur = head;
    struct ListNode* tail, *guard;
    tail = guard = (struct ListNode*)malloc(sizeof(struct ListNode));

    while(cur)
    {
        //1.结点不为待删结点,拿过来尾插
        if(cur->val != val){
            tail->next = cur;
            tail = tail->next;
            cur = cur->next;
        }else{      //2.结点为待删结点,实行删除结点操作
            struct ListNode* nextNode = cur->next;         //首先保存下一结点
            free(cur);
            cur = nextNode;
        }
    }
    //tail不可能为空,一开始已经开出空间指向第一个结点
    tail->next = NULL;

    struct ListNode*next = guard->next;
    free(guard);
    return next;
}

五、总结与提炼

  • 好,我们来总结一下本文所学习的知识,在本文中我是讲解了一道力扣的题解,为力扣203的【移除链表元素】,在文中我给出了两种方法,一种是带头结点,一种是不带头结点。对于不带头结点的,我在第一次提交之后进行了一个视频讲解,相信看了之后你应该能明白要如何将LeetCode上的代码放到VS上面去调试运行,然后一步步地跟着程序的逻辑进行调试。从中大家应该也明白了在C/C++中对于指针的操作是需要多么地谨慎,可能你一个不小心就会使一个指针变为野指针,或者去操作一个空指针
  • 希望在看了本文之后,你不仅可以对力扣上的报错有了一个分析的能力,而且也能学会如何使用VS去调试力扣上的代码
以上就是本文所要描述的所有内容,感谢您对本文的观看,如有疑问请于评论区留言或者私信我都可以:four_leaf_clover:
相关文章
|
9月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
97 1
|
3月前
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
245 90
|
1月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
60 1
|
1月前
|
分布式计算 算法 Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本文讲解了两道经典的图论问题:**岛屿数量(LeetCode 200)** 和 **腐烂的橘子(LeetCode 994)**,分别通过 DFS/BFS 实现。在“岛屿数量”中,利用深度或广度优先搜索遍历二维网格,标记连通陆地并计数;“腐烂的橘子”则采用多源 BFS,模拟腐烂传播过程,计算最短时间。两者均需掌握访问标记技巧,是学习网格搜索算法的绝佳实践。
67 1
|
2月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
206 14
|
1月前
|
Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本篇博客详细解析了三道经典的动态规划问题:198. 打家劫舍(线性状态转移)、279. 完全平方数与322. 零钱兑换(完全背包问题)。通过 Go 语言实现,帮助读者掌握动态规划的核心思想及其实战技巧。从状态定义到转移方程,逐步剖析每道题的解法,并总结其异同点,助力解决更复杂的 DP 问题。适合初学者深入理解动态规划的应用场景和优化方法。
45 0
|
1月前
|
算法 Go 索引
【LeetCode 热题100】回溯:括号生成 & 组合总和(力扣22 / 39 )(Go语言版)
本文深入解析了LeetCode上的两道经典回溯算法题:**22. 括号生成**与**39. 组合总和**。括号生成通过维护左右括号数量,确保路径合法并构造有效组合;组合总和则允许元素重复选择,利用剪枝优化搜索空间以找到所有满足目标和的组合。两者均需明确路径、选择列表及结束条件,同时合理运用剪枝策略提升效率。文章附有Go语言实现代码,助你掌握回溯算法的核心思想。
62 0
|
3月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
80 4
|
3月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
84 10
|
3月前
|
算法 Go
【LeetCode 热题100】23:合并 K 个升序链表(详细解析)(Go语言版)
本文详细解析了 LeetCode 热题 23——合并 K 个升序链表的两种解法:优先队列(最小堆)和分治合并。题目要求将多个已排序链表合并为一个升序链表。最小堆方法通过维护节点优先级快速选择最小值,;分治合并则采用归并思想两两合并链表。文章提供了 Go 语言实现代码,并对比分析两种方法的适用场景,帮助读者深入理解链表操作与算法设计。
114 10

热门文章

最新文章