大家好,今天我们来研究一个比较常见的编码问题。 假如现在给我们一个对象数组,它可以是整数数组和字符串数组,也可以是实现 Comparable 接口的任何对象。
带着以下问题,我们来开始今天的文章:
- 我们如何从数组中找到重复的元素?
- 你能用 O(n) 复杂度来解决这个问题吗?
不论在日常工作中,或者在面试中,这都是经常遇到的问题;
其实有多种方法可以解决这个问题,在这里我们将讨论两种比较常见的方法,首先是常规方法,这种方法指将每个元素与其他元素进行比较,其次是使用类似哈希表的数据结构来将问题的时间复杂度从二次降低到线性,当然要增加一些空间复杂度。这也说明通过使用合理的数据结构,我们可以想出更优时间复杂度的算法来解决问题,所以说数据结构和算法的相关知识对程序员非常重要;
方案1- 在 O(n^2)中寻找重复项
在第一种解决方案中,我们将数组中的每个元素与其他每个元素进行比较。 如果它们相同,那么就有重复项,如果不相同,那么就没有重复项,通常把这种方法称为:暴力破解算法
当我们使用这种方案从数组中寻找重复项时,它的时间复杂度就是O (n ^ 2)
public static Set<Integer> findDuplicates(int[] input) {
Set<Integer> duplicates = new HashSet<Integer>();
for (int i = 0; i < input.length; i++) {
for (int j = 1; j < input.length; j++) {
if (input[i] == input[j] && i != j) {
// duplicate element found
duplicates.add(input[i]);
break;
}
}
}
return duplicates;
}
我们将最后的重复项放入到Set集合返回,但是如果面试官问你还有其他优化方案吗?将它的时间复杂度降为O(n);
我们接着往下看
方案2 - 在 O(n) 中寻找重复项
第二个解决方案演示了如何使用合适的数据结构编写更好的算法来解决同样的问题。 我们知道,在 Java 中,由于Set 集合底层是基于散列表数据结构所以不允许重复元素,因此平均情况下插入需要 O(1)
通过HashSet
集合来解决这个问题,我们可以在O(n)
时间内完成,我们在for循环
中将每个元素插入HashSet
中,因为它只允许唯一的元素,所以当我们尝试添加重复元素时候,add()
方法会返回false
;
最后,我们将重复下打印出来,看看是不是可以实现我们的需求;
public static <T extends Comparable<T>> void getDuplicates(T[] array) {
Set<T> dupes = new HashSet<T>();
for (T i : array) {
if (!dupes.add(i)) {
System.out.println("Duplicate element in array is : " + i);
}
}
}
这个方法适用于Java
中任何类型的 Java 数组,比如 Array with Integer
,Array with String
或者任何实现 Comparable
接口的对象,但是不适用于原语数组,因为它们在 Java
中不是对象
代码清单
为了方便大家测试,提供了代码清单,大家可以直接跑一跑
package com.milo.collection.list;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 过滤数组中重复的元素
* @author milogenius
* @date 2020/4/22 23:03
*/
public class DuplicatesFromArray {
public static void main(String args[]) {
int[] withDuplicates = { 1, 2, 3, 1, 2, 3, 4, 5, 3, 6 };
//调用常规方法
Set<Integer> duplicates = findDuplicates(withDuplicates);
System.out.println("input array is : " + Arrays.toString(withDuplicates));
System.out.println("Duplicate elements found in array are : " + duplicates);
// 调用泛型方法
String[] myArray = { "ab", "cd", "ab", "de", "cd" };
System.out.println("input string array is : " + Arrays.toString(myArray));
getDuplicates(myArray);
}
/**
* 时间复杂度是O(n²)
*
* @param input
* @return
*/
public static Set<Integer> findDuplicates(int[] input) {
Set<Integer> duplicates = new HashSet<Integer>();
for (int i = 0; i < input.length; i++) {
for (int j = 1; j < input.length; j++) {
if (input[i] == input[j] && i != j) {
// 发现重复元素
duplicates.add(input[i]);
break;
}
}
}
return duplicates;
}
/**
* 时间复杂度为O(n) ,因为我们使用了HashSet数据结构
*
* @param array
* @return
*/
public static <T extends Comparable<T>> void getDuplicates(T[] array) {
Set<T> dupes = new HashSet<T>();
for (T i : array) {
if (!dupes.add(i)) {
System.out.println("Duplicate element in array is : " + i);
}
}
}
}
Output :
input array is : [1, 2, 3, 1, 2, 3, 4, 5, 3, 6]
Duplicate elements found in array are : [1, 2, 3]
input string array is : [ab, cd, ab, de, cd]
Duplicate element in array is : ab
Duplicate element in array is : cd
总结
我们学习了两种解决如何在数组中找到重复元素的方法,第一个解决方案是暴力破解算法,第二个解决方案是我们使用HashSet
数据结构将第一种方案的时间复杂度从O(n^2)
降为O (n)
,同时也展示了利用泛型实现方法的通用性;