每日一题——力扣27. 移除元素(举一反三)

题目链接:https://leetcode.cn/problems/remove-element/description/

 

菜鸡写法:

// 函数定义,移除数组nums中所有值为val的元素,并返回新的数组长度
int removeElement(int* nums, int numsSize, int val) {// 如果数组长度为0,直接返回0if (numsSize == 0) {return 0;}// 初始化两个指针,left指向数组的开始,right指向数组的末尾int left = 0, right = numsSize - 1;// 初始化一个变量来记录值为val的元素数量int num_of_val = 0;// 初始化一个临时变量用于交换元素int tmp;// 如果数组只有一个元素if (left == right) {// 如果这个元素的值等于val,则将数组指针置为NULL,并返回0if (*(nums + left) == val) {nums = NULL;return 0;} else {// 否则返回1,因为数组中没有需要移除的元素return 1;}}// 开始遍历数组,直到left指针超过right指针for (; left < right;) {// 如果left指向的元素等于val,且right指向的元素不等于valif (*(nums + left) == val && *(nums + right) != val) {// 增加值为val的元素计数num_of_val += 1;// 交换left和right指向的元素tmp = *(nums + left);*(nums + left) = *(nums + right);*(nums + right) = tmp;// 移动left和right指针left += 1;right -= 1;} else {// 如果left指向的元素不等于val,则移动left指针if (*(nums + left) != val) {left += 1;}// 如果right指向的元素等于val,则增加计数并移动right指针if (*(nums + right) == val) {num_of_val += 1;right -= 1;}}}// 如果left和right指针相遇,且指向的元素等于valif (left == right && *(nums + left) == val) {// 将该元素移到数组的末尾,并增加计数for (; left <= numsSize - num_of_val - 2; left++) {*(nums + left) = *(nums + left + 1);}num_of_val += 1;}// 返回新的数组长度,即原数组长度减去值为val的元素数量return numsSize - num_of_val;
}

这段代码实现了移除数组中指定值的功能,其核心思想是使用双指针技术,一个指针从数组头开始,另一个从数组尾开始,通过交换元素的方式将所有值为val的元素移到数组的末尾。下面是对这段代码的点评:

代码结构与逻辑

  • 初始化:代码首先检查数组长度是否为0,如果是,则直接返回0。然后初始化两个指针leftright,分别指向数组的开始和末尾。
  • 单元素处理:对于只有一个元素的数组,代码进行了特殊处理,如果该元素等于val,则将数组指针置为NULL并返回0,否则返回1。
  • 双指针遍历:代码使用双指针遍历数组,如果left指向的元素等于valright指向的元素不等于val,则交换这两个元素,并移动指针。如果left指向的元素不等于val,则只移动left指针;如果right指向的元素等于val,则只移动right指针。
  • 边界情况处理:当leftright指针相遇时,如果指向的元素等于val,则将该元素移到数组的末尾。
  • 返回结果:最后,代码返回新的数组长度,即原数组长度减去值为val的元素数量。

时间复杂度分析

  • 最坏情况:在最坏的情况下,即数组中所有的元素都等于val,此时需要遍历整个数组,因此时间复杂度为O(n),其中n是数组的长度。
  • 最好情况:在最好的情况下,即数组中没有元素等于val,此时只需要遍历一次数组,时间复杂度同样为O(n)。

空间复杂度分析

  • 额外空间:代码只使用了常数级别的额外空间,包括用于交换元素的临时变量tmp和用于计数的变量num_of_val,因此空间复杂度为O(1)。

改进建议

  • 指针移动逻辑:在双指针遍历的过程中,代码中的指针移动逻辑可以进一步优化,以减少不必要的判断和操作。例如,可以直接在交换元素的同时移动指针,而不需要额外的判断。
  • 边界处理:对于leftright指针相遇时的处理,可以考虑直接在遍历过程中处理,而不是在遍历结束后单独处理。
  • 返回数组指针:如果需要返回修改后的数组指针,可以考虑在函数中返回,而不是仅仅返回新的数组长度。

总体来说,这段代码实现了移除数组中指定值的功能,具有较好的时间复杂度和空间复杂度,但在代码逻辑和边界处理上还有一定的优化空间。
 

双指针优化法:
 

