统计参与通信的服务器 图解题解
这道题到底在问什么
- 输入
- grid = [[1,0],[0,1]]
- 输出
- 0(两台各处一行一列,谁也碰不到谁)
- 输入
- grid = [[1,0],[1,1]]
- 输出
- 3(三台都能找到同行或同列的伙伴)
- 输入
- 本节演示 3×4 网格
- 输出
- 3
最优解:一步一步想明白
- 3记住这条:先一遍扫出每行、每列各有几台服务器,再看每台服务器的行总数或列总数是不是大于 1,大于 1 就有同伴、能通信。
- 4先看清这张网格。中间 3 行 4 列是数据,绿色格子是服务器,浅色格子是空位。一共有 4 台服务器:在第 0 行第 0 列、第 0 行第 1 列、第 1 行第 0 列,还有孤零零的第 2 行第 3 列。右边那一列等会儿填每行的服务器总数,底下那一行填每列的服务器总数,现在都是空的小圆点。
- 5扫到第 0 行第 0 列,这格是 1,是一台服务器。把这 1 同时记两笔:第 0 行的行总数加到 1,第 0 列的列总数加到 1。一个格子的值既进行计数、又进列计数,这是这趟扫描的关键。
- 6扫到第 0 行第 1 列,这格是 1,是一台服务器。把这 1 同时记两笔:第 0 行的行总数加到 2,第 1 列的列总数加到 1。一个格子的值既进行计数、又进列计数,这是这趟扫描的关键。
- 7扫到第 0 行第 2 列,这格是 0,空位。行计数、列计数都不加,第 0 行还是 2,第 2 列还是 0。空格子不算服务器,自然不进任何计数。
- 8扫到第 0 行第 3 列,这格是 0,空位。行计数、列计数都不加,第 0 行还是 2,第 3 列还是 0。空格子不算服务器,自然不进任何计数。
- 9扫到第 1 行第 0 列,这格是 1,是一台服务器。把这 1 同时记两笔:第 1 行的行总数加到 1,第 0 列的列总数加到 2。一个格子的值既进行计数、又进列计数,这是这趟扫描的关键。
- 10扫到第 1 行第 1 列,这格是 0,空位。行计数、列计数都不加,第 1 行还是 1,第 1 列还是 1。空格子不算服务器,自然不进任何计数。
- 11扫到第 1 行第 2 列,这格是 0,空位。行计数、列计数都不加,第 1 行还是 1,第 2 列还是 0。空格子不算服务器,自然不进任何计数。
- 12扫到第 1 行第 3 列,这格是 0,空位。行计数、列计数都不加,第 1 行还是 1,第 3 列还是 0。空格子不算服务器,自然不进任何计数。
- 13扫到第 2 行第 0 列,这格是 0,空位。行计数、列计数都不加,第 2 行还是 0,第 0 列还是 2。空格子不算服务器,自然不进任何计数。
- 14扫到第 2 行第 1 列,这格是 0,空位。行计数、列计数都不加,第 2 行还是 0,第 1 列还是 1。空格子不算服务器,自然不进任何计数。
- 15扫到第 2 行第 2 列,这格是 0,空位。行计数、列计数都不加,第 2 行还是 0,第 2 列还是 0。空格子不算服务器,自然不进任何计数。
- 16扫到第 2 行第 3 列,这格是 1,是一台服务器。把这 1 同时记两笔:第 2 行的行总数加到 1,第 3 列的列总数加到 1。一个格子的值既进行计数、又进列计数,这是这趟扫描的关键。
- 17十二个格子扫完,两张计数都齐了。右列的行总数是第 0 行 2 台、第 1 行 1 台、第 2 行 1 台;底行的列总数是第 0 列 2 台、第 1 列 1 台、第 2 列 0 台、第 3 列 1 台。注意这些总数都把服务器自己算进去了。接下来只盯着 4 台服务器,逐台判它能不能通信。
- 18判定规则就一句:对每台服务器,看它所在行的总数,或者所在列的总数,只要有一个大于 1,就说明同行或同列还坐着别的服务器,它就能通信,计入答案。为什么是大于 1 不是大于 0?因为总数把它自己也数进去了,大于 1 才意味着除自己之外还有同伴。下面一台一台来。
- 19看第 0 行第 0 列这台服务器:行总数 2、列总数 2。它所在第 0 行总共 2 台,大于 1,这一行还有别的服务器,能通信,计入答案,能通信数变成 1。
- 20看第 0 行第 1 列这台服务器:行总数 2、列总数 1。它所在第 0 行总共 2 台,大于 1,这一行还有别的服务器,能通信,计入答案,能通信数变成 2。
- 21看第 1 行第 0 列这台服务器:行总数 1、列总数 2。它所在第 0 列总共 2 台,大于 1,这一列还有别的服务器,能通信,计入答案,能通信数变成 3。
- 22看第 2 行第 3 列这台服务器:行总数 1、列总数 1。两个都只有 1,说明它这一行、这一列都只有它自己,找不到能通信的同伴,不计入。把它标红,提醒这是台孤独的服务器。
- 23四台判完:第 0 行的两台靠同一行互相通信,第 1 行第 0 列那台靠第 0 列和上面那台通信,这三台都是绿色、计入答案;只有第 2 行第 3 列那台,行里列里都只有它一个,标红出局。能通信的服务器一共 3 台,这就是最终答案。
⚠️ 容易写错的地方
✗ 错:上来就想用 DFS、BFS 或并查集去连通
✓ 对:只需数清每行、每列的服务器总数,再逐台判大于 1
通信关系不传递:这题只问「同行或同列有没有别人」,不是连通块。直接看行总数、列总数即可,O(m·n) 比建图遍历又快又短
✗ 错:判定写成行总数大于 0 或列总数大于 0
✓ 对:必须严格大于 1,也就是至少 2 台
行总数、列总数把服务器自己也数进去了。大于 1 才代表除自己之外还有同伴;写成大于 0 会把孤独服务器也错算进去
✗ 错:忘了先确认这格有服务器,对空格也套规则
✓ 对:只对 grid[i][j] 等于 1 的格子做判定
空格子哪怕所在行列服务器再多,它本身没有服务器,不该计数。判定前一定要先卡 grid[i][j] 等于 1 这个前提
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def countServers(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
row = [0] * m
col = [0] * n
for i in range(m):
for j in range(n):
row[i] += grid[i][j]
col[j] += grid[i][j]
return sum(
grid[i][j] and (row[i] > 1 or col[j] > 1)
for i in range(m)
for j in range(n)
)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int countServers(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<int> row(m), col(n);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
row[i] += grid[i][j];
col[j] += grid[i][j];
}
}
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
ans += grid[i][j] && (row[i] > 1 || col[j] > 1);
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int countServers(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[] row = new int[m];
int[] col = new int[n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
row[i] += grid[i][j];
col[j] += grid[i][j];
}
}
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1 && (row[i] > 1 || col[j] > 1)) {
++ans;
}
}
}
return ans;
}
}复杂度
时间
O(m·n)
第一趟扫每个格子累加行列计数是 m 乘 n;第二趟再扫一遍判定还是 m 乘 n。两趟相加仍是 O(m·n),也就是格子总数级别
空间
O(m + n)
按峰值算:只额外开了 row、col 两个一维数组,长度分别是 m 和 n,合起来 m 加 n。没有递归、不建图、不开和网格同样大的二维表,所以是线性而非平方
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 统计参与通信的服务器 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不用 DFS、BFS 或并查集?+
因为这题的通信关系不需要传递。题目只问每台服务器「同行或同列有没有另一台」,这是个局部判断,看它所在行的总数、列的总数就够了。建图做连通分量反而把简单问题复杂化:既要遍历每条同行同列的边,又要额外的访问标记或父指针。直接两个一维计数数组,一遍扫累加、一遍扫判定,时间 O(m·n)、空间 O(m+n),最简洁。
行计数和列计数为什么能在同一趟循环里一起算?+
因为每个格子 grid[i][j] 对它所在行和所在列的贡献是各自独立的一次累加。遍历到 (i,j) 时,把这个值加进 row[i],同时加进 col[j],两笔互不干扰。所以一个双重循环走完,row 和 col 两张计数就都完整了,不必扫两遍网格去分别统计行和列。
空间还能再省吗?+
已经是 O(m+n) 两个一维数组,很省了。真要抠,可以先只算列计数 col,再逐行扫的时候即时算出当前行的总数 rowSum,这样只留 col 一个数组,空间降到 O(n);但代码会绕一点,实际意义不大。瓶颈始终在必须看每个格子,时间 O(m·n) 是下不去的。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 统计参与通信的服务器 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。