说明
本文默认读者拥有基础的图论知识,或者图的基础算法。
同时,读者掌握C++基础语法。
题目描述
这道题有两种设计方式,运用到不同的知识点。
下面我们先来说说官方答案,算法1:
依照题目意思图中的点存在依赖关系--先修关系。以示例2为讲解例子,也就是我们在修读课程 5 时,我们应当修读完 1,2,3,4 四门课程。
而为了最小化我们的时间成本,我们采取策略1:并行修读先修课程。因为先修课是并行修读的,而下一修读课程(进程)需要等待先修课程的结束,所以开始修读下一课程的时间就是先修课程的结束时间。
我们规定 reach(i) 表示开始修读第 i 门课程的开始时间。time(i) 表示修读第 i 门课程需要的时间。那么依据之前的说明,有 reach(i) = max{ reach(j) + time(j) } ,其中 j ∈ pre[i]。
PS: pre[i] 表示第 i 门课程的先修课程。
所以,此时此刻我门需要取记录相对应的reach数组即可。所以我们可以采取记忆化搜索的方式来进行设计。与此同时,因为在状态转移的过程中只有运用到第 i 门课的先修课程关系,所以我们需要将 relations 数组转置为 pre 数组。
故此可以获得AC代码:
class Solution {
public:
int minimumTime(int n, vector<vector<int>>& relations, vector<int>& time) {
int mx = 0;//初始化mx,mx记录答案
//构建邻接表--表述先修关系
vector<vector<int>> prev(n + 1);
for (auto &relation : relations) {
int x = relation[0], y = relation[1];
prev[y].emplace_back(x);
}
//memory == 文中所述的 reach
unordered_map<int, int> memo;
//使用函数类 编写函数dp
function<int(int)> dp = [&](int i) -> int {
if (!memo.count(i)) {//未计算则计算
int cur = 0;
for (int p : prev[i]) {
cur = max(cur, dp(p));
}
cur += time[i - 1];
memo[i] = cur;
}
return memo[i];
};
//获取答案
for (int i = 1; i <= n; i++) {
mx = max(mx, dp(i));
}
return mx;
}
};
接下来,我们来说说另一种角度的算法2,也就是博主的思考代码。
这个角度也是十分的简单。我们知道要修读第 i 门课程就要修读该课程的先修课程,我们将其记录为 pre[i] ,那么当我们要修读 pre[i] 的课程时,我们就要修读 pre[pre[i]] 的课程。以此类推,我们会知道课程修读一定是从没有先修课的课程开始的(结论1)。这也是题目保证是有向无环图(DAG)的原因,因为只有如此才会存在解。
如果,我们课程视作点,课程的关系视作边。那么我们会得到一份关系图,就如两个示例所画的那样。还记得我们的结论1吗?结论1就是说明,算法的开始是从无入度的课程开始的。修读完一个课程后,我们就要将其从图中剔除(不会重读修读,即一门课只修读一次)。
而从入读为0的点开始,使用完后将其抛出。这样的算法逻辑,我们很快就会想到拓扑排序。
所以为了使用拓扑排序,我们需要将 relations 数组转置为 入度数组(ind) 和 邻接表。
故而我们获得了AC代码:
class Solution {
public:
int minimumTime(int n, vector<vector<int>> &relations, vector<int> &time) {
vector<int> next[n];//邻接表
vector<int> ind(n);//记录点的入度
for (auto &x: relations) {//建图
next[x[0] - 1].push_back(x[1] - 1);//课程号转换成0~n-1
ind[x[1] - 1]++;//获取入读
}
queue<int> q;
vector<int> reach(n);
for (int i = 0; i < n; i++) {
if (!ind[i]) {//压入无入度节点
q.push(i);
}
}
while (!q.empty()) {//拓扑排序
auto x = q.front();
q.pop();
for (auto j: next[x]) {
reach[j] = max(reach[j], reach[x] + time[x]);//更新reach[j]
if (--ind[j] == 0) {//抛出x后,j点入度 - 1
q.push(j);
}
}
}
//获取答案
int res = 0;
for (int i = 0; i < n; i++) {
res = max(res, reach[i] + time[i]);
}
return res;
}
};