news 2026/8/7 14:55:10

LeekCode面试经典150题之删除有序数组中的重复项

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeekCode面试经典150题之删除有序数组中的重复项

LeekCode面试经典150题之删除有序数组中的重复项

本篇博文我将针对删除有序数组中的重复项问题(I,II),给出一些解决的方法。两个问题我将分开进行讲解。有些方法是我自己想的(所以有不合理的地方很乐意与大家一起讨论),有些方法是参照的之前的博文中的。本片博文我们还引进了一种新的方法:快慢指针法

一、题目1 :

题目描述:

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

考虑 nums 的唯一元素的数量为 k。去重后,返回唯一元素的数量 k。

nums 的前 k 个元素应包含 排序后 的唯一数字。下标 k - 1 之后的剩余元素可以忽略。

分析:这是移到删除元素的题目。我们可以采用遍历数组,然后直接求每个元素的出现次数,将出现多次的元素删掉。这种方法我在这个题目里面不给出具体的代码。在下面的拿到题目里面会具体给出代码。可以进行参照。还可以使用之前的博文中用到的栈的思想来解决(之前用到栈的思想解决的问题),还可以用我们今天重点要介绍的快慢指针法。接下来我会一一介绍。

1.栈的思想的解决方法:

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """stack_pointer=0length=len(nums)foriinrange(length-1):ifnums[i]!=nums[i+1]:nums[stack_pointer]=nums[i]stack_pointer+=1ifi==length-2:nums[stack_pointer]=nums[i+1]returnstack_pointer+1

这里我们判断数组中的元素,如果当前元素和后面那个元素不一样话,我们就把当前的元素压入栈中,如果相同的话会直接跳到后一个元素。若现在已经是最后一个元素的话,就直接将其压入栈中。
这里我想在进一步解释一下,为什么是最后一个元素就可以直接压入栈中:因为在倒数第二个元素的时候,我们已经判断了倒数第二个数和倒数第一个数是否相等,如果不相等的话,那么这两个数都应该压入栈中,那么到最后一个数的时候直接将它压入栈中是没有问题的。如果他们不相等,那么我们不会将倒数第二数压入栈中,而是会直接来到倒数第一个数,所以将倒数第一个压入栈中也是合理的。其实从上面我们可以看出,为我们在每次压入的都是一串相同的数中的最后一个。

2.快慢指针法

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """slow,fast=0,1whilefast<len(nums):ifnums[slow]!=nums[fast]:slow+=1nums[slow]=nums[fast]fast+=1returnslow+1

在这个方法里面,我们给定快慢两个指针。快的指针总是往后移动,直至移动到和当前慢指针所指的数不一样的时候,我们就将慢指针往后移动一个单位,再将这个不一样的数填入到慢指针当前所指的位置。然后快指针继续往后移动,重复这个操作。最后返回的数组长度就是慢指针+1。这是因为慢指针从零开始,所以+1才是数组的真实长度。

二、题目二:

题目描述:

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

分析:这个题目和刚才那个题目的思路基本一致,但是又比之前的那个题目更难了。这里我们需要控制最多能够重复的次数,并且必须要在原地进行,要在额外空间为 O(1) 下完成。下面我将介绍四种方法。除了第一种方法比较暴力,其他的方法的核心思想其实是共通的。当然这里也包含了栈的方法和快慢指针法,供大家参考学习。

1.暴力方法:

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """foriinnums:whilenums.count(i)>2:nums.remove(i)returnlen(nums)

这种方法效率很低,但是也能够完成题目的要求。核心思想就是对数组中的每个数进行计数,如果数量大于2就删除数组中碰到的第一个这个数。用这种方式就能够达到题目的要求了。如果要求的数量不是2的话,我们直接将while nums.count(i)>2这里的2改成要求的数量即可。

2.快慢指针法:

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """iflen(nums)<=2:returnlen(nums)slow=1fast=2whilefast<len(nums):ifnums[fast]!=nums[slow-1]:slow+=1nums[slow]=nums[fast]fast+=1returnslow+1

这里和之前不一样的是,两个指针要从1和2开始,然后我们需要判断快指针所指的和慢指针的前面那个是否相同,如果相同说明快指针后面可能还有和它相同的,因此我们要将快指针后移,直至快指针指向的不是和慢指针前面的数相同的数,我们就将慢指针后移,然后将快指针当前所指的数赋给慢指针。后面重复此流程。
这里说一下为什么这样做能够控制每个数重复不超过两次 :因为数组是有序的,因此要想快指针与慢指针之前的指针指向的数是相同的,除非慢指针当前所指的数也是相同的。这样慢指针加上慢指针前面的那个数,者两个是六十相同的了,那么慢指针的下一个位置需要指向一个不一样的数,这个数我们通过快指针找到了。
在这种方法下,如果我们要控制的最多出现次数变化的话,我们可以通过改变快慢指针的初始位置,和每次与快指针比较的数的位置来实现。

