原地去重通过双指针实现:slow指向去重后末尾,fast遍历数组;当arr[fast]≠arr[slow]时,将arr[fast]赋给arr[++slow];最终有效长度为slow+1。

Java 数组本身是固定长度的,无法真正“删除”元素来缩短数组,但原地去重的核心思想是:**不使用额外数组,只通过移动、覆盖等方式,把去重后的元素紧凑地放在原数组前端,并返回新有效长度**。适用于 int[]、String[] 等基本类型或引用类型数组(需保证元素可比较)。
前提:数组已排序(最常用且高效场景)
若原数组已升序/降序排列(如 [1,1,2,2,3,4,4,5]),可用双指针法在 O(n) 时间、O(1) 空间完成:
- 定义 slow 指针指向去重后子数组的末尾位置(初始为 0)
- 定义 fast 指针遍历整个数组(从 1 开始)
- 当
arr[fast] != arr[slow]时,说明遇到新元素,执行arr[++slow] = arr[fast] - 最终有效长度为
slow + 1,前slow + 1个元素即为去重结果
示例代码:
public static int removeDuplicatesSorted(int[] arr) {
if (arr == null || arr.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < arr.length; fast++) {
if (arr[fast] != arr[slow]) {
arr[++slow] = arr[fast];
}
}
return slow + 1; // 新的有效长度
}
// 调用后 arr 前 len 个元素为去重结果,其余元素保留但忽略
未排序数组:需额外逻辑判断重复
未排序时无法仅靠相邻比较,必须记录已出现的元素。虽然要求“原地”,但**仍需少量辅助空间(如 Set)来查重**,否则时间复杂度会退化到 O(n²)。这是实际开发中的合理妥协:
立即学习“Java免费学习笔记(深入)”;
- 用
LinkedHashSet可保持插入顺序,同时支持 O(1) 查重 - 遍历原数组,将未见过的元素加入 Set 并写回数组前端
- 最后返回 Set 的大小作为新长度
示例代码:
import java.util.LinkedHashSet;
import java.util.Set;
public static int removeDuplicatesUnsorted(Integer[] arr) {
if (arr == null || arr.length == 0) return 0;
Set<Integer> seen = new LinkedHashSet<>();
int writeIndex = 0;
for (Integer x : arr) {
if (seen.add(x)) { // add() 返回 true 表示首次添加
arr[writeIndex++] = x;
}
}
return writeIndex;
}
⚠️ 注意:此方法对基本类型数组(如 int[])需先转为包装类数组;若坚持用 int[],则只能用嵌套循环实现 O(n²) 原地查重(不推荐)。
关键提醒:Java 中“原地”的真实含义
所谓“原地”不是让数组变短,而是:
- 不创建新数组存储结果(避免
new int[n]) - 所有操作在原数组内存地址上完成
- 通过返回值告知调用方“有效长度”,后续逻辑只访问
[0, len)范围 - 超出部分的数据虽未清除,但不再视为有效数据
进阶:泛型支持与对象数组
对自定义对象数组去重,需确保对象正确重写了 equals() 和 hashCode()。使用上述未排序方案时,将 Set<Integer> 替换为 Set<YourType> 即可。若需按特定字段去重(如 Person.name),可配合 Stream.distinct() + 自定义比较器,但该方式会生成新数组,不属于原地操作。


















