Java算法题:移除数组中的重复项

题目描述

给你一个非递减顺序 排列的整数数组 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

解题思路

核心依然是双指针法(简单版),逻辑比 "最多保留两次" 更易理解:

  1. 边界处理:数组为空直接返回 0;数组长度为 1 直接返回 1(无重复可删)。
  2. 定义慢指针 slow:表示删除重复元素后数组的最后一个有效位置,初始为 0(第一个元素默认保留)。
  3. 定义快指针 fast:遍历数组的指针,初始为 1(从第二个元素开始检查)。
  4. 遍历逻辑:
    • 若 nums[fast] ≠ nums[slow],说明当前元素是新的、不重复的元素:
      • 慢指针后移一位(扩展有效区域);
      • 将 nums[fast] 赋值给 nums[slow](原地覆盖重复值)。
    • 若相等,说明是重复元素,快指针直接后移(跳过该元素)。
  5. 遍历结束后,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
    }
}
代码解释
  1. 边界处理:优先判断空数组,避免后续遍历出现索引越界。
  2. 双指针初始化 :
    • slow = 0:第一个元素一定是有效元素,作为初始有效区域的末尾;
    • fast = 1:从第二个元素开始检查是否重复。
  3. 核心逻辑 :
    • 当快指针找到与慢指针不同的元素时,说明遇到了新元素,先把慢指针后移(给新元素腾位置),再将新元素赋值到慢指针位置,完成 "原地去重";
    • 重复元素仅快指针后移,慢指针不动,相当于跳过重复值。
  4. 返回值 :slow + 1 是新长度(比如 slow 最终是 1,对应索引 0、1 两个有效元素,长度为 2)。
核心考点
  • 双指针法的基础应用(快慢指针分工:遍历 vs 标记有效区域);
  • 利用数组 "非递减" 的特性(重复元素连续,无需额外判断);
  • 原地修改数组的思路(覆盖重复值而非删除);
  • 简单边界条件的处理(空数组、单元素数组)。
易理解的通俗解释

可以把慢指针想象成 "有效数组的守门员",快指针是 "探路者":

  • 探路者每找到一个和守门员位置不同的元素,就喊守门员往前挪一步,然后把这个新元素放到守门员的新位置;
  • 探路者遇到和守门员位置相同的元素,就自己继续往前走,不打扰守门员;
  • 最后守门员的位置 +1,就是有效数组的长度。
相关推荐
BK贝壳1 天前
亿级用户签到位图选型复盘:用 bool-hybrid-array 扛冷启动打卡,也把它和 Roaring/Redis 的边界测清楚了
数据结构·python·性能优化·numpy
拓恒技术1 天前
无人机巡检AI算法的工程化落地:从需求到交付的5个核心环节
人工智能·算法·无人机
晨曦中的暮雨1 天前
限流算法——固定窗口、滑动窗口、漏桶、令牌桶
java·开发语言·算法
怕浪猫1 天前
Claude Code vs Codex:Computer Use 实现原理深度拆解
算法·面试·github
文丑颜不良啊1 天前
LeetCode876~900题目解析
java·leetcode
罗西的思考1 天前
AI 时代的 “房谋杜断”:如何培养能力、品味与判断力
人工智能·算法·机器学习
aloha_7891 天前
Java 基础、集合、异常与 JUnit 5 速查
java·junit·sqlserver
淡海水1 天前
07-02-源码-.NET Core GC分配路径
算法·unity·c#·.netcore·gc·1024程序员节
知守观1 天前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式
独隅1 天前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code