开发者社区> Yohifo> 正文

C语言题解 | 移除元素(多种解法)

简介: 这是力扣上的一道简单题,需求是 移除数组中的指定元素,并且要求 空间复杂度为O(1) ,即原地移除,我们可以用顺序表中的任意位置删除的思想解决这个题,符合题目要求,当然还有其他解法。
+关注继续查看

🍉前言

这是力扣上的一道简单题,需求是 移除数组中的指定元素,并且要求 空间复杂度为O(1) ,即原地移除,我们可以用顺序表中的任意位置删除的思想解决这个题,符合题目要求,当然还有其他解法。

179ebb4fb8844dac87d08c4dc542f6d9.png



🍉正文

首先要想清楚移除的本质并不是真删除,而是把元素覆盖即可,覆盖n个元素后,数组总长度就要-n

🍍解法一、逐个判断

解法一是比较容易想到的解法,比较朴素,具体实现起来就是 从头开始遍历,找到目标元素val,就执行一次任意位置删除

6c68bcedf5d6462987b2ec4ebd5ee44b.gif


因为题目给的是数组,而非顺序表的常规结构,因此在设计任意删除函数时,需要多设计一个参数len ,不然就很难控制循环的终止

注意: 在实际写代码时,每删除一次元素,i 都要回退一次,因为有的测试用例是 3 3 3 3val = 3,如果不回退,直接覆盖,会少删两个元素。

//27.移除数组
//思路1,一个一个判断
#include<assert.h>
void Erase(int* nums, int pos, int len)
{
    //因为题目给的是数组,所以我们要对顺序表的任意删做点改变
    //比如给出第三个参数长度
    assert(len > 0);    //如果长度小于等于0,就报错
    while (pos < len - 1)
    {
        nums[pos] = nums[pos + 1];  //用数组的方式覆盖
        pos++;  //下标++,向后移动
    }
}

int removeElement(int* nums, int numsSize, int val)
{
    assert(nums);   //nums不能为空指针
    int i = 0;
    int len = numsSize;
    for (i = 0; i < numsSize; i++)
    {
        if (nums[i] == val)
        {
            Erase(nums, i, numsSize--);
            i--;    //这里要--的原因是防止漏掉val,多判断一次
            len--;  //总长度会-1
        }
    }

    return len; //返回的是删除后的数组长度
}

🍍解法二、分离注入

这个解法也比较容易想到,就是 创建一个额外的 数组 ,对 原数组 进行 遍历判断 ,如果元素不等于 val ,就可以放入 新数组 中,遍历 结束后,需要把 新数组 中的元素注入 原数组 中,最后再返回 新数组 的 下标 就行了。 这种方法也是比较通俗易懂,但不符合题目要求,因为我们这个开辟了 ​n ​大小的空间,实际提交时,力扣也没说不对,可能是它无法检查得这么细吧,这个不太好的解法我也会分享给大家,但不推荐使用,可以作为一种新思路学习。

b7dbb59fea3f4c1ab7f63d8ff563eebe.gif

//27.移除数组
//思路2,以空间换时间
#include<stdlib.h>
#include<assert.h>
int removeElement(int* nums, int numsSize, int val)
{
    int* pa = (int*)malloc(sizeof(int) * numsSize); //动态申请内存
    assert(pa); //预防申请失败的情况
    int i = 0;  //原数组的下标
    int j = 0;  //新数组的下标
    for (i = 0; i < numsSize; i++)
    {
        //如果不是目标值,就将其放入到新数组中
        if (nums[i] != val)
            pa[j++] = nums[i];  //vs中会报一个小警告,原因pa[j]可能会越界,可不管
    }
    //将新数组中的元素注入到原数组中
    for (i = 0; i < j; i++)
        nums[i] = pa[i];

    free(pa);   //释放空间
    pa = NULL;  //指针置空
    return j;   //此时新数组的下标就是有效元素数
}

🍍解法三、双指针覆盖

这是一种比较巧妙的解法,用到了 双指针 ,对数组内元素进行覆盖,具体实现为:存在两个指针p1 p2 ,两者初始都指向数组起始位置,遍历 整个数组,对指针 p1 和指针 p2 所指向的值进行比较,如果 *p1 != val ,就把 *p1 赋给 *p2 ,然后 p2 向后移动,当然无论相等还是不相等,p1 都需要往后移动,这个解法的目的就是把数组中所有非目标值的元素往前移动,最后返回 p2 - nums 的值( 两指针相减,得到的是其中间的元素个数 )

f411b13e20c948ba9e13acb8ff53c8db.gif

//27.移除数组
//思路3,双指针覆盖
#include<assert.h>
int removeElement(int* nums, int numsSize, int val)
{
    assert(nums);   //断言,防止空指针
    int* p1 = nums; 
    int* p2 = nums; //这是两个指针
    //注:直接使用numsSize没事,因为这是局部变量
    while (numsSize--)
    {
        //如果 *p1 != val,就将当前元素向前覆盖
        if (*p1 != val)
            *p2++ = *p1;    //覆盖后,p2要++
        p1++;   //p1需要一直向后移动
    }

    return p2 - nums;   //指针-指针,得到两者间的个数
}

🍍解法四、双指针左右交换

这种解法也用到了 双指针 ,不过是左右两个 指针左指针 pleft 向右移动,右指针 pright 向左移动,其中,左指针 的目标是找到 val ,而 右指针 的目标是找到非 val 的元素,两者交换,显然这需要在一个大循环内嵌套两个小循环,结束条件很讲究 ,大循环为 pleft < pright,第一个小循环为 *pleft != val && pleft < pright,第二个小循环为 *pright == val && pright > pleft这种解法速度很快,但有一个小缺陷:交换结束后,需要遍历一遍数组,确认其中的非目标元素数,并返回此值。

3faed1c1a1df4fabb6a53097c1eeb6a5.gif

//27.移除数组
//思路4,双指针左右交换版
#include<assert.h>
int removeElement(int* nums, int numsSize, int val)
{
    assert(nums);   //断言,防止空指针
    int* pleft = nums;  //左指针,从数组起始位置开始
    int* pright = nums + numsSize - 1;  //右指针,从数组尾元素位置开始
    while (pleft < pright)
    {
        while (*pleft != val && pleft < pright)
        {
            pleft++;    //左指针向右走
        };
        while (*pright == val && pright > pleft)
        {
            pright--;   //右指针向左走
        };
        if (pleft < pright)
        {
            *pleft ^= *pright;  //因为都是整型元素
            *pright ^= *pleft;  //所以可以用异或交换法
            *pleft++ ^= *pright--;  //其实可以在交换后,让左右指针各走一步
        }
    }
    int len = 0;    //记录返回的长度
    int i = 0;
    for (i = 0; i < numsSize; i++)
    {
        //如果元素不等于val,长度就可以统计
        if (nums[i] != val)
            len++;
    }
    return len; //返回长度
}



🍉总结

以上就是今天给大家带来的题解文章,关于 顺序表(数组) 的题还是比较简单的,想清楚思路就行了,关于 顺序表 的OJ还有两题,后面会分享的。第二次做动图相比于之前,容易了很多,QQ录屏画质有压缩,等后面找到合适的录屏软件后,动图会更加高清 ,当然 内容也会更加扎实 ,制作动图不易,还请各位多多支持!

如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正

c3a2fbd4cece4640b5ba77b478ae727a.jpg

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
数组元素循环右移问题 (C语言解法)
数组元素循环右移问题 (C语言解法)
408 0
浙大版《C语言程序设计(第3版)》题目集 - 练习7-4 找出不是两个数组共有的元素(20 分)
浙大版《C语言程序设计(第3版)》题目集 - 练习7-4 找出不是两个数组共有的元素(20 分)
117 0
数据结构(C语言版)实现链栈的创建,赋值随机数,进栈,出栈,取栈顶元素,输出
数据结构(C语言版)实现链栈的创建,赋值随机数,进栈,出栈,取栈顶元素,输出
154 0
(第11列)C语言练习:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组。五步带你解决。
(第11列)C语言练习:输入数组,最大的与第一个元素交换,最小的与最后一个元素交换,输出数组。五步带你解决。
285 0
(第23列)C语言典型题:求两个数的最小公倍数和最大公约数。(两种解法)
(第23列)C语言典型题:求两个数的最小公倍数和最大公约数。(两种解法)
74 0
(第二列)C语言常见基础题型,确定不看一下?:给一个考试分数判断等级(三种解法)。
(第二列)C语言常见基础题型,确定不看一下?:给一个考试分数判断等级(三种解法)。
55 0
《测试驱动的嵌入式C语言开发》——3.1节具有可测性的C模块的那些元素
本节书摘来自华章社区《测试驱动的嵌入式C语言开发》一书中的第3章,第3.1节具有可测性的C模块的那些元素,作者:(美)James W. Grenning,更多章节内容可以访问云栖社区“华章社区”公众号查看
1192 0
《C语言程序设计与实践(第2版)》——第2章 示例驱动的C语言语法元素 2.1变量与表达式
在所有C语言的程序中,必须有且只能有一个main函数,所有C程序总是从main函数开始执行的,而不管main函数在整个程序中的位置如何。int指明了main函数的返回类型,意味着main函数返回值的类型是整数。
1099 0
C语言OJ项目参考(1044):矩阵对角线元素之和
1044:矩阵对角线元素之和 Description 求一个3×3矩阵对角线元素之和。 Input 矩阵 Output 主对角线 副对角线 元素和 Sample Input 1 2 3 1 1 1 3 2 1 Sample Output 3 7 参考解答: #include <stdio.h> int main() { int a
1197 0
+关注
Yohifo
CSDN博主、本科大二在读,改变自己,从现在开始。 临渊羡鱼,不如退而结网。
文章
问答
视频
文章排行榜
最热
最新
相关课程
更多
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载