lcGraphDirected1 - 有向图 1

0 有向图的环路判断

  • 1 bfs访问节点的数目超过n本身才能说明有环
  • 2 或者dfs能够访问到之前访问过的节点,也说明有环
  • 3 不能仅仅通过是否有出度为0的节点来判断是否成环,eg:
    [[0,1],[1,1]]

1857largestPathValue 路径最大节点颜色数

1 题目

https://leetcode-cn.com/problems/largest-color-value-in-a-directed-graph/

2 解题思路

  • 1 自己的思路:自然能够想到的解题方法, 对于出度为0的点做dfs,然后统计每个颜色的值
  • 2 思路存在的问题:

    上述算法的遍历节点总是从出度为0的地方开始遍历所有可能的路径
    有重复计算的第方,想象一下,1, 2分别连着3,3后面跟了1000个节点
    那么会从1,2分别计算一遍,那3后面的1000个节点被重复计算了2次

  • 3 解决思路:

    很显然我们就会想到从小规模开始计算,那么1,2的计算结果就能利用
    小规模的值得到了,那么什么样的节点算是小规模子图的起始点?
    这隐藏了一个拓扑排序 + 动态规划的思路在其中:
    如果想要减少上述重复计算过程,可以考虑使用动态规划,但是
    需要直到1,2,3以及后面节点谁在前谁在后的问题,使用拓扑排序解决
    这里可以顺便说一下
    首先bfs获得拓扑排序,依此入栈s,(则s的栈顶得到的是没有后继节点的
    那些节点),则依此从s中pop然后得到节点v,dp[v][c]代表从v出发的
    颜色为c的最大值
    ,则对于每个有到达v的边的u,有:
    dp[u][c] = max(dp[u][c], dp[v][c]);
    但是这个需要知道,v的前级节点有哪些,则需要翻转一遍v,所以就
    bfs拓扑排序依此入队列,从队头出数据(这些数据都是没有入度的),
    dp[v][c] 表示到达节点v的所有路径的颜色为c的最大值(并未统计v节点本身)
    对于u所有的 -> v:
    dp[v][c] = max(dp[u][c], dp[v][c]); // 广度优先遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    class Solution {
    public:
    int ans = -1;

    void dfs(int from, string& colors, vector<vector<int>>& g, map<char, int>& cntInPath) {
    cntInPath[colors[from]]++;
    ans = max(ans, cntInPath[colors[from]]);
    for(int to = 0; to < g.size(); ++to) {
    if(g[from][to] == 1) {
    dfs(to, colors, g, cntInPath);
    }
    }
    cntInPath[colors[from]]--;
    }

    int largestPathValue(string colors, vector<vector<int>>& edges) {
    // // 自然能够想到的解题方法:
    // // 基于出度为0的点做dfs,然后统计每个颜色的值
    // vector<int> noInNode;
    // map<int, int> inDegreeStatistic;
    // for(auto &e : edges) {
    // inDegreeStatistic[e[1]]++;
    // }
    // int n = colors.size();
    // for(int i = 0; i < n; ++i) {
    // auto it = inDegreeStatistic.find(i);
    // if(it == inDegreeStatistic.end()) {
    // noInNode.emplace_back(i);
    // }
    // }

    // if(noInNode.size() == 0){
    // return -1;
    // }

    // vector<vector<int>> g(n, vector<int>(n, -1));
    // for(int i = 0; i < edges.size(); ++i) {
    // g[edges[i][0]][edges[i][1]] = 1;
    // }

    // // 对每一个节点出度为0的遍历
    // int maxApperanceNumInPath = -1;
    // map<char, int> cntInPath;
    // for(auto& c : colors) {
    // cntInPath[c] = 0;
    // }
    // for(auto& n : noInNode) {
    // dfs(n, colors, g, cntInPath);
    // }

    // 上述算法的遍历节点总是从出度为0的地方开始遍历所有可能的路径
    // 有重复计算的第方,想象一下,1, 2分别连着3,3后面跟了1000个节点
    // 那么会动1,2分别计算一遍,那3后面的1000个节点被重复计算了2次
    // 很显然我们就会想到从小规模开始计算,那么1,2的计算结果就能利用
    // 小规模的值得到了,那么什么样的节点算是小规模子图的起始点?
    // 这隐藏了一个拓扑排序 + 动态规划的思路在其中:
    // 如果想要减少上述重复计算过程,可以考虑使用动态规划,但是
    // 需要直到1,2,3以及后面节点谁在前谁在后的问题,使用拓扑排序解决
    // dp[v][c] 表示到达节点v的所有路径的颜色为c的最大值(并未统计v节点本身)
    // 对于u所有的 -> v:
    // dp[v][c] = max(dp[u][c], dp[v][c]); // 广度优先遍历

    // 构建邻接表,统计入度情况
    vector<int> noInNode;
    int n = colors.size();
    vector<vector<int>> g(n);

    map<int, int> inDeg;
    for(auto &e : edges) {
    inDeg[e[1]]++;
    g[e[0]].emplace_back(e[1]);
    }

    // 做拓扑排序,首先找到那些个入度为0的点
    vector<array<int, 26>> dp(n);
    deque<int> q;
    for(int i = 0; i < n; ++i) {
    auto it = inDeg.find(i);
    if(it == inDeg.end()) {
    q.push_back(i);
    }
    }

    // bfs访问节点的数目超过n本身才能说明有环
    // 或者dfs能够访问到之前访问过的节点,也说明有环
    // 不能仅仅通过是否有出度为0的节点来判断是否成环,eg:
    // [[0,1],[1,1]]
    int bfsTravelNum = 0;
    // 使用bfs遍历获取拓扑排序顺便动态规划
    while(!q.empty()) {
    ++bfsTravelNum;
    // 取出一个没有入度的节点
    int u = q.front();
    q.pop_front();
    // 访问到u节点
    dp[u][colors[u] - 'a']++;

    // 更新所有 以v为终点的路径(不包含v本身) 的颜色为c的最大节点数
    for(int v : g[u]) {
    inDeg[v]--;
    for(int c = 0; c < 26; ++c) {
    dp[v][c] = max(dp[v][c], dp[u][c]);
    }
    // 位于u拓扑排序后面的v
    if(0 == inDeg[v]) {
    q.push_back(v);
    }
    }
    }

    if(bfsTravelNum != n) return -1;

    for(int i = 0; i < n; ++i) {
    ans = max(ans, *max_element(dp[i].begin(), dp[i].end()));
    }

    return ans;
    }
    };

3 环路判断

  • 1 bfs访问节点的数目超过n本身才能说明有环
  • 2 或者dfs能够访问到之前访问过的节点,也说明有环
  • 3 不能仅仅通过是否有出度为0的节点来判断是否成环,eg:
    [[0,1],[1,1]]