颇有意境的题目。
1、先看题
在经历了昨天的困难题洗礼后,今天又回到了中等难度。
不过是一个中等中较为简单的题。
2、审题
题目再次罗里吧嗦地说了很多。
但是赖着性子看完后可以很快发现,这是一道关于图的题目。
图是一个相对比较复杂的结构。所以在算法中涉及到图的话,可以玩得各种五花八门。特别是困难题目,往难了去的话,图论能够把我这种一般社畜折磨得半死不活。
不过好在,今天的题难度全是图堆起来的。如果有入门级的图论知识的话,今天纯粹是道简单题。
按惯例,我们还是先来提取重点:
richer[]
数组构成了一个有向连通图。- 由于
richer[]
是比较的关系,且题目保证了逻辑自洽,所以这是一个单向无环连通图。 quiet[]
数组记录了每个点的安静值,即为每个点的权重。(注意,这里的权重和图论中的路径加权不是一个概念,因而这不是一个加权图)- 题目需要我们返回的数组,为记录从每个点出发,比它更有钱的点中,安静值最小的点。所以这只是一个单向图中的遍历问题。
此外,除了一些数据边界,便也没有什么需要注意的了。
思路很清晰,开始吧。
3、思路
当知道了题目的考点后,首先要做的自然是先构造出图来。
我初始化一个二维数组map[][]
,记录点和点之间的连通关系。
同时,初始化result[]
,来记录某一点的最终结果。
之后需要的,就只有深度优先遍历所有点的路径,并求其可以连通的所有点中的最小值了。
4、撸代码
class Solution {
public int[] loudAndRich(int[][] richer, int[] quiet) {
int n = quiet.length;
int[] result = new int[n];
byte[][] map = new byte[n][n];
for (int[] rich : richer) {
//由于0比1有钱,故查找时会从1向0查找,设置从1到0的路径
map[rich[1]][rich[0]] = 1;
}
int[] isVis = new int[n];
for (int i = 0; i < n; i++) {
getQuiet(map, quiet, result, isVis, i);
}
return result;
}
private int getQuiet(byte[][] map, int[] quiet, int[] result, int[] isVis, int t) {
//如果该点已经访问,则直接返回结果
if (isVis[t] == 1) {
return result[t];
}
//优先将最安静的指向自身
int lowQuietness = t;
//获取此点的全部路径
byte[] loads = map[t];
for (int i = 0; i < loads.length; i++) {
//路径不通,或遇到此点本身时,跳过
if (loads[i] != 1 || i == t) {
continue;
}
//找到相通的路径时
//获取该点的最小安静度
int quietness = this.getQuiet(map, quiet, result, isVis, i);
//当该点的安静度<当前最小安静度时,修改此点的安静度为该点
if (quiet[quietness] < quiet[lowQuietness]) {
lowQuietness = quietness;
}
}
//记录此点的最小安静点
result[t] = lowQuietness;
//标记已经访问
isVis[t] = 1;
return result[t];
}
}
5、解读
首先,我遍历了richer[]
,并以此构建了一个图。
byte[][] map = new byte[n][n];
for (int[] rich : richer) {
//由于0比1有钱,故查找时会从1向0查找,设置从1到0的路径
map[rich[1]][rich[0]] = 1;
}
如richer[]
中出现了[i,j]
的情况,则说明了i
比j
富裕。
而由于考察的是比某点要富有的所有点的安静值,所以此时我们记录的是穷人到富人的路径,即j->i
,于是我们设置map[j][i] = 1
。
事后我反思此处构建N*N的二维数组是纯属浪费的。
浪费空间不说,还会影响时间。
图构建完后,就可以遵循路径,进行dfs了。
此处,我以isVis
数组来记录了每个点的访问情况。
这一步看似纯粹浪费空间。但事后我比较了许多大牛的解法,他们普遍使用Arrays.fill方法来给最终的结果数组进行了一遍初始化。
从而,我反而节省了一遍遍历的时间。
int[] isVis = new int[n];
for (int i = 0; i < n; i++) {
getQuiet(map, quiet, result, isVis, i);
}
之后就是dfs了,中规中矩的写法。注释也挺完善的,相信各位能看懂。
private int getQuiet(byte[][] map, int[] quiet, int[] result, int[] isVis, int t) {
//如果该点已经访问,则直接返回结果
if (isVis[t] == 1) {
return result[t];
}
//优先将最安静的指向自身
int lowQuietness = t;
//获取此点的全部路径
byte[] loads = map[t];
for (int i = 0; i < loads.length; i++) {
//路径不通,或遇到此点本身时,跳过
if (loads[i] != 1 || i == t) {
continue;
}
//找到相通的路径时
//获取该点的安静度
int quietness = this.getQuiet(map, quiet, result, isVis, i);
//当该点的安静度<当前最小安静度时,修改此点的安静度为该点
if (quiet[quietness] < quiet[lowQuietness]) {
lowQuietness = quietness;
}
}
//记录此点的最小安静点
result[t] = lowQuietness;
//标记已经访问
isVis[t] = 1;
return result[t];
}
6、提交
情况比我预期的要好。
空间换时间后,让时间排名较前。
7、咀嚼
唔……
看上去我的代码循环特别多,但仔细分析则不然。
首先,我遍历了一遍richer[]
,时间复杂度为O(N),N为richer[]
的长度。
之后,就是进行dfs。此处看似遍历了很多次,而且循环套循环。但实际上,我控制了每个点仅会访问一次,实际上是线性的耗时,时间复杂度为O(M),M为quiet[]
的长度,也是点的数量。
于是,整体的时间复杂度实际上是O(N+M)。
由于画了个图,空间复杂度为O(N2)
8、他人的智慧
我扫了题解区的大部分解法,无论语言,大家的思路都如出一辙,并没有看到什么黑科技。
不过倒是看到一个大牛,其思路与我一致,但解法将空间换时间表现到了极致,从而耗时极低。
虽然写得很乱,也没有什么注释,但仍然值得学习下。
9、总结
就如我开头所说,涉及到图的算法题上限极高,下限也不低的。
我本人在这方面就很薄弱,涉及到复杂的多半只能缴械投降。
今天的题还是相较容易的,所以才能信手拈来。
但图论是我不太熟悉的题目,之后值得加大力度去学习。
姑且一步一个脚印,慢慢来吧。
周三了,社畜的一周不知不觉就过半了。
大家共勉!熬过今年,明年就有老头环玩了。(^-^)V