代码训练LeetCode(7)删除有序数组中的重复项

news2024/9/27 7:26:06

代码训练(7)LeetCode之删除有序数组中的重复项

Author: Once Day Date: 2024年3月10日

漫漫长路,才刚刚开始…

全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客

参考文章:

  • 26. 删除有序数组中的重复项 - 力扣(LeetCode)
  • 力扣 (LeetCode) 全球极客挚爱的技术成长平台

文章目录

      • 代码训练(7)LeetCode之删除有序数组中的重复项
        • 1. 原题
        • 2. 分析
        • 3. 代码实现
          • 3.1 基础实现
          • 3.2 过度优化版本代码
        • 4. 总结

1. 原题

给你一个 非严格递增排列 的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

例如,对于nums = [1, 2, 2, 3],其输出长度为3,输出数据为nums = [1, 2, 3, x]即可。

2. 分析

有一个数组 nums,它是非严格递增的,也就是说,数组中的元素可能会有重复,并且它们是按照顺序排列的。你的任务是要在不改变元素相对顺序的情况下,仅保留每个元素的一个副本,并删除所有的重复元素。你需要在不使用额外数组的条件下完成这个操作,这就意味着你需要在原地修改输入的数组 nums

解题思路:

  1. 保持两个指针,i 作为慢指针,j 作为快指针。
  2. 遍历数组,快指针 j 用于寻找下一个与慢指针 i 指向的数字不同的数字。
  3. 当快指针 j 找到一个新的不同数字时,将慢指针 i 向前移动一位,然后将 j 指向的数字复制到 i 的位置。
  4. 重复步骤2和3,直到快指针 j 遍历完整个数组。

分析步骤:

  • 初始化两个指针 ij,其中 i = 0j = 1
  • j小于数组nums的长度时,执行以下操作:
    • 如果 nums[j]nums[i] 相同,j++,跳过重复的元素。
    • 如果 nums[j]nums[i] 不同,i++,并将 nums[j] 的值赋给 nums[i]
  • 继续执行上述步骤直到 j 遍历完数组。
  • 最终,i+1 就是数组中不重复元素的个数。

举例分析:
如果输入的数组 nums[1, 1, 2],初始时 i=0j=1

  • 因为 nums[j] 等于 nums[i],所以 j++,现在 j=2
  • 现在 nums[j] 不等于 nums[i],所以 i++,将 nums[j] 的值赋给 nums[i],现在 nums 变为 [1, 2, 2]
  • j 继续递增,因为已经到数组末尾,停止循环。
  • 最终 i+1 等于 2,表示数组 nums 中有两个不重复的元素。

性能优化关键点:

  • 尽量减少不必要的操作,例如在快指针找到新元素时才移动慢指针。
  • 避免使用额外的内存空间,直接在原数组上操作。