int removeElement(int* nums, int numsSize, int val) {// 初始化两个指针,`i` 用于遍历数组,`j` 用于记录新数组的长度int i = 0, j = 0;// 遍历整个数组for (i = 0; i < numsSize; i++) {// 如果当前元素不等于 `val`if (nums[i] != val) {// 将当前元素移动到新数组的位置 `j`nums[j] = nums[i];// 更新新数组的长度j++;}}// 返回新数组的长度return j;
}

逐行注释解释:

  1. int i = 0, j = 0;:初始化两个指针,i 用于遍历数组,j 用于记录新数组的长度。
  2. for (i = 0; i < numsSize; i++) {:开始遍历数组,i 从0开始,直到数组的长度。
  3. if (nums[i] != val) {:检查当前元素是否不等于 val
  4. nums[j] = nums[i];:如果当前元素不等于 val,将其移动到新数组的位置 j
  5. j++;:更新新数组的长度,即 j 指针向前移动一位。
  6. return j;:遍历结束后,返回新数组的长度。

时间复杂度和空间复杂度:

  • 时间复杂度:O(n),其中 n 是数组的长度。我们只需要遍历一次数组。
  • 空间复杂度:O(1),我们只使用了常数级别的额外空间。

改进建议:

这段代码已经很高效,没有明显的改进空间。它简洁、直观地实现了移除数组中指定值的功能,同时保持了较低的时间和空间复杂度。
 

该方法体现了以下哲学和编程思想:

  1. 简洁性(Simplicity):代码力求简洁明了,避免不必要的复杂性。通过使用两个指针,代码直接实现了移除指定值的功能,没有引入额外的数据结构或复杂的逻辑。

  2. 效率(Efficiency):双指针方法在时间复杂度上达到了O(n),这意味着它只需要遍历一次数组,是一种非常高效的处理方式。同时,空间复杂度为O(1),表明代码只使用了常数级别的额外空间,符合空间效率的要求。

  3. 迭代(Iteration):代码通过迭代的方式遍历数组,这是一种常见的编程模式,用于处理数组、列表等线性数据结构。迭代允许我们逐步处理数据,而不是一次性处理,这有助于提高代码的可读性和可维护性。

  4. 指针操作(Pointer Manipulation):在C语言中,指针是一种强大的工具,可以直接操作内存。双指针方法巧妙地利用了指针来跟踪数组中的元素,这是一种低级别的编程技巧,体现了对内存管理的深刻理解。

  5. 条件逻辑(Conditional Logic):代码中使用了条件语句(if)来决定是否将当前元素添加到新数组中。这种基于条件的逻辑是编程中的基本思想,用于根据不同的情况执行不同的操作。

  6. 增量式开发(Incremental Development):虽然代码本身很简洁,但在开发过程中,程序员可能会逐步添加功能,测试每个部分,确保代码的正确性。这种增量式的开发方法有助于减少错误,提高代码质量。

  7. 算法思维(Algorithmic Thinking):双指针方法是一种特定的算法,用于解决特定类型的问题。这种思维方式要求程序员能够识别问题的模式,并设计出有效的解决方案。

  8. 抽象(Abstraction):代码抽象了移除数组中指定值的逻辑,使得其他开发者可以不关心具体的实现细节,直接使用这个函数来完成任务。

举一反三

基于双指针方法的思想和应用,以下是一些技巧和原则,可以帮助你在其他编程任务中举一反三:

1. 空间优化

  • 原地操作:尽可能在输入的数据结构上直接进行修改,减少额外空间的使用。例如,在排序、合并或修改数组和链表时,考虑是否可以通过调整元素位置而不是创建新的数据结构来实现。

2. 时间优化

  • 单遍扫描:设计算法时,尽量确保只需要遍历数据一次。多数使用双指针的场景,如去除重复项、查找对应和等,都是基于只遍历一遍数据来实现的。

3. 思维模式

  • 对撞指针:在排序数组或链表中寻找两个数的和等特定问题时,可以从两端开始遍历,根据条件向中间逼近。
  • 快慢指针:用于解决循环链表的判断、查找中间节点等问题。快指针的移动速度是慢指针的两倍,通过它们的相对速度差来解决问题。

4. 抽象思维

  • 泛化问题解决方案:学习双指针技巧的同时,思考其背后的原理,例如减少不必要的遍历、空间优化等。这些原理不仅仅适用于数组和链表,也可以扩展到其他数据结构和问题上。

5. 条件逻辑与增量思想

  • 逐步构建解决方案:在解决问题时,从最简单的情况开始,逐渐添加条件和逻辑,以构建完整的解决方案。这有助于避免一开始就陷入复杂的情况,使问题更易于理解和解决。
  • 灵活应用条件判断:在使用双指针技巧时,根据问题的需求调整指针的移动条件。例如,满足某条件时移动左指针,不满足时移动右指针,或者两者都移动。

6. 代码优化与重构

  • 迭代与重构:在初步实现功能后,通过测试和验证来识别性能瓶颈或不必要的复杂度,然后迭代优化。这可能涉及调整算法、简化逻辑或利用更高效的数据结构。

通过掌握这些技巧和原则,你可以将双指针方法背后的思想应用到更宽泛的编程问题和数据结构上,提高代码的效率和质量。记住,好的编程实践不仅仅是关于解决一个特定问题,而是学会如何以一种可扩展、可维护的方式思考和实现解决方案。

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

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

相关文章

linux arm 安装nacos(pg数据库版)

要在Linux ARM架构上安装Nacos&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1、下载Nacos压缩包&#xff1a; 首先&#xff0c;你需要下载Nacos的压缩包。访问Nacos的GitHub Release页面&#xff08;nacos下载&#xff09; 2、解压Nacos压缩包&#xff1a; 下载完成后…

Java基础知识(7)

Java基础知识&#xff08;7&#xff09; &#xff08;包括&#xff1a;Arrays工具类&#xff0c;Lambda表达式&#xff0c;单列集合&#xff09; 目录 Java基础知识&#xff08;7&#xff09; 一.Arrays操作数组的工具类。 二. Lambda表达式 1. 函数式编程 2. Lambda表达…

VS Code 远程连接 SSH 服务器

文章目录 一、安装 Remote - SSH 扩展并连接远程主机二、免密连接远程主机1. 生成 SSH 密钥对2. 将公钥复制到远程服务器3. 配置 SSH 客服端4. 连接测试 随着技术的不断迭代更新&#xff0c;在 Linux 系统中使用 Vim、nano 等基于 Shell 终端的编辑器&#xff08;我曾经也是个 …

⚡OWASF 十大 API 安全问题盘点

OWASP 是一个致力于提升 Web 应用程序安全的国际非营利组织&#xff0c;其核心原则之一是公开、免费地提供所有相关资料&#xff0c;方便大众在其官方网站上轻松查阅&#xff0c;助力任何人提升 Web 应用程序的安全性。该组织提供的资料涵盖文档、工具、视频以及论坛等多种形式…

【PowerJob】从源码编译到k8s部署

前言 虽然PowerJob官方说支持JPA各种数据源&#xff0c;但在PG数据库的兼容性上&#xff0c;确实存在小问题&#xff0c;issue也有相关原理描述&#xff0c;官方采用的优雅方式并未真正解决问题&#xff0c;因为只解决了从Lob字段读取的时候&#xff0c;自动建表的时候还是会生…

List集合中对asList的使用

List<String> sArrays.asList(“qwe”,”cvb”,”mnb”); List<String> s1s.subList(1,2); System.out.Pintln(“s”);//输出结果&#xff1a;[qwe,cvb,mnb] System.out.Pintln(“s1”);//输出结果&#xff1a;[cvb] s1.add(“123qwe”);//报错&#xff1a;java…

【机器学习300问】82、RMSprop梯度下降优化算法的原理是什么?

RMSprop&#xff0c;全称Root Mean Square Propagation&#xff0c;中文名称“均方根传播”算法。让我来举个例子给大家介绍一下它的原理&#xff01; 一、通过举例来感性认识 建议你第一次看下面的例子时忽略小括号里的内容&#xff0c;在看完本文当你对RMSprop有了一定理解时…

使用单片机的IO引脚直接驱动段码屏

使用单片机的IO引脚直接驱动段码屏,目的是为了降低成本。这种古老的应用,在低功耗产品中比较多见。 如:水表&#xff0c;燃气表等需要电池供电的产品。 下面纯属个人理解&#xff0c;未经测试。 1/3Duty表示LCD共有3个COM引脚,分别占显示周期的1/3 1/2BIAS表示电压0和VCC 1、…

通义千问2.5中文能力地表最强

随着人工智能技术的不断进步&#xff0c;智能问答系统已成为人们日常生活中不可或缺的一部分。阿里巴巴集团作为全球领先的科技公司&#xff0c;一直致力于AI领域的研发和创新。最近&#xff0c;阿里巴巴发布了其最新的智能问答系统——通义千问2.5。 通义千问2.5在AI问答领域…

企业签名分发跟应用商城分发有什么区别

企业签名分发是移动应用开发者在应用程序发布前测试、内部分发和特定的受众群体分发等方面比较常用的一种工具。那对于应用商城分发有啥区别&#xff0c;下面简单的探讨一下。 独立分发能力 通过企业签名分发开发者可以自己决定应用程序的发布时间和方式&#xff0c;不用受应…

真驱鸟农业专用插电款驱鸟器,防喜鹊、麻雀各种鸟类

成年喜鹊的栖息地非常多样化&#xff0c;它们常常在人类活动的区域出没&#xff0c;喜欢把鸟巢建在民宅旁边的大树上、旷野的高树上或者电线塔杆上。白天&#xff0c;它们会飞到农田等开阔的地方觅食&#xff0c;到了傍晚则会返回巢内栖息。 喜鹊食性杂&#xff0c;它们的食物组…

【Linux】编写一个简易的shell

思维导图 学习目标 将简易的shell代码进行编写。 一、阐述shell的基本思路 在进程程序替换中&#xff0c;我们可以将一个指令交给子进程&#xff0c;让子进程去完成这个指令。如果这个命令是一个内建命令&#xff0c;我们需要将这个命令交给bash进行处理。 大致思路是&#xf…

【更具吸引力的回答】Java中final、finally、finalize的区别(二)

当谈到Java中的final、finally和finalize关键字时&#xff0c;它们各自在编程中扮演着不同的角色。下面我将从您提出的几个方面来详细解释它们之间的区别&#xff1a; 1. final 概念与用途&#xff1a;final关键字在Java中用于表示不可变性。它可以修饰类、方法和变量&#x…

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务 引言 网络文件系统(Network File System)&#xff0c;简称NFS&#xff0c;是一种分布式文件系统协议。它允许网络上的客户端机器像访问本地磁盘文件一样&#xff0c;通过网络访问服务器上的文件。在某些特定的业务场景中…

从drugbank提取药物对应的靶点和基因信息

DrugBank是一个综合性的在线数据库,它提供了关于药物的详细化学、药理、药代动力学和药物-药物相互作用的信息。这个数据库是一个宝贵的资源,广泛用于药物研究、药理学、毒理学、药物设计和个性化医疗等领域。 以下是DrugBank的一些主要特点和用途: 药物信息:DrugBank提供了…

HTTP/1.0、HTTP/1.1、HTTP/2.0区别

文章目录 区别HTTP/1.0HTTP/1.11. 持久连接&#xff08;长连接&#xff09;2. 管道化3. Host头字段4. 分块传输编码5. 缓存机制6. 请求方法 HTTP/2.01. 二进制分帧2. 多路复用3. 服务器推送4. 优先级设置5. 头信息压缩6. 安全性7. 流量控制 区别 特性HTTP/1.0HTTP/1.1HTTP/2.0…

【笔试训练】day23

一、打怪 思路 由于是先手攻击&#xff0c;如果一次攻击就能杀死小怪&#xff0c;那么说明可以为无限杀小怪。 再计算杀一只小怪要扣多少血就好了&#xff0c;再用总生命值去除这个扣血量&#xff0c;得到的就是最多杀死小怪的数量。注意&#xff0c;由于最后一定要活下来&am…

博客系统问题

1.数据库相关的问题&#xff0c;包括定义表的结构、创建数据库表、增删改查操作的实现&#xff1a; Flask程序中&#xff0c;使用了ORM(Object Relation Mapping, 对象关系映射)这种思想来定义实体类并据此创建数据库表。 创建&#xff1a;首先是在代码中定义python类&#xf…

React面试经验2

1.执行顺序题 onClick () > {//athis.setState({num: this.state.num 1,})console.log(1:,this.state.num);//bthis.setState({num: this.state.num 1,})console.log(2:,this.state.num);setTimeout(() > {//cthis.setState({num: this.state.num 1,});console.log(…

将矩阵按对角线排序(Lc1329)——排序

矩阵对角线 是一条从矩阵最上面行或者最左侧列中的某个元素开始的对角线&#xff0c;沿右下方向一直到矩阵末尾的元素。例如&#xff0c;矩阵 mat 有 6 行 3 列&#xff0c;从 mat[2][0] 开始的 矩阵对角线 将会经过 mat[2][0]、mat[3][1] 和 mat[4][2] 。 给你一个 m * n 的整…