某大厂员工:有个很菜的同事,去了体制内像他这样有学历没技术的,去体制内,是最不浪费个人优势的,能完美避开自己的短处!
你说他不努力吧,好像也不是;你说他强吧,项目一压上来就露馅。
结果人家转头去了体制内,反而像换了个地图。学历能用上,背景能解释得通,表达能力也算加分,关键是不用天天跟 bug、性能、上线窗口硬刚。短板一下被藏起来,长处还被放大了。
所以有时候不是人菜,是赛道选得太折磨。放错地方叫混子,放对地方可能还挺稳。职场这玩意儿,真不是只看能力,还看你会不会挑坑。
算法题:省份数量
isConnected[i][i] 全是 1,这玩意儿别拿来当有效连接算。
省份数量这题,真正要数的不是城市数量,也不是矩阵里有多少个 1,而是有多少坨互相能连到一起的城市。
比如 A 和 B 连,B 和 C 连,那 A、B、C 就算一个省。哪怕 A 和 C 在矩阵里不是直接相连,也不能拆成两个。
这个地方我一般不用 DFS 写,虽然也能过。矩阵题用并查集更顺手,尤其是看到“传递关系”这几个字,基本就该往合并集合上想了。
矩阵大概长这样:
int[][] isConnected = {
{1, 1, 0},
{1, 1, 0},
{0, 0, 1}
};
0 号城市和 1 号城市连着,2 号城市自己一坨,所以答案是 2。
代码别写复杂,关键就三件事:初始化每个城市自己是一个省;看到两个城市相连就合并;最后数根节点。
classSolution{
publicintfindCircleNum(int[][] isConnected){
int n = isConnected.length;
ProvinceSet set = new ProvinceSet(n);
for (int from = 0; from < n; from++) {
for (int to = from + 1; to < n; to++) {
if (isConnected[from][to] == 1) {
set.merge(from, to);
}
}
}
return set.count();
}
staticclassProvinceSet{
privatefinalint[] parent;
privatefinalint[] size;
privateint groups;
ProvinceSet(int n) {
parent = newint[n];
size = newint[n];
groups = n;
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
introot(int city){
while (city != parent[city]) {
parent[city] = parent[parent[city]];
city = parent[city];
}
return city;
}
voidmerge(int a, int b){
int ra = root(a);
int rb = root(b);
if (ra == rb) {
return;
}
if (size[ra] < size[rb]) {
parent[ra] = rb;
size[rb] += size[ra];
} else {
parent[rb] = ra;
size[ra] += size[rb];
}
groups--;
}
intcount(){
return groups;
}
}
}
这里有个细节,内层循环从 from + 1 开始。
因为这个矩阵是对称的:
isConnected[0][1] == isConnected[1][0]
扫两遍没意义。isConnected[i][i] 又永远是自己连自己,也不用看。
这题最容易写偏的地方,是把“直接相连”当成“一个省”。比如:
0 - 1
1 - 2
0 和 2 没有直接连线,但它们通过 1 连上了,所以还是一个省。
并查集处理这种传递关系比较稳,merge(0,1) 后,0 和 1 是一组;再 merge(1,2),2 也被并进来。最后剩几个集合,就有几个省。
复杂度也很直观,矩阵必须扫一遍上三角,所以时间主要是 O(n²)。并查集的查找和合并因为做了路径压缩,基本可以当成很小的常数看。
这题不难,但它很适合拿来判断一个人是不是只会套 DFS。看到“连通块”,脑子里至少要同时有 DFS、BFS、并查集三种解法。矩阵给得这么直接,并查集写起来反而最干净。