3. 代码实现
3.1 基础实现
int removeDuplicates(int* nums, int numsSize) {
    if (numsSize == 0) return 0;
    int i = 0;
    for (int j = 1; j < numsSize; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

这段代码含义如下:

  • removeDuplicates 函数接受一个整数数组 nums和它的大小 numsSize,然后返回一个整数表示唯一元素的数量。
  • main 函数中,我们定义了一个示例数组并调用 removeDuplicates 函数。
  • 然后打印出修改后的数组和唯一元素的数量。

运行测试结果如下(仅供参考):

在这里插入图片描述

这段代码性能不算非常高,因为存在nums[i]这种随机访问,并且判断nums[j] != nums[i]在大部分情况也有些多余。

3.2 过度优化版本代码

注意,本部分代码仅供各位C语言爱好者一乐,真实项目别写这种风格代码,上面3.1简单版本的代码更合适

struct temp{
    int last;
    int curr;
};

int removeDuplicates(int* nums, int numsSize) {
    int *deal_nums, *resv_nums, *max_nums;

#define unlikely(x) __builtin_expect(!!(x), 0)
#define likely(x) __builtin_expect(!!(x), 1)

    if (unlikely(numsSize <= 1)) {
        return numsSize;
    }

    max_nums  = &nums[numsSize] - 1;
    deal_nums = nums;
    resv_nums = &nums[1];
    while(likely(deal_nums < max_nums)) {
        struct temp *temp = (struct temp *)deal_nums++;
        if (temp->curr ^ temp->last) {
            if (deal_nums != resv_nums) {
                *resv_nums = temp->curr;
                goto another;
            }
            resv_nums++;
        } 
    }

    while(likely(deal_nums < max_nums)) {
        struct temp *temp = (struct temp *)deal_nums++;
        if (temp->curr ^ temp->last) {
            *resv_nums = temp->curr;
another:
            resv_nums++;
        } 
    }

    return resv_nums - nums;
}

代码中使用了一个结构体 temp,包含两个整型成员 lastcurr,用于同时存储相邻的两个元素。

主要的优化点如下:

  1. 使用 unlikelylikely 宏来优化分支预测,提高代码执行效率。
  2. 使用指针操作来遍历数组,避免了数组下标的计算,提高了性能。
  3. 使用异或运算 ^ 来比较相邻元素是否相等,避免了条件分支的使用,提高了性能。
  4. 使用 goto 语句来优化代码流程,减少了重复的代码。

缺点也很明显:

  • 代码可读性较差,使用了一些技巧性的优化,如位运算和 goto 语句,可能会影响代码的可维护性。
  • 虽然使用了一些优化手段,但整体的时间复杂度仍然是 O(n),与普通的去重算法相比,优化的效果可能并不明显。

这段代码在一定程度上提高了去重操作的性能,但也牺牲了一定的可读性和可维护性。

实际运行结果如下(仅供参考):

在这里插入图片描述

4. 总结

这个题目考查了对数组操作的基本能力,特别是双指针技巧,这是一个常用于解决类似问题的方法。为了提高编程能力,应当熟练掌握数组相关的操作,包括但不限于遍历、插入、删除等。

同时,熟悉双指针技巧在其他问题中的应用也非常重要,即如何在不使用额外空间的情况下修改输入数据







Alt

Once Day

也信美人终作土,不堪幽梦太匆匆......

如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!

(。◕‿◕。)感谢您的阅读与支持~~~

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

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

相关文章

割点原理及封装好的割点类

作者推荐 视频算法专题 预备知识 本分析针对&#xff1a;连通无向图G。 搜索树 节点的父子关系&#xff1a;任意 节点的邻接 节点除了已处理 节点&#xff0c;都是它的子 节点。 以任意一点为根开始DFS&#xff0c;计算所有 节点的父子关系。只保留个子 节点到父 节点形成…

C++学习随笔(2)——引用与函数

经过上章对C有了一个初步认识后&#xff0c;本章我们来学习一下C的一些与C语言不同的新玩样引用&#xff0c;还有C的函数规则。 目录 1. 引用 1.1 引用概念 1.2 引用特性 1.3 常引用 1.4 使用场景 &#xff08;1&#xff09; 做参数 &#xff08;2&#xff09; 做返回值…

【Emgu CV教程】9.1、形态学常用操作之腐蚀

文章目录 一、相关概念1.什么叫形态学2.形态学操作的目的3.形态学都包含哪些操作4.结构元素StructuringElement 二、腐蚀1.什么叫腐蚀2.腐蚀的作用3.腐蚀的函数 三、演示1.原始素材2.代码3.运行结果 一、相关概念 1.什么叫形态学 形态学&#xff0c;英文名称morphology&#…

无需编程技能:Python爬虫与数据可视化毕业论文代写服务

引言 作为一名在软件技术领域深耕多年的专业人士&#xff0c;我不仅在软件开发和项目部署方面积累了丰富的实践经验&#xff0c;更以卓越的技术实力获得了&#x1f3c5;30项软件著作权证书的殊荣。这些成就不仅是对我的技术专长的肯定&#xff0c;也是对我的创新精神和专业承诺…

网络协议常见问题

网络协议常见问题 OSI&#xff08;Open Systems Interconnection&#xff09;模型OSI 封装 TCP/IP协议栈IP数据报的报头TCP头格式UDP头格式TCP (3-way shake)三次握手建立连接&#xff1a;为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立 TCP 连接。每次建立TCP连接…

揭秘海外媒体运营:扩大影响力、跨越边界的创新之道

导语&#xff1a;随着全球化的不断深入&#xff0c;海外市场对于企业和个人的影响力已变得至关重要。在这一背景下&#xff0c;海外媒体运营崭露头角&#xff0c;成为扩大影响力、跨越边界的创新方式。本文将揭秘海外媒体运营的真正含义和功能&#xff0c;以及它如何通过原创内…

easyexcel文件上传

easyexcel文件上传 前言&#xff1a;功能开发中&#xff0c;难免碰到数据上传下载功能&#xff0c;excel上传常见用于报表上传&#xff0c;绩效上传&#xff0c;考勤上传… 使用步骤&#xff1a; 1&#xff0c;编写业务层&#xff1a; 1&#xff0c;添加easyexcel依赖 <…

linux:线程的控制

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程的总结1. 线程的优点2. 线程的缺点3. 线程异常4.线程和进程 二、线程的控制创建线程线程终止线程等待获取返回值 线程分离 总结 前言 本文作为我对于线程的…

基于美洲狮优化算法(Puma Optimizar Algorithm ,POA)的无人机三维路径规划(提供MATLAB代码)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

架构学习总结:企业架构=业务+数据+技术+应用架构

最近再次研读DAMA数据管理知识体系,结合工作对什么是企业架构?如何开展企业架构设计工作有一些新的认识,供大家参考。企业架构包括企业的业务架构、数据架构、技术架构和应用架构,要想做好企业的信息化数字化建设规划,这四个架构都不可缺少,这四个方面的内容共同组成了企…

药品管理系统|基于SSM 框架+ Mysql+Java+的药品管理系统设计与实现(可运行源码+数据库+设计文档+部署说明)

目录 文末获取源码 系统实现 前台首页功能 用户功能模块 管理员功能 员工功能模块 系统设计 数据库设计 lunwen参考 概述 源码获取 文末获取源码 系统实现 前台首页功能 用户功能模块 管理员功能 员工功能模块 系统设计 数据库设计 lunwen参考 概述 随着科学技术的…

区块链和人工智能的关系以及经典案例

目录 1.区块链与人工智能的关系 2.应用案例&#xff1a;基于区块链的医疗数据共享平台 2.1背景 2.2方案 2.3优势 2.4挑战 区块链技术和人工智能&#xff08;AI&#xff09;是两种不同的技术&#xff0c;但它们之间存在着互补关系。区块链技术提供了一种安全、透明、去中心…

设置文件管理器默认列宽 - Win系统

问题 当我们使用Windows系统自带的文件管理器时&#xff0c;手动调整列宽后&#xff0c;进入其他文件夹并不会生效更改&#xff0c;下文介绍解决方案&#xff0c;即如何设置文件管理器默认列宽。 解决方案 打开文件管理器&#xff0c;调整好每一列的列宽&#xff0c;然后点击…

vim基础命令

目录 前言 一.vim基础命令大全 二.vim熟练的好处 三.入门使用命令 四.使用案例 4.1 gg和G 4.2 i 和 u 和 ESC使用 4.3 y$ 和 p 和 u 使用 五.注意事项 前言 启动vim编辑器后自动进入编辑模式&#xff0c;在此模式中输入命令对应vim一个动作&#xff0c;比如&#xff1a;进入编辑…

Java EE之wait和notify

一.多线程的执行顺序 由于多个线程执行是抢占式执行&#xff0c;就会导致顺序不同&#xff0c;同时就会导致出现问题&#xff0c;就比如俩个线程同时对同一个变量进行修改&#xff0c;我们难以预知执行顺序。 但在实际开发中&#xff0c;我们希望代码按一定的逻辑顺序执行&am…

Visual Basic6.0零基础教学(2)—vb中类的介绍和基本控件的属性

Visual Basic 6.0中类的介绍和基本控件的属性 文章目录 Visual Basic 6.0中类的介绍和基本控件的属性前言一、对象的有关概念1.类2.对象3.对象的三要素4.5. VB程序的执行步骤 二、基本控件属性1.修改控件属性的练习案例 总结 前言 大家好&#xff0c;昨天我们学习了vb的简单介…

elasticsearch 深度分页查询 Search_after(图文教程)

Search_after使用 一. 简介二. 不带PIT的search_after查询2.1 构造数据2.2 search_after分页查询2.2 问题 三. 带PIT的search_after查询3.1 构建第一次查询条件3.2 进行下一页查询3.3 删除PIT 四.参考文章 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注…

opencv解析系列 - 基于DOM提取大面积植被(如森林)

Note&#xff1a;简单提取&#xff0c;不考虑后处理&#xff08;填充空洞、平滑边界等&#xff09; #include <iostream> #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include <opencv2/opencv.hpp> using namespace cv…

Feign远程调用错误

说明&#xff1a;记录一次使用Feign远程调用的错误&#xff0c;错误信息如下&#xff1a; },Server stats: [[Server:192.168.222.1:8082; Zone:UNKNOWN; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:…

spring-boot-maven-plugin springboot打包配置问题

目录 一、打包可执行jar 二、打包非可执行jar 三、两种jar对比 springboot项目的pom文件中一般都配置了spring-boot-maven-plugin打包插件。 <!-- 打包插件依赖 --><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-b…