题目解析 小R的井集大小期望计算 | 豆包MarsCode AI 刷题

1 阅读4分钟

问题:小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 加 1pairCount++;

类似的结构还有很多,在刷题中,这几个非常有用,并且还可以配合迭代器:

迭代器

// 使用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"));
    }
}


复杂度分析

 O(n2maxLenOfAllElements)O(n^2 * maxLenOfAllElements) 

分析循环部分,有两层循环遍历集合两两组合情况,外层循环执行 n 次,内层循环对于外层循环的每一次执行,平均要执行大约 n / 2 次(因为 j 从 i + 1 开始到 n - 1),所以总的循环次数大约是 ,即时间复杂度在这部分是 O(n2)O(n^2) 级别。

假设每个集合的最大大小为m,则对于每一对集合的处理时间复杂度为O(m)。由于我们需要考虑所有的集合对,因此总的时间复杂度为O(n^2 * m),其中n是集合的数量。