ArrayList无法真正原地去重,但可用双指针实现逻辑原地去重:slow指向唯一元素末尾,fast遍历跳过重复值,最后clear冗余部分;值去重需HashSet辅助。

ArrayList 本身不支持真正的“原地去重”(因为底层是动态数组,删除元素会触发移动),但可以通过双指针技巧在 不使用额外集合 的前提下,逻辑上原地去重并收缩有效长度,最后调用 subList().clear() 真正释放多余空间。
核心思路:快慢指针模拟“覆盖式压缩”
类似数组去重的经典双指针法(如 LeetCode 26):
- slow 指向已确定唯一元素的末尾位置(下一个唯一元素该放的地方)
- fast 遍历整个列表,跳过与前一个已保留元素重复的值
- 只保留每个重复组的第一个出现的元素(稳定去重)
代码实现(升序或无序都适用)
注意:此方法要求元素可比较(如实现了 Comparable),或你传入自定义比较逻辑。若元素无序且不可比,需先排序或改用哈希辅助——但那就不是纯双指针了。
<font color="#888">public static <T extends Comparable<T>> void deduplicateInPlace(ArrayList<T> list) {
if (list == null || list.size() <= 1) return;
<pre class='brush:java;toolbar:false;'>int slow = 1; // 第0个元素默认保留
for (int fast = 1; fast < list.size(); fast++) {
// 只有和上一个保留元素不同,才保留当前元素
if (!list.get(fast).equals(list.get(slow - 1))) {
list.set(slow, list.get(fast));
slow++;
}
}
// 删除 slow 之后所有冗余元素
list.subList(slow, list.size()).clear();}
立即学习“Java免费学习笔记(深入)”;
关键细节说明
- 稳定性:保持元素首次出现的相对顺序
- 时间复杂度:O(n),仅一次遍历;空间复杂度:O(1) 额外空间(不计 list 自身)
-
无需排序:上面代码适用于任意顺序,靠
equals()判断相邻是否重复;若想按值去重(非相邻也去),必须先排序,否则双指针无法覆盖全部重复 -
null 安全:若可能含
null,需单独判断,例如Objects.equals(list.get(fast), list.get(slow-1))
如果要“值去重”(非相邻也算重复)?
纯双指针做不到——它只看局部相邻关系。此时必须引入辅助结构(如 Set)记录已见元素,但仍可“原地覆盖”:
<font color="#888">public static <T> void deduplicateByValueInPlace(ArrayList<T> list) {
if (list == null || list.size() <= 1) return;
Set<T> seen = new HashSet<>();
int slow = 0;
for (int fast = 0; fast < list.size(); fast++) {
if (seen.add(list.get(fast))) { // add() 返回 true 表示首次加入
list.set(slow++, list.get(fast));
}
}
list.subList(slow, list.size()).clear();
}</font>这仍是“逻辑原地”:不新建 ArrayList,复用原有底层数组,仅收缩 size。


















