问题:小R的井集大小期望计算
问题描述
小R有 n 个集合,她想通过随机选择两个集合,并计算它们的并集,来求出这个并集大小的期望值。每个集合中的元素都是唯一的且互不相同。她需要计算出随机选择两个集合并集大小的期望值,并且要求结果保留两位小数。
保证输入至少有两个集合。
测试样例
样例1:
输入:
n = 2,st = [[1, 2], [1, 3, 5]]
输出:'4.00'
样例2:
输入:
n = 3,st = [[1, 4], [2, 5], [3, 6, 7]]
输出:'4.67'
样例3:
输入:
n = 2,st = [[10, 20, 30], [10, 30, 50, 70]]
输出:'5.00'
问题理解
本题的核心目标是计算从 n 个集合中随机选取两个集合并集大小的期望值。由于每个集合内元素唯一且互不相同,我们需要遍历所有可能的两个集合的组合情况,分别求出它们的并集大小,再根据组合数以及这些并集大小来计算期望值,
还有顺便注意一下,按照要求将结果保留两位小数输出。
算法分析
首先注意“由于每个集合内元素唯一且互不相同”,所以我们可以优先考虑HashSet存储每个结果。
然后核心就是两次循环+HashSet
- 通过两层
for
循环实现对所有两两集合组合的遍历。外层循环for (int i = 0; i < n; i++)
依次选取第一个集合,内层循环for (int j = i + 1; j < n; j++)
选取与第一个集合搭配的第二个集合(避免重复组合)。 - 对于每一对集合组合,创建一个
HashSet<Integer>
实例unionSet
来求并集。先使用for (int num : st[i]) { unionSet.add(num); }
将第一个集合st[i]
的元素添加到unionSet
中,再用for (int num : st[j]) { unionSet.add(num); }
把第二个集合st[j]
的元素添加进去,此时unionSet
的大小就是这两个集合并集的大小,将其累加到sum
变量中(sum += unionSet.size();
),同时组合数pairCount
加1
(pairCount++;
)
类似的结构还有很多,在刷题中,这几个非常有用,并且还可以配合迭代器:
迭代器
// 使用keySet迭代器遍历键
Iterator<Integer> keyIterator = treeMap.keySet().iterator();
while (keyIterator.hasNext()) {
int key = keyIterator.next();
System.out.println("Key: " + key);
}
TreeMap 键值对,键唯一。按照键从小到大的顺序存储键值对
TreeSet
从小到大存储元素,元素唯一
priorityQueue
从小到大默认排列,不唯一。
可以使用PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
来从大到小,变成最大的是头部
poll()去除获得头部
peek()获得头部
LinkedHashMap
键值对,键唯一,继承自 HashMap
,它在保持了 HashMap
的快速查找等特性基础上,还能按照元素插入的顺序或者访问的顺序(可通过构造函数参数设置)来记录键值对,在需要记住元素添加顺序或者访问顺序的场景下很实用,比如实现一个简单的最近访问记录功能,以访问的页面 URL 作为键,访问时间等信息作为值存储在 LinkedHashMap
中,按照访问顺序就可以快速查看最近访问了哪些页面。
HashMap 键值对,键唯一。
代码实现(java)
import java.util.HashSet;
import java.util.Set;
import java.io.*;
public class Main {
public static String solution(int n, int[][] st) {
double sum = 0.0;
int pairCount = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Set<Integer> unionSet = new HashSet<>();
for (int num : st[i]) {
unionSet.add(num);
}
for (int num : st[j]) {
unionSet.add(num);
}
sum += unionSet.size();
pairCount++;
}
}
double expectation = sum / pairCount;
return String.format("%.2f", expectation);
}
public static void main(String[] args) {
System.out.println(solution(2, new int[][]{{1, 2}, {1, 3, 5}}).equals("4.00"));
System.out.println(solution(3, new int[][]{{1, 4}, {2, 5}, {3, 6, 7}}).equals("4.67"));
System.out.println(solution(2, new int[][]{{10, 20, 30}, {10, 30, 50, 70}}).equals("5.00"));
}
}
复杂度分析
分析循环部分,有两层循环遍历集合两两组合情况,外层循环执行 n
次,内层循环对于外层循环的每一次执行,平均要执行大约 n / 2
次(因为 j
从 i + 1
开始到 n - 1
),所以总的循环次数大约是 ,即时间复杂度在这部分是 级别。
假设每个集合的最大大小为m
,则对于每一对集合的处理时间复杂度为O(m)
。由于我们需要考虑所有的集合对,因此总的时间复杂度为O(n^2 * m)
,其中n
是集合的数量。