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

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

 

菜鸡写法:

// 函数定义,移除数组nums中所有值为val的元素,并返回新的数组长度
int removeElement(int* nums, int numsSize, int val) {
    // 如果数组长度为0,直接返回0
    if (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,并返回0
        if (*(nums + left) == val) {
            nums = NULL;
            return 0;
        } else {
            // 否则返回1,因为数组中没有需要移除的元素
            return 1;
        }
    }


    // 开始遍历数组,直到left指针超过right指针
    for (; left < right;) {
        // 如果left指向的元素等于val,且right指向的元素不等于val
        if (*(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指针相遇,且指向的元素等于val
    if (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. 代码优化与重构

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Steam游戏搬砖,不说破万,月入5K没问题

steam游戏搬砖项目的玩法就是打汇率差&#xff0c;在steam平台购买道具&#xff0c;挂在网易buff上出售&#xff0c;通过汇率差盈利。一天交易几百美金的道具&#xff0c;大概能搞到200块左右的利润&#xff0c;而且平台是支持这样交易的&#xff0c;还很稳定。目前最主流的游戏…

设计模式1——初步认识篇

设计模式1——初步认识篇 一、先让我们浅聊一下面向对象和设计模式。 说起设计模式&#xff0c;我第一次听到它&#xff0c;是在学习面向对象的时候。那么什么是面向对象&#xff0c;什么是设计模式&#xff0c;而且设计模式和面向对象又有什么关系呢&#xff1f; 1、什么是面…

im8mm 网络卡死 Rx packets:1037578 errors:66 dropped:0 overruns:66 frame:0

1&#xff1a;网络接收数据包异常 2&#xff1a;问题复现 问题在进行网络数据包同吞吐量测试的时候出现的。同时发现&#xff0c;在使用iperf2测试时&#xff0c;是不会出现网络中断卡死的情况&#xff0c;使用 iperf3时才会出现此问题 指令(下面的指令运行在PC2上面&#xff…

十二种网络威胁防护方案

一、SQL注入 SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严&#xff0c;攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句&#xff0c;在管理员不知情的情况下实现非法操作&#xff0c;以此来实现欺骗数据库服务器执行非授权的任…

kali linux更新卡在libc6:amd64 (2.37-15)

适配于linux的windows子系统,wsl2,安装kali linux,运行 sudo apt update 卡在:Setting up libc6:amd64 (2.37-15) … 关机重启、重新修复执行也不行 解决办法:kill当前apt进程或者关机重启kali-linux,然后执行: ssudo mv /usr/sbin/telinit /usr/sbin/telinit.baksu…

安装docker镜像nginx1.26.0版本,与删除docker容器【灵异事件】

为了http3 的这个模块&#xff0c;所以需要升级nginx的版本&#xff0c;需要nginx1.26.0才有 –with-http_v3_module 这个模块 为什么记录一下&#xff1f;因为觉得奇怪 1&#xff1a;删除nginx镜像&#xff0c;显示镜像还被某个容器在使用 luichunluichun:~$ docker rmi ng…

数电——集成计数器

分析 &#xff08;1&#xff09;74161 4位同步&#xff08;cp相同&#xff09;二进制&#xff0c;模16&#xff08;2的4次方&#xff09; 逻辑符号 端口 D0,D1,D2,D3为输入信号 Q0,Q1,Q2,Q3为输出信号 RCO输出进位标志&#xff1a;记满16个数后&#xff0c;输出1 P,T 控…

番外篇 | 利用PyQt5+YOLOv5来搭建目标检测系统(附可视化界面+功能介绍+源代码)

前言:Hello大家好,我是小哥谈。PyQt5是一个Python绑定的Qt库,是用于创建图形用户界面(GUI)和其他应用程序组件的工具包。PyQt5提供了许多GUI元素,如按钮、文本框、标签等,也提供了许多Qt的功能,如网络、数据库、XML等。通过PyQt5可以在Python中使用Qt的丰富功能和强大的工…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…

Python运维之协程

目录 一、定义协程 二、并发 三、异步请求 协程是一种轻量级的线程&#xff0c;它通过保存和恢复寄存器上下文和栈来实现调度切换&#xff0c;从而保留函数执行的状态。 这种机制使得协程在处理I/O密集型任务时效率较高&#xff0c;因为它们可以在I/O操作期间让出CPU&#…

【触摸案例-手势解锁案例-错误的样式 Objective-C语言】

一、然后呢,我们再来说一下这个错误的样式 1.首先,在我们的示例程序里边,我现在来连一条线,一撒手的时候, 它先出来一个,红色的按钮的样式,那么这个时候呢,实际上,是在设置另外一种状态,给按钮的另外一种状态,再去设置另外一张红色的图片,然后呢,再去切换成那一种…

C++青少年简明教程:C++中的常量、变量、表达式和语句

C青少年简明教程&#xff1a;C中的常量、变量、表达式和语句 在C编程中&#xff0c;常量、变量、表达式和语句是基本的编程概念。 常量&#xff08;Constants&#xff09;&#xff1a;在程序中具有固定值的数据称为常量。常量可以是字面值&#xff0c;如整数、浮点数、字符或…

信息系统项目管理基础

目录 一、项目管理概论 1、定义 2、项目管理的十二原则 3、SMART原则 4、项目经理 5、项目的生命周期 二、项目立项管理 1、项目启动过程 三、项目整合管理 1、管理基础 2、项目整合管理过程 ①制定项目章程 ②制定项目管理计划 ③指导与管理项目工作 ④管理项目…

河南大学大礼堂火灾事故引发安防监控对智能分析技术应用的思考

一、方案背景 2024年5月2日&#xff0c;在修缮施工期间的河南大学河南留学欧美预备学校旧址大礼堂发生火情。现场航拍画面显示&#xff0c;大礼堂经过火灾&#xff0c;房顶已经基本坍塌&#xff0c;被火烧过的建筑呈焦黑状。 公开资料显示&#xff0c;大礼堂属河南留学欧美预…

【栈】Leetcode 比较含退格的字符串

题目讲解 844. 比较含退格的字符串 算法讲解 使用栈模拟&#xff0c;但遇到#字符就让栈顶元素出栈&#xff0c;但是在写的过程中有两点需要注意&#xff1a;当#出现在第一个位置&#xff0c;需要特殊处理一下&#xff1b;当栈为空的时候&#xff0c;还出现#字符需要特殊处理…

FFmpeg常用API与示例(二)—— 解封装与转封装

封装层 封装格式(container format)可以看作是编码流(音频流、视频流等)数据的一层外壳&#xff0c;将编码后的数据存储于此封装格式的文件之内。 封装又称容器&#xff0c;容器的称法更为形象&#xff0c;所谓容器&#xff0c;就是存放内容的器具&#xff0c;饮料是内容&…

【操作系统】处理机调度

处理机调度 处理机调度概念调度概念调度时机 调度原则调度算法实时调度优先级翻转 处理机调度概念 调度概念 进程切换&#xff1a; CPU资源的当前占用者切换 保存当前进程在PCB中的执行上下文(CPU状态)恢复下一个进程的执行上下文 处理机调度: 从就绪队列中挑选下一个占用…

在哪里打印资料比较便宜

在数字时代&#xff0c;我们常常需要在各种文档、资料之间穿梭&#xff0c;然而&#xff0c;有时候我们需要的并不是数字版&#xff0c;而是纸质版。那么&#xff0c;在哪里打印资料比较便宜呢&#xff1f; 琢贝云打印以其超低的价格&#xff0c;优质的打印服务&#xff0c;赢…

html划过盒子出现弹窗

<template><div><div class"content">盒子<div class"topUserInfo">弹窗</div></div></div> </template><script> export default {} </script><style lang"less" scoped> .…

P8802 [蓝桥杯 2022 国 B] 出差

P8802 [蓝桥杯 2022 国 B] 出差 分析 很明显&#xff1a;单源最短路径 没有负权边 dijkstra 1.存图 2.准备两个数组 dis[]&#xff1a;更新源点到各个点的距离 vis[]&#xff1a;标记是否访问 3.从源点开始&#xff0c;更新源点到与其邻接的点的距离&#xff0c;每次选…