Leetcode 2050. 并行课程III

说明

本文默认读者拥有基础的图论知识,或者图的基础算法。

同时,读者掌握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;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值