给你一个无向图,无向图由整数 n ,表示图中节点的数目,和 edges 组成,其中 edges[i] = [ui, vi] 表示 ui 和 vi 之间有一条无向边。同时给你一个代表查询的整数数组 queries 。
第 j 个查询的答案是满足如下条件的点对 (a, b) 的数目:
a < bcnt是与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]
解释: 每个点对中,与至少一个点相连的边的数目如上图所示。
示例 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 * 10^41 <= edges.length <= 10^51 <= ui, vi <= nui != vi1 <= queries.length <= 200 <= queries[j] < edges.length
思路
解本题我们先要求出无向图个节点的度degs,遍历各条边即可求出。查询的时候,我们双循环遍历各个顶点组,把他们的度求和,由于顶点组可能有边相连,如果这样我们会把一条边计算两次,还要减去顶点组之间的边数。这就是基本思路。由于点可能很多直接这样求解有可能超时,我们还要优化一下查询。
我们先把节点按度进行升序排序得到新的数组arr。拿到一个查询 q,我们定义两个指针 l 和 r 分别指向arr的首尾,
- 当
arr[l] + arr[r] > q时,arr[r]和arr[l]到arr[r]之间的节点组成节点组有可能符合查询,r向前移继续遍历。 - 当
arr[l] + arr[r]<=q时,l向后移继续遍历。 - 当
l == r时结束遍历
遍历结束后我们只是拿到了有可能符合查询的节点组,我们还好遍历边,把不符合条件的节点组移除。我们在最初遍历边的时候可以用 map 来缓存节点间边的条数,我们再次遍历边时,设 x 和 y 分别为边的两个节点,当 degs[x] + degs[y] > q 并且 degs[x] + degs[y] - map[x][y]<=q 时,从备选节点组中移除。
解题
/**
* @param {number} n
* @param {number[][]} edges
* @param {number[]} queries
* @return {number[]}
*/
var countPairs = function (n, edges, queries) {
const deg = new Array(n + 1).fill(0);
const edgeMap = new Map();
for (let edge of edges) {
let x = Math.min(edge[0], edge[1]);
let y = Math.max(edge[0], edge[1]);
deg[x]++;
deg[y]++;
const key = (x << 16) | y;
edgeMap.set(key, (edgeMap.get(key) || 0) + 1);
}
const arr = Array.from(deg).sort((a, b) => a - b);
let res = Array(queries.length).fill(0);
for (let i = 0; i < queries.length; i++) {
const q = queries[i];
let l = 1;
let r = n;
while (l < r) {
if (arr[l] + arr[r] <= q) {
l++;
} else {
res[i] += r - l;
r--;
}
}
for (const [k, v] of edgeMap.entries()) {
const x = k >> 16;
const y = k & 0xffff;
if (deg[x] + deg[y] > q && deg[x] + deg[y] - v <= q) {
res[i]--;
}
}
}
return res;
};