面對 10 萬級甚至百萬級數組去重,雙重循環 O(n²) 算法將導致嚴重性能下降。本文對比三種方案,推薦基於 HashSet 的 O(n) 線性方案。
public static int[] distinctBruteForce(int[] arr) {
int[] result = new int[arr.length];
int idx = 0;
for (int i = 0; i < arr.length; i++) {
boolean exists = false;
for (int j = 0; j < idx; j++) {
if (arr[i] == result[j]) { exists = true; break; }
}
if (!exists) result[idx++] = arr[i];
}
return Arrays.copyOf(result, idx);
}
public static int[] distinctHashSet(int[] arr) {
Set set = new LinkedHashSet<>(); // 保留順序
for (int num : arr) set.add(num);
return set.stream().mapToInt(Integer::intValue).toArray();
}
public static int[] distinctSort(int[] arr) {
int[] copy = Arrays.copyOf(arr, arr.length);
Arrays.sort(copy);
int idx = 0;
for (int i = 0; i < copy.length; i++) {
if (i == 0 || copy[i] != copy[i-1]) {
copy[idx++] = copy[i];
}
}
return Arrays.copyOf(copy, idx);
}
// HashSet.add 內部委託 HashMap.put
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// HashMap.putVal 關鍵判斷邏輯
Node e;
if ((e = tab[i]) != null) {
// 遍歷鏈表/紅黑樹
for (int binCount = 0; ; ++binCount) {
if (e.hash == hash && (e.key == k || k.equals(e.key))) {
// 找到重複 key
V oldValue = e.value;
e.value = value;
return oldValue;
}
// ... 擴容/樹化邏輯
}
}
// 新增節點
tab[i] = newNode(hash, key, value, null);
關鍵點:
hashCode() 定位桶位置(高位參與擾動運算)equals() 精確比較解決哈希碰撞public class Employee {
private Long id;
private String department;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee that = (Employee) o;
return Objects.equals(id, that.id) &&
Objects.equals(department, that.department);
}
@Override
public int hashCode() {
return Objects.hash(id, department);
}
}
常見錯誤:
| 算法 | 耗時 | equals 調用次數 | 適用場景 |
|---|---|---|---|
| HashSet (LinkedHashSet) | ~26ms | ~3.6K | 通用首選,保留插入順序 |
| TreeSet (Comparator) | ~45ms | ~12K | 需自然序/自定義序去重 |
| 排序+相鄰比較 | ~18ms | 0 (基本類型) | 基本類型、內存極敏感 |
| 雙重循環 | ~378ms | ~28.4M | 教學演示、極小數據 |
// 基本類型
int[] distinct = Arrays.stream(arr).distinct().toArray();
// 對象流(需正確 equals/hashCode)
List unique = employees.stream()
.distinct()
.collect(Collectors.toList());
// 按屬性去重(保留首次出現)
List byId = employees.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Employee::getId))),
ArrayList::new
));