哈喽,各位道友,在平时面试过程中,避免不了一些笔试题(算法&SQL),今天分享想一个经典的笔试题,从一个数组、集合中找出重复的元素:
//获取集合中某个重复的元素
List<String> list = Arrays.asList("1","2","3","3","4");
//方法一:使用Stream API找到重复的元素
List<String> duplicates = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
System.out.println("重复的元素: " + duplicates);
//方法二:使用HashMap
List<String> duplicates = new ArrayList<>();
HashMap<String , Integer> map = new HashMap<>();
for (String s : list) {
if (map.containsKey(s)){
map.put(s,map.get(s)+1);
}else {
map.put(s,1);
}
}
map.entrySet().stream().filter(entry -> entry.getValue() > 1).forEach(entry -> duplicates.add(entry.getKey()));
System.out.println("重复的元素: " + duplicates);
//方法三:简单粗暴for双循环
List<String> duplicates = new ArrayList<>();
// 遍历列表中的每一个元素
for (int i = 0; i < list.size(); i++) {
// 再次遍历剩余的元素,检查是否有重复
for (int j = i + 1; j < list.size(); j++) {
if (list.get(i).equals(list.get(j))) {
// 如果还没有添加到重复列表中,则添加
System.out.println(list.get(i));
}
}
}
//方法四:二分法查找
List<String> duplicates = new ArrayList<>();
Collections.sort(list);
// 对排序后的列表进行遍历
for (int i = 0; i < list.size(); i++) {
String current = list.get(i);
// 检查下一个元素是否相同,如果是,则使用二分查找确认重复次数
if (i + 1 < list.size() && current.equals(list.get(i + 1))) {
int firstOccurrence = binarySearchFirstOccurrence(list, current, 0, i);
int lastOccurrence = binarySearchLastOccurrence(list, current, i, list.size() - 1);
if (lastOccurrence - firstOccurrence > 0) {
duplicates.add(current);
}
}
}
private static int binarySearchFirstOccurrence(List<String> list, String target, int start, int end) {
while (start <= end) {
int mid = start + (end - start) / 2;
if (list.get(mid).compareTo(target) < 0) {
start = mid + 1;
} else if (list.get(mid).compareTo(target) > 0) {
end = mid - 1;
} else {
if (mid == start || !list.get(mid - 1).equals(target)) {
return mid;
}
end = mid - 1;
}
}
return -1;
}
private static int binarySearchLastOccurrence(List<String> list, String target, int start, int end) {
while (start <= end) {
int mid = start + (end - start) / 2;
if (list.get(mid).compareTo(target) < 0) {
start = mid + 1;
} else if (list.get(mid).compareTo(target) > 0) {
end = mid - 1;
} else {
if (mid == end || !list.get(mid + 1).equals(target)) {
return mid;
}
start = mid + 1;
}
}
return -1;
}
//方法五:用于存储首次遇到的元素
Set<String> seen = new TreeSet<>();
// 用于存储重复元素
Set<String> duplicates = new LinkedHashSet<>();
for (String element : list) {
// 如果元素已经存在seen中,则表明它是重复的
if (!seen.add(element)) {
duplicates.add(element);
}
}
System.out.println("重复的元素: " + duplicates);
//方法六:在多线程环境下查找重复元素,可以使用ConcurrentHashMap来避免线程安全问题
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
List<String> duplicates = new ArrayList<>();
for (String element : list) {
map.compute(element, (key, value) -> value == null ? 1 : ++value);
}
map.forEach((key, value) -> {
if (value > 1) {
duplicates.add(key);
}
});
System.out.println("重复的元素: " + duplicates);
//方法七:使用Bloom Filter(适用于大数据集)
//对于非常大的数据集,可以使用Bloom Filter来近似查找重复元素。
// Bloom Filter是一个空间效率极高的概率型数据结构,用于测试一个元素是否属于一个集合。它可能会产生假阳性,但不会产生假阴性。
import com.google.common.hash.BloomFilter;
BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), list.size());
List<String> duplicates = new ArrayList<>();
for (String element : list) {
if (bloomFilter.mightContain(element)) {
duplicates.add(element);
} else {
bloomFilter.put(element);
}
}
System.out.println("重复的元素: " + duplicates);
//方法八:使用BitSet(适用于整数列表)
//如果列表中的元素是整数,并且数值范围有限(例如,介于0到某个最大值之间)
//可以使用BitSet来标记每个元素是否出现过。这种方法特别节省空间,因为BitSet使用位来存储信息
List<Integer> duplicates = new ArrayList<>();
List<Integer> intList = list.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
int maxValue = intList.stream().max(Integer::compare).orElse(0);
BitSet bitSet = new BitSet(maxValue + 1);
for (Integer number : intList) {
if (bitSet.get(number)) {
duplicates.add(number);
} else {
bitSet.set(number);
}
}
System.out.println("重复的元素: " + duplicates);
对于前面三种,按照时间复杂度和实际执行效率,我们可以对这四种方法进行排名。请注意,这里的排名主要基于算法的时间复杂度,同时也考虑了空间复杂度和实际执行效率。
使用HashMap和Stream API过滤
时间复杂度: O(n)
空间复杂度: O(n)
效率: 高,因为HashMap操作在平均情况下是常数时间,且Stream API的过滤操作也很快。
适用场景: 数据量较大,需要高效查找重复元素的情况。
使用Stream API的groupingBy和counting
时间复杂度: O(n)
空间复杂度: O(n)
效率: 高,但可能略低于直接使用HashMap,因为内部可能有额外的管理开销。
适用场景: Java 8及以上版本,偏好简洁代码风格,数据量适中。
排序+二分查找
时间复杂度: O(n log n)
空间复杂度: O(1) 或 O(n),取决于排序算法是否原地排序。
效率: 中等,适合数据量较大但已知数据接近排序或排序成本可以接受的情况。
适用场景: 数据量大,且排序成本相对于查找成本较低。
双重for循环
时间复杂度: O(n^2)
空间复杂度: O(1) 或 O(k),其中k是重复元素的数量。
效率: 低,不适合数据量大的情况。
适用场景: 数据量非常小,或者对性能要求不高,且代码简单性是优先考虑的。
综上所述,从效率角度,使用HashMap和Stream API过滤的方法是最佳选择,其次是使用Stream API的groupingBy和counting,然后是排序+二分查找,最后是双重for循环。在实际应用中,应根据具体的数据量、性能需求以及代码的可读性和维护性来选择最合适的方法。
对了,你能写出几种呢?有没有考虑SQL方面呢?
//你可以修改findDuplicates方法,使其接受List<String>并处理字符串形式的数字。
//这需要在方法内部将字符串转换为整数
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
public List<Integer> findDuplicates(List<String> stringList) {
List<Integer> intList = stringList.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
int maxValue = intList.stream().max(Integer::compare).orElse(0);
BitSet bitSet = new BitSet(maxValue + 1);
List<Integer> duplicates = new ArrayList<>();
for (Integer number : intList) {
if (bitSet.get(number)) {
duplicates.add(number);
} else {
bitSet.set(number);
}
}
return duplicates;
}