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

2024-05-11 06:12

本文主要是介绍每日一题——力扣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. 代码优化与重构

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

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

这篇关于每日一题——力扣27. 移除元素(举一反三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978720

相关文章

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

遮罩,在指定元素上进行遮罩

废话不多说,直接上代码: ps:依赖 jquer.js 1.首先,定义一个 Overlay.js  代码如下: /*遮罩 Overlay js 对象*/function Overlay(options){//{targetId:'',viewHtml:'',viewWidth:'',viewHeight:''}try{this.state=false;//遮罩状态 true 激活,f

每日一题|牛客竞赛|四舍五入|字符串+贪心+模拟

每日一题|四舍五入 四舍五入 心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。 四舍五入 题目: 牛牛发明了一种新的四舍五入应用于整数,对个位四舍五入,规则如下 12345->12350 12399->12400 输入描述: 输入一个整数n(0<=n<=109 ) 输出描述: 输出一个整数

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

每日一练7:简写单词(含链接)

1.链接 简写单词_牛客题霸_牛客网 2.题目 3.代码1(错误经验) #include <iostream>#include <string>using namespace std;int main() {string s;string ret;int count = 0;while(cin >> s)for(auto a : s){if(count == 0){if( a <=

两数之和--力扣1

两数之和 题目思路C++代码 题目 思路 根据题目要求,元素不能重复且不需要排序,我们这里使用哈希表unordered_map。注意题目说了只对应一种答案。 所以我们在循环中,使用目标值减去当前循环的nums[i],得到差值,如果我们在map中能够找到这个差值,就说明存在两个整数的和为目标值。 如果没有找到,就将当前循环的nums[i]以及下标i放入map中,以便后续查

树莓派5_opencv笔记27:Opencv录制视频(无声音)

今日继续学习树莓派5 8G:(Raspberry Pi,简称RPi或RasPi)  本人所用树莓派5 装载的系统与版本如下:  版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下: 今天就水一篇文章,用树莓派摄像头,Opencv录制一段视频保存在指定目录... 文章提供测试代码讲解,整体代码贴出、测试效果图 目录 阶段一:录制一段