题目描述
给你一个非递减顺序 排列的整数数组 nums,请你原地 删除重复出现的元素,使得每个元素只出现一次,返回删除后数组的新长度。
要求:
- 不要使用额外的数组空间,必须在原地修改输入数组并使用 O (1) 额外空间完成。
- 数组的 "非递减" 特性意味着重复元素必然连续出现。
- 返回新长度后,调用者会根据该长度打印数组前对应长度的元素(无需关心数组超出新长度的部分)。
示例 1 :输入:nums = [1,1,2]输出:2解释:函数应返回新长度 2,且原数组前两个元素被修改为 1,2,无需考虑数组中超出新长度的元素。
示例 2 :输入:nums = [0,0,1,1,1,2,2,3,3,4]输出:5解释:函数应返回新长度 5,且原数组前五个元素被修改为 0,1,2,3,4。
示例 3 :输入:nums = []输出:0
示例 4 :输入:nums = [5]输出:1
解题思路
核心依然是双指针法(简单版),逻辑比 "最多保留两次" 更易理解:
- 边界处理:数组为空直接返回 0;数组长度为 1 直接返回 1(无重复可删)。
- 定义慢指针
slow:表示删除重复元素后数组的最后一个有效位置,初始为 0(第一个元素默认保留)。 - 定义快指针
fast:遍历数组的指针,初始为 1(从第二个元素开始检查)。 - 遍历逻辑:
- 若
nums[fast]≠nums[slow],说明当前元素是新的、不重复的元素:- 慢指针后移一位(扩展有效区域);
- 将
nums[fast]赋值给nums[slow](原地覆盖重复值)。
- 若相等,说明是重复元素,快指针直接后移(跳过该元素)。
- 若
- 遍历结束后,
slow + 1即为新数组长度(slow是最后一个有效元素的索引,索引从 0 开始)。
该思路时间复杂度 O (n)(仅一次遍历),空间复杂度 O (1),完全满足题目要求,且逻辑比 "最多保留两次" 简化了核心判断。
代码实现
import java.util.Arrays;
public class RemoveDuplicates {
// 主方法:原地删除重复项,每个元素仅保留一次
public static int removeDuplicates(int[] nums) {
// 边界条件:空数组直接返回0
if (nums == null || nums.length == 0) {
return 0;
}
// 慢指针:初始指向第一个有效元素
int slow = 0;
// 快指针:从第二个元素开始遍历
for (int fast = 1; fast < nums.length; fast++) {
// 找到不重复的元素
if (nums[fast] != nums[slow]) {
slow++; // 扩展有效区域
nums[slow] = nums[fast]; // 原地覆盖
}
// 重复元素则仅快指针后移,无需操作
}
// 新长度 = 慢指针索引 + 1
return slow + 1;
}
// 测试用例
public static void main(String[] args) {
// 示例1
int[] nums1 = {1, 1, 2};
int len1 = removeDuplicates(nums1);
System.out.println("新长度:" + len1); // 输出 2
System.out.println("新数组前" + len1 + "个元素:" + Arrays.toString(Arrays.copyOf(nums1, len1))); // [1,2]
// 示例2
int[] nums2 = {0, 0, 1, 1, 1, 2, 2, 3, 3, 4};
int len2 = removeDuplicates(nums2);
System.out.println("新长度:" + len2); // 输出 5
System.out.println("新数组前" + len2 + "个元素:" + Arrays.toString(Arrays.copyOf(nums2, len2))); // [0,1,2,3,4]
// 示例3
int[] nums3 = {};
int len3 = removeDuplicates(nums3);
System.out.println("新长度:" + len3); // 输出 0
// 示例4
int[] nums4 = {5};
int len4 = removeDuplicates(nums4);
System.out.println("新长度:" + len4); // 输出 1
}
}
代码解释
- 边界处理:优先判断空数组,避免后续遍历出现索引越界。
- 双指针初始化 :
slow = 0:第一个元素一定是有效元素,作为初始有效区域的末尾;fast = 1:从第二个元素开始检查是否重复。
- 核心逻辑 :
- 当快指针找到与慢指针不同的元素时,说明遇到了新元素,先把慢指针后移(给新元素腾位置),再将新元素赋值到慢指针位置,完成 "原地去重";
- 重复元素仅快指针后移,慢指针不动,相当于跳过重复值。
- 返回值 :
slow + 1是新长度(比如 slow 最终是 1,对应索引 0、1 两个有效元素,长度为 2)。
核心考点
- 双指针法的基础应用(快慢指针分工:遍历 vs 标记有效区域);
- 利用数组 "非递减" 的特性(重复元素连续,无需额外判断);
- 原地修改数组的思路(覆盖重复值而非删除);
- 简单边界条件的处理(空数组、单元素数组)。
易理解的通俗解释
可以把慢指针想象成 "有效数组的守门员",快指针是 "探路者":
- 探路者每找到一个和守门员位置不同的元素,就喊守门员往前挪一步,然后把这个新元素放到守门员的新位置;
- 探路者遇到和守门员位置相同的元素,就自己继续往前走,不打扰守门员;
- 最后守门员的位置 +1,就是有效数组的长度。