3.栈的方法

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """stack_pointer=1forxinnums[2:]:ifx!=nums[stack_pointer-1]:stack_pointer+=1nums[stack_pointer]=xreturnmin(stack_pointer+1,len(nums))

在栈的方法下,我们不需要对于数组的零号元素进行判断,因为不论再怎样它都会留下。因此我们的栈顶指针要从1开始。然后我们要判断从数组的第三个位置开始是否与当前栈顶指针指向的前一个位置的数相同,如果相同,那我们找到数组的下一个位置,直至找到与其不同的数,我们就把它放到栈顶指针的下一个位置。然后我们最后返回当前栈顶指针+1和数组长度这两者之间的最小值。

4.计数法

defremoveDuplicates(self,nums):""" :type nums: List[int] :rtype: int """index=count=1foriinrange(1,len(nums)):ifnums[i]==nums[i-1]:count+=1else:count=1ifcount<=2:nums[index]=nums[i]index+=1returnindex

对于这类问题,我们很容易想到要对每个数字重复的次数进行计数。根据重复次数的不同对数字进行不同的操作。
初始计数器为1,如果当前的数字和后一个数字相同,计数器加一。若当前的次数小于等于2的话,会继续往将当前的数赋给index指针所指的数,并且index指针后移。(赋值给index指针的数,都是确定了会保留下来的数)如果大于计数器大于2的话不会管它,会继续往后读取数字,直至读到新的相邻的相同的数,重复上面的操作。因为每次给index位置赋值的时候,index都会往后移动,所以最后返回index即可。

以上就是本次博文的所有内容,如果有什么问题,欢迎来和我讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 7:14:48

35 岁职场焦虑蔓延?为什么网络安全行业越老越值钱?

前几天我表弟小王来找我喝茶&#xff0c;聊着聊着突然问我&#xff1a;“老曹&#xff0c;你说我要不要转行做网络安全啊&#xff1f; 听说这行业挺赚钱的。 “我一听就笑了&#xff0c;这不正好最近我刚研究过这个行业吗&#xff1f; 我跟他说&#xff0c;别看现在各行各业…

作者头像 李华
网站建设 2026/8/5 22:45:19

内网渗透实战干货:12 个优质靶场平台精选,附避坑指南 + 实操技巧合集!

前言 在内网渗透学习中&#xff0c;“实战靶场” 是连接理论与实操的核心桥梁 —— 它能模拟真实企业内网的拓扑结构、漏洞分布和信任关系&#xff0c;让你在合法可控的环境中练手 “跳板机横向移动”“域控提权” 等关键技能。 以下按 “新手入门→进阶实战→专项突破” 三个…

作者头像 李华
网站建设 2026/8/5 20:02:29

50天学习FPGA第16天-verilog的模块与端口

目录 简介 目标 模块 端口 端口列表 简介 在前面学习中,我们对Verilog中的基础内容,基本约定和语法结构等有了一定的理解。在本章中,我们从Verilog的角度对模块和端口做进一步的讨论。 目标 说明Verilog模块定义中的各个组成部分,例如模块名、端口列表、参数、变量声…

作者头像 李华
网站建设 2026/8/5 7:03:47

50天学习FPGA第15天-verilog基本概念

目录 简介 目标 语法约定 空白符 注释 操作符 数字声明 字符串 标识符和关键字 数据类型 简介 本周讨论Verilog中的基本语法结构和约定,后续章节中会使用这些结构和约定。这些约定构成了Verilog语言的基本框架。Verilog模型中的数据类型与实际硬件电路中的数据存储和…

作者头像 李华
网站建设 2026/8/7 6:20:40

基于Docker容器化部署Lsky Pro私有图床系统

在数字内容创作与团队协作中&#xff0c;高效、可控的图片托管与管理一直是个痛点。将图片存储在第三方平台面临链接失效、收费涨价和安全风险。今天&#xff0c;我们将介绍如何通过 Docker容器化技术&#xff0c;快速搭建一个属于自己或团队的私有图床——Lsky Pro&#xff0c…

作者头像 李华