leetcode
leetcode 1551 ~ 1600
统计点对的数目

统计点对的数目

难度:

标签:

题目描述

给你一个无向图,无向图由整数 n  ,表示图中节点的数目,和 edges 组成,其中 edges[i] = [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。

j 个查询的答案是满足如下条件的点对 (a, b) 的数目:

  • a < b
  • cnt 是与 a 或者 b 相连的边的数目,且 cnt 严格大于 queries[j] 。

请你返回一个数组 answers ,其中 answers.length == queries.length 且 answers[j] 是第 j 个查询的答案。

请注意,图中可能会有 多重边 。

 

示例 1:

输入:n = 4, edges = [[1,2],[2,4],[1,3],[2,3],[2,1]], queries = [2,3]
输出:[6,5]
解释:每个点对中,与至少一个点相连的边的数目如上图所示。
answers[0] = 6。所有的点对(a, b)中边数和都大于2,故有6个;
answers[1] = 5。所有的点对(a, b)中除了(3,4)边数等于3,其它点对边数和都大于3,故有5个。

示例 2:

输入:n = 5, edges = [[1,5],[1,5],[3,4],[2,5],[1,3],[5,1],[2,3],[2,5]], queries = [1,2,3,4,5]
输出:[10,10,9,8,6]

 

提示:

  • 2 <= n <= 2 * 104
  • 1 <= edges.length <= 105
  • 1 <= ui, vi <= n
  • ui != vi
  • 1 <= queries.length <= 20
  • 0 <= queries[j] < edges.length

代码结果

运行时间: 279 ms, 内存: 50.3 MB


/*
 思路:
 1. 构建一个图的数据结构来存储节点和边的关系。
 2. 计算每个节点的度数(与该节点相连的边的数目)。
 3. 对于每个查询,通过计算满足条件的点对的数目来得到结果。
 4. 使用Java Stream来简化处理。
*/

import java.util.*;
import java.util.stream.*;

public class Solution {
    public int[] countPairs(int n, int[][] edges, int[] queries) {
        int[] degree = new int[n + 1];
        Map<Integer, Integer>[] edgeCount = new Map[n + 1];
        for (int i = 1; i <= n; i++) {
            edgeCount[i] = new HashMap<>();
        }
        for (int[] edge : edges) {
            int u = edge[0], v = edge[1];
            degree[u]++;
            degree[v]++;
            edgeCount[u].put(v, edgeCount[u].getOrDefault(v, 0) + 1);
            edgeCount[v].put(u, edgeCount[v].getOrDefault(u, 0) + 1);
        }
        int[] sortedDegree = degree.clone();
        Arrays.sort(sortedDegree);
        return IntStream.range(0, queries.length)
                .map(i -> {
                    int q = queries[i];
                    int total = 0;
                    int left = 1, right = n;
                    while (left < right) {
                        if (sortedDegree[left] + sortedDegree[right] > q) {
                            total += right - left;
                            right--;
                        } else {
                            left++;
                        }
                    }
                    for (int u = 1; u <= n; u++) {
                        for (int v : edgeCount[u].keySet()) {
                            if (degree[u] + degree[v] > q && degree[u] + degree[v] - edgeCount[u].get(v) <= q) {
                                total--;
                            }
                        }
                    }
                    return total;
                }).toArray();
    }
}

解释

方法:

此题解采用了一种基于计数的方法,通过统计每个节点的度数,以及每种度数出现的频率,来高效计算满足条件的点对数量。主要步骤如下: 1. 计算每个节点的度数,并统计每条边出现的次数。 2. 使用度数统计来预计算所有可能的度数和的点对数量,这里忽略了多重边的特殊处理。 3. 调整计数以考虑多重边,即对于图中实际存在的每条边,调整其对应的度数和的点对计数。 4. 通过计算后缀和来快速回答每个查询,即对于每个查询值,计算所有度数和大于该查询值的点对数量。

时间复杂度:

O(E + d^2 + Q)

空间复杂度:

O(n + E + d + Q)

代码细节讲解

🦆
在题解中提到的`调整计数以考虑多重边`的过程是如何操作的?请详细解释其对点对计数的具体影响。
在计算点对的过程中,题解首先计算了所有可能的度数和,然后针对每条边进行调整。具体地,对于图中的每条边(x, y),若边(x, y)为多重边,其存在次数为c,则会影响节点x和y的度数和的计算。首先,原始计算中假设x和y之间的联系只计算一次,因此需要从度数和s(deg[x] + deg[y])中减去这个点对一次,然后再加入调整后的点对计数,即在s - c(实际的度数和,考虑到多重边应该减去重复计算的边数)处加一。这种调整是必须的,因为多重边意味着在计算x和y的度数和时,可能会重复计数,导致对该点对的统计不准确。
🦆
为何在计算所有可能的度数和的点对数量时,对于同一度数的节点使用了`c1 * (c1 - 1) // 2`的计算方式?这里的逻辑是什么?
使用`c1 * (c1 - 1) // 2`的计算方式是为了统计相同度数的节点间的点对数量。这个计算公式基于组合数学中的组合公式,即从c1个相同度数的节点中选取2个节点可以形成的点对数。公式中的`c1 * (c1 - 1) / 2`表示从c1个元素中任意选择2个不同的元素的方法数,这是因为每个点与其他点都可以形成一个不重复的点对,而每个点对在统计时只能计算一次,故需要除以2以避免重复计数。
🦆
题解中提到的后缀和数组是如何构建的,其在解题过程中具体扮演了什么角色?
后缀和数组是通过从后往前累加得到的。具体来说,首先计算了不同度数和下的点对数,然后从数组的最后一个元素开始向前累加,每个位置存储的是从当前度数和到最大度数和之间所有点对的总和。这样构建的后缀和数组使得对于任何一个查询值q,只需通过访问数组中的q+1位置即可直接得到所有度数和大于q的点对总数,极大提高了查询效率,避免了对每个查询重复计算或线性扫描的需要。

相关问题