移除元素【数组】

news2024/11/25 22:35:14

⭐前言⭐

※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入练习模式。若有错误,请多多指教。更多有趣的代码请移步Gitee
👍 点赞 ⭐ 收藏 📝留言 都是我创作的最大的动力!

题目

27. 移除元素

题目:

  • 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

  • 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

  • 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。


示例1

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。

示例2

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。

提示

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

思路

题目分析:

  • 去除掉数组 nums 中 值为 val 的元素,返回新数组的长度;
  • 不能使用额外数组空间,因为要删除元素,新数组的长度 一定小于等于 原来数组元素,我们可以在原数组上进行操作
  • 元素的顺序可变,这个是我们进行优化的关键
  • 由提示可知,根据相关数据的范围,本题可以直接使用int类型

由于数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖;

解法一:使用暴力破解,用两层循环,第一层循环用于 寻找数组中等于 val 的元素,第二层循环负责如果第一层循环找到等于 val 的元素,就将该元素后面的元素向前一步,覆盖掉该元素;

解法二: 使用快慢指针,定义 fast = slow = 0; fast 指针用来查询当前数组的元素的值 是否为 val 如果 等于 val 就 跳过该元素,如果不等就将该元素赋值给 slow ,有此可知 slow 前面的元素都是值 不为 val 的元素,返回slow 就是新数组的长度;

解法三: 使用相向双指针,题解二中如果数组中没有val 元素那么fast 和slow 都会遍历一遍数组,会遍历两遍数组,但由于题目说明的是元素的顺序可变,我们可以用双指针分别从前向后(left),和从后向前遍历(right) 当 right 和 left 相遇的时候说明遍历了数组,我们要做的就是将left 遍历到 值等于 val 的元素和right 遍历的不等于 val 的值进行互换,这样就不用一个一个地移动元素,但是缺点却是改变了元素的原有顺序位置;

解法一:暴力破解

使用双层 for 循环

  • 第一层 for 循环 遍历数组元素

i = 0; i < len; i ++

  • 第二层for循环 更新数组元素

    如果 nums[i] == val
    for(int j = i + 1; j < len ; j++) nums[j - 1] = nums[j];
    将i后面值向前一位进行覆盖;

程序源码

// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
public class Solution {
    public static int removeElement(int[] nums, int val) {
        // 双重循环
        int len = nums.length;
        for (int i = 0; i < len; i++) { // 第一层循环, 查找值为 val 的元素
            if (nums[i] == val) {   // 找到值为 val 的元素, 将i后面的元素集体向前移动一位
                for (int j = i + 1; j < len; j++) {
                    nums[j - 1] = nums[j]; // 注意 j的起始值 和 结束条件
                }
                i--;  // 因为 i 以后的元素都集体向前移动一位, 此时位于下标 i 的元素, 其实是下标为i + 1的元素,需要重新遍历;
                len--;  // 覆盖一个元素, 数组的长度 - 1;
            }
        }
        return len; // 返回数组的长度;
    }
}

例:nums = [0,1,2,2,3,0,4,2], val = 2 移除 nums 中的 2

使用暴力破解删除过程为:

初始化:i = 0

在这里插入图片描述


当 i = 0, 1时 nums[ i ] != 2 不执行if 语句 i = 2 时,进入if语句 j = i + 1;

在这里插入图片描述


执行 第二层循环 更新数组元素 : 将 i 后面的所有元素集体想前移动一位
此时灰色的框的元素 2 仍然存在数组中,我们需要将数组的长度由 8 改为 7 将最后一个元素 删除掉
注意 for循环的结束条件 是 i < len 而不是 i < nums.length

在这里插入图片描述

i --; len--;
这里需要重新遍历 下标为 2 的元素 需要 i-- 向前一步,同时 新数组的长度变成了7 循环到 下标为 6的时候就结束了,
注意要在循环的过程中更新结束的len值

在这里插入图片描述

i++ i = 2; 重复上述过程

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述

接下来过程和 i = 0 ,i 时一样 i 一直++到 i == 5

在这里插入图片描述

此时 j = 6 = len 不进入第二层for循环 执行 i--len-- 操作

在这里插入图片描述

此时 i ++ 后 i = 5 i = len 不满足进入第一层for循环条件,跳出循环 新数组长度为 5 元素分别是:0 1 3 0 4 符合预期

使用暴力破解 的时间复杂度 为:O(n^2) 时间复杂度较高,我们能不能进一步优化下;

解法二:快慢指针(双指针)

快慢指针: 通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作
定义快慢指针:

  • 快指针: 寻找新数组的元素,即元素的值不等于 val 的元素,
  • 慢指针: 指向更新 新数组下标的位置,即新数组的最后一个元素的下一个的位置,和新数组的长度相等

思路: 快指针查询不等于val的元素,将这些数组赋值给慢指针所在的位置,如果查询到等于 val 的元素,快指针就跳过该元素;

完整代码:

// 时间复杂度: O(n)
// 空间复杂度: O(1)
public class demo2 {
    public static int removeElement(int[] nums, int val) {
        // 这种实现方法没有改变元素的相对位置
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
            if (nums[fast] != val) {
                nums[slow] = nums[fast];
                slow++;
                // 这里可以化简为 nums[slow++] = nums[fast]; ++在slow的后面, 表示先用后加
            }
        }
        return slow;
    }
}

例:nums = [0,1,2,2,3,0,4,2], val = 2 移除 nums 中的 2

图解:
初始化:
在这里插入图片描述

nums[fast] != val 0 != 2 nums[slow] = nums[fast];

在这里插入图片描述

fast = 1; nums[1] != val

在这里插入图片描述

fast = 2 nums[2] == val

在这里插入图片描述

fast == 3 nums[3] == val

在这里插入图片描述

fast == 4 nums[4] != 2

在这里插入图片描述

fast = 5 nums[5] != val

在这里插入图片描述

fast = 6 nums[6] != val

在这里插入图片描述

fast = 8 nums[7] == val

在这里插入图片描述

fast = 8 == nums.length 跳出循环
此时slow 所值的下标位置,正好等于新数组(橙色部分)的长度;
将slow返回;

这次解法比上一次解法时间复杂度直接从O(n^2) 降低到 O(n)但这种算法在最坏情况下(数组没有元素等于val)需要左右指针各遍历一次数组,遍历了两次数组,而且还进行了无效复制 nums[slow] = nums[fast](fast == slow时)

解法三:双指针优化

相向双指针:通过左右两个指针,向中间遍历实现只遍历一次完成删除的工作

  • 前提: 元素的顺序可以改变
  • 思路:如果要移除的元素在数组开头,我们可以直接从后面不排除的元素移动到开头这个元素的位置
    例:【3 4 6 2 7 8】 val 3 我们直接可以用 8 来替代 3 得到 【8 4 6 2 7】同样满足题目
    这个优化在序列中val 元素的数量较少时效果较明显;

实现 :

  • 使用left = 0; right = nums.length; 向中间遍历,
  • 如果 left 指向的元素等于 val 就将 right 指向的元素 复制到left的位置,然后right–;
  • 如果 left 指向的元素 不等于 val, 就left ++
  • 会有一种情况是right 指向的元素也等于 val ,不过没有关系,因为上一步中 没有left++;还会检查新复制来的元素是不是为val;
  • 当left 和 right重合的时候就遍历完数组中所有元素,结束循环

完整代码

// 时间复杂度: O(n)
// 空间复杂度: O(1)
public class Solution {
    public static int removeElement(int[] nums, int val) {
        int left = 0;  // 指向数组第一个元素
        int right = nums.length - 1;  // 指向数组最后一个元素
        while(left <= right){
            if (nums[left] == val) {
                // 如果 left 所指向的元素等于 val 就将 right 指向的元素复制到 left 的位置
                nums[left] = nums[right--];
            } else {
                // 不等于 val, 就left++;
                left++;
            }
        }
        return left;
    }
}

例:nums = [0,1,2,2,3,0,4,2], val = 2 移除 nums 中的 2

初始化 left = 0; right = 7

在这里插入图片描述

left = 0 nums[0] != val

在这里插入图片描述

left = 1 nums[0] != val

在这里插入图片描述

left = 2 nums[0] == val
nums[left] = nums[right–]

在这里插入图片描述

left = 2 nums[0] == val
nums[left] = nums[right–]

在这里插入图片描述

left = 2 nums[2] == 4 != 2
left++

在这里插入图片描述

left = 3
nums[3] = 2 == val
nums[left] = nums[right–]

在这里插入图片描述

left = 3 nums[3] != val
left++;

在这里插入图片描述
left = 4 nums[4] != val
left++;
在这里插入图片描述

left > right
跳出循环, 返回 left ,5

这种算法两个指针在最坏的情况下合起来只遍历了数组一次,避免了需要保存的元素的重复复制操作

但是这种算法重复第判断了好几次在同一个下标位置,对于right指向的元素 等于val 的情况还复制了过去,还存在着一定的问题,所以我们还可以进行优化

寻找left 指向的元素 等于 val 和 right 指向的元素 不等于 val,这种情况下才进行复制

完整代码

// 时间复杂度: O(n)
// 空间复杂度: O(1)
public class Solution {
    public static int removeElement(int[] nums, int val) {
        int left = 0;  // 指向数组第一个元素
        int right = nums.length - 1;  // 指向数组最后一个元素
        while (left <= right){
            while (left <= right && nums[left] != val){ // 寻找左边等于 val 的元素
                left++;
            }
            while (left <= right && nums[right] == val){ // 寻找右边不等于 val 的元素
                right--;
            }
            if (left < right) { // 将右边不等于val的元素覆盖掉左边等于val的元素

                nums[left++] = nums[right--];
            }
        }
        return left;  // left 制定指向最终数组末尾的下一个元素;
    }
}

例:nums = [0,1,2,2,3,0,4,2], val = 2 移除 nums 中的 2

图解 : 初始化
left = 0; right = 7

在这里插入图片描述

寻找左边等于 val 的元素 left = 2

在这里插入图片描述

寻找右边不等于 val 的元素 right = 6

在这里插入图片描述

left < right
nums[left++] = nums[right–]; 将右边不等于val的元素覆盖掉左边等于val的元素

在这里插入图片描述

left <= right 进入循环
寻找左边等于 val 的元素 left = 3
寻找右边不等于 val 的元素 right = 5
left < right
nums[left++] = nums[right–]; 将右边不等于val的元素覆盖掉左边等于val的元素

在这里插入图片描述

left == right 进入循环
寻找左边等于 val 的元素 left = 5
left > right跳出循环
left > right 不会进入循环 并且也不会 进行交换

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/460974.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IPEmotion 2023 R1支持在线能量分析

新发布的IPEmotion 2023 R1提供了许多新功能&#xff0c;其中最重要的是新的“在线功率计算&#xff08;Online Power Calculation&#xff09;”功能。该功能允许使用预定义的功率计算来进行测量任务和数据分析。此外&#xff0c;IPEmotion 2023 R1现在支持一种新的存储模式&a…

Maya英文界面怎么改为中文界面

Maya是一款3D动画和视觉效果软件&#xff0c;用于创建逼真的角色和大片般的效果&#xff0c;也是受到电影、电视和游戏行业的 3D 建模师、动画师、照明艺术家和 VFX 艺术家等多数人喜爱的一款3D软件。我们在使用Maya的过程中&#xff0c;常常会遇到一些小阻碍&#xff0c;比如M…

【Python爬虫实战】你不必到处找数据,你完全可以自己爬之Python批量采集图虫网摄影师高清美照,听说~你喜欢御姐...(爬图神器)

前言 怎么批量保存网页图片&#xff1f; 有时候在网页中看到很多美图其中有很多自己喜欢的图片素材或壁纸&#xff0c;一张纸一张下载保存未 免太低效了。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众ha…

C++哈希应用——位图布隆过滤器

C布隆过滤器 文章目录 C布隆过滤器概念实质用途控制误判率实现插入和查找布隆过滤器的删除 布隆过滤器优点布隆过滤器缺陷相关大数据题目 用哈希表存储用户记录&#xff0c;缺点是需要消耗较大的内存&#xff1b;用位图存储用户记录&#xff0c;缺点是位图一般处理整形&#xf…

P1039 [NOIP2003 提高组] 侦探推理

题目描述 明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中&#xff0c;于是他召集了一群同学玩推理游戏。游戏的内容是这样的&#xff0c;明明的同学们先商量好由其中的一个人充当罪犯&#xff08;在明明不知情的情况下&#xff09;&#xff0c;明明的任务就是找出这…

Java-设计模式中事件与委托Java版本

目录 背景介绍 实现过程 类图 NS图 代码 客户端 业务封装类 委托类 事件类 猫类 老鼠类 运行结果 总结提升 背景介绍 相信大家在学习大话设计模式的时候都有接触过事件与委托&#xff0c;但是对于事件与委托具体的业务逻辑也不是很清楚&#xff0c;只能照猫画虎去使用…

SEO机制算是让我玩明白了

获取当前时间时间戳&#xff0c;返回遵循ISO 8601扩展格式的日期 new Date(Date.now()).toISOString() 使用moment库转换回来 this.moment(new Date(Date.now()).toISOString()).format("YYYY-MM-DD") js去掉富文本中html标签和图片 filterHtmlTag(val) {if(!val){…

Shell编程规范与使用

一、Shell脚本概述 1&#xff09;Shell的作用——命令解释器&#xff0c;“翻译官” Linux 系统中的 Shell 是一个特殊的应用程序&#xff0c;它介于操作系统内核与用户之间&#xff0c;充当 了一个“命令解释器”的角色&#xff0c;负责接收用户输入的操作指令&#xff08;命…

接口协作--apipost接口协作工具

接口协作 apipost支持接口在线协作编辑功能&#xff0c;打开apipost创业一个团队&#xff0c;在创建一个项目。 在把需要一起协作的人员添加到团队中 在进行项目编辑把需要进行协作的人员拉取到项目中 之后在进入项目创建接口就可以进行接口协作了

scratch猫捉老鼠 少儿编程 电子学会图形化编程scratch编程等级考试二级真题和答案解析2023年3月

目录 scratch猫捉老鼠 一、题目要求 1、准备工作 2、功能实现 二、案例分析

kafka调试脚本的使用

创建名称为test的topic且副本数量3&#xff0c;partition数量6 /etc/kafka/kafka/bin/kafka-topics.sh --create --bootstrap-server 10.1.60.112:9092 --replication-factor 3 --partitions 6 --topic test 查看名称为test的topic信息 /etc/kafka/kafka/bin/kafka-topics.sh -…

uniapp微信小程序图片预览PreviewImage

一、说明 功能&#xff1a;点击图片预览大图&#xff0c;并且可以通过滑动查看不同图片的预览大图。 点击预览大图后&#xff1a; 二、上代码 参考uniapp官方文档 其提供了预览大图的函数uni.previewImage(OBJECT). //放大查看推荐图片enlargePicture(index) {console.log…

【Unity-ML】Unity机器学习(一)

安装环境&#xff1a;Windows10 Anaconda3(64-bit)&#xff0c;网上很多教程&#xff0c;例如这个anaconda下载及安装(保姆级教程) - 知乎anaconda包管理器和环境管理器&#xff0c;强烈建议食用 1.下载官网下载太慢可选用镜像下载 官网下载&#xff1a; Anaconda | Individua…

Softing FiberXpert 700光纤测试套件助力一级多模和单模光纤认证

FiberXpert 700是用于多模和单模的四路波长测试套件&#xff0c;不仅可以对光纤链路进行直观、灵活和快速地认证&#xff0c;而且可以导出数据报告。 测试网络安装以确保其符合指定标准的过程称为认证&#xff0c;并且这通常需要纸质文件作为符合标准的证明。而FiberXpert 700光…

Docker 的数据管理

一、Docker 的数据管理 管理 Docker 容器中数据主要有两种方式&#xff1a;数据卷&#xff08;Data Volumes&#xff09;和数据卷容器&#xff08;DataVolumes Containers&#xff09;。 1&#xff0e;数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可将宿…

为什么说网络安全行业是IT的风口行业?

前言 2023年网络安全行业的前景看起来非常乐观。根据当前的趋势和发展&#xff0c;一些趋势和发展可能对2023年网络安全行业产生影响&#xff1a; 5G技术的广泛应用&#xff1a;5G技术的普及将会使互联网的速度更快&#xff0c;同时也将带来更多的网络威胁和安全挑战。网络安全…

eBPF技术介绍

前言 eBPF起源于linux内核&#xff0c;它可以以砂箱程序运行在操作系统内核的特权上下文&#xff0c;高效&#xff0c;安全&#xff0c;易于扩展而不需要修改内核源码或者加载内核模块。 操作系统一直是实现观测&#xff0c;安全和网络功能的最理想的地方&#xff0c;因为内核的…

Vue基本的内置指令

前言 除了常见的v-bind,v-for,v-if,v-on.v-model等&#xff0c;本次学习一些vue提供的其他内置指令 1 v-text 给标签插入文本&#xff0c;类似于插值语法 它会把全部的字符串当成文本去解析,不会当成标签的,哪怕写的是标签结构 效果和插值语法是一样的 插值语法比v-text更加…

P1037 [NOIP2002 普及组] 产生数

题目描述 给出一个整数 &#xfffd;n 和 &#xfffd;k 个变换规则。 规则&#xff1a; 一位数可变换成另一个一位数。规则的右部不能为零。 例如&#xff1a;&#xfffd;234,&#xfffd;2n234,k2。有以下两个规则&#xff1a; 2⟶52⟶5。3⟶63⟶6。 上面的整数 23423…

SpringBoot的配置和日志

1.配置文件的作用和意义 配置文件中配置整个项目中所有重要的数据&#xff0c;比如&#xff1a; 1.数据库的连接信息&#xff08;包含用户名和密码的设置&#xff09;&#xff1b; 2.项目的启动端口&#xff1b; 3.第三方系统的调用秘钥等信息&#xff1b; 4.用于发现和定位问…