Problem

You are given a Directed Acyclic Graph (DAG) with n nodes labeled from 0 to n - 1, represented by a 2D array edges, where edges[i] = [ui, vi] indicates a directed edge from node ui to vi. Each node has an associated score given in an array score, where score[i] represents the score of node i.

You must process the nodes in a valid topological order. Each node is assigned a 1-based position in the processing order.

The profit is calculated by summing up the product of each node’s score and its position in the ordering.

Return the maximum possible profit achievable with an optimal topological order.

A topological order of a DAG is a linear ordering of its nodes such that for every directed edge u -> v, node u comes before v in the ordering.

Example 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Input: n = 2, edges = [[0,1]], score = [2,3]
Output: 8
Explanation:
![](https://assets.leetcode.com/uploads/2025/03/10/screenshot-2025-03-11-at-021131.png)
Node 1 depends on node 0, so a valid order is `[0, 1]`.
Node | Processing Order | Score | Multiplier | Profit Calculation  
---|---|---|---|---  
0 | 1st | 2 | 1 | 2 × 1 = 2  
1 | 2nd | 3 | 2 | 3 × 2 = 6  
The maximum total profit achievable over all valid topological orders is `2 +
6 = 8`.

Example 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Input: n = 3, edges = [[0,1],[0,2]], score = [1,6,3]
Output: 25
Explanation:
![](https://assets.leetcode.com/uploads/2025/03/10/screenshot-2025-03-11-at-023558.png)
Nodes 1 and 2 depend on node 0, so the most optimal valid order is `[0, 2,
1]`.
Node | Processing Order | Score | Multiplier | Profit Calculation  
---|---|---|---|---  
0 | 1st | 1 | 1 | 1 × 1 = 1  
2 | 2nd | 3 | 2 | 3 × 2 = 6  
1 | 3rd | 6 | 3 | 6 × 3 = 18  
The maximum total profit achievable over all valid topological orders is `1 +
6 + 18 = 25`.

Constraints

  • 1 <= n == score.length <= 22
  • 1 <= score[i] <= 10^5
  • 0 <= edges.length <= n * (n - 1) / 2
  • edges[i] == [ui, vi] denotes a directed edge from ui to vi.
  • 0 <= ui, vi < n
  • ui != vi
  • The input graph is guaranteed to be a DAG.
  • There are no duplicate edges.

Examples

Solution

Method 1 – Bitmask Dynamic Programming (DP over Subsets)

Intuition

Since the number of nodes n is small (≤ 22), we can use bitmask DP to try all possible valid topological orders efficiently. For each subset of nodes, we keep track of the maximum profit achievable by processing those nodes in some valid order.

Approach

  1. For each node, compute its dependencies as a bitmask (which nodes must come before it).
  2. Let dp[mask] be the maximum profit for the subset of nodes represented by mask.
  3. For each subset mask, try to add a node u not in mask whose dependencies are all satisfied (i.e., all its dependencies are in mask).
  4. When adding node u as the next node, its position is popcount(mask) + 1.
  5. Update dp[mask | (1 << u)] with the new profit.
  6. The answer is max(dp[mask]) for all masks with all nodes included.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
    int maximumProfit(int n, vector<vector<int>>& edges, vector<int>& score) {
        vector<int> dep(n, 0);
        for (auto& e : edges) dep[e[1]] |= 1 << e[0];
        int N = 1 << n;
        vector<int> dp(N, INT_MIN);
        dp[0] = 0;
        for (int mask = 0; mask < N; ++mask) {
            int pos = __builtin_popcount(mask) + 1;
            for (int u = 0; u < n; ++u) {
                if (!(mask & (1 << u)) && (dep[u] & mask) == dep[u]) {
                    int nmask = mask | (1 << u);
                    dp[nmask] = max(dp[nmask], dp[mask] + score[u] * pos);
                }
            }
        }
        return *max_element(dp.begin(), dp.end());
    }
};
 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
func maximumProfit(n int, edges [][]int, score []int) int {
    dep := make([]int, n)
    for _, e := range edges {
        dep[e[1]] |= 1 << e[0]
    }
    N := 1 << n
    dp := make([]int, N)
    for i := range dp {
        dp[i] = -1 << 30
    }
    dp[0] = 0
    for mask := 0; mask < N; mask++ {
        pos := bits.OnesCount(uint(mask)) + 1
        for u := 0; u < n; u++ {
            if mask&(1<<u) == 0 && (dep[u]&mask) == dep[u] {
                nmask := mask | (1 << u)
                if dp[mask]+score[u]*pos > dp[nmask] {
                    dp[nmask] = dp[mask] + score[u]*pos
                }
            }
        }
    }
    ans := 0
    for _, v := range dp {
        if v > ans {
            ans = v
        }
    }
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
    public int maximumProfit(int n, int[][] edges, int[] score) {
        int[] dep = new int[n];
        for (int[] e : edges) dep[e[1]] |= 1 << e[0];
        int N = 1 << n;
        int[] dp = new int[N];
        Arrays.fill(dp, Integer.MIN_VALUE);
        dp[0] = 0;
        for (int mask = 0; mask < N; mask++) {
            int pos = Integer.bitCount(mask) + 1;
            for (int u = 0; u < n; u++) {
                if ((mask & (1 << u)) == 0 && (dep[u] & mask) == dep[u]) {
                    int nmask = mask | (1 << u);
                    dp[nmask] = Math.max(dp[nmask], dp[mask] + score[u] * pos);
                }
            }
        }
        int ans = 0;
        for (int v : dp) ans = Math.max(ans, v);
        return ans;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
    fun maximumProfit(n: Int, edges: Array<IntArray>, score: IntArray): Int {
        val dep = IntArray(n)
        for (e in edges) dep[e[1]] = dep[e[1]] or (1 shl e[0])
        val N = 1 shl n
        val dp = IntArray(N) { Int.MIN_VALUE }
        dp[0] = 0
        for (mask in 0 until N) {
            val pos = mask.countOneBits() + 1
            for (u in 0 until n) {
                if ((mask and (1 shl u)) == 0 && (dep[u] and mask) == dep[u]) {
                    val nmask = mask or (1 shl u)
                    dp[nmask] = maxOf(dp[nmask], dp[mask] + score[u] * pos)
                }
            }
        }
        return dp.maxOrNull() ?: 0
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Solution:
    def maximumProfit(self, n: int, edges: list[list[int]], score: list[int]) -> int:
        dep = [0] * n
        for u, v in edges:
            dep[v] |= 1 << u
        N = 1 << n
        dp = [float('-inf')] * N
        dp[0] = 0
        for mask in range(N):
            pos = bin(mask).count('1') + 1
            for u in range(n):
                if not (mask & (1 << u)) and (dep[u] & mask) == dep[u]:
                    nmask = mask | (1 << u)
                    dp[nmask] = max(dp[nmask], dp[mask] + score[u] * pos)
        return max(dp)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
impl Solution {
    pub fn maximum_profit(n: i32, edges: Vec<Vec<i32>>, score: Vec<i32>) -> i32 {
        let n = n as usize;
        let mut dep = vec![0; n];
        for e in &edges {
            dep[e[1] as usize] |= 1 << e[0];
        }
        let N = 1 << n;
        let mut dp = vec![i32::MIN; N];
        dp[0] = 0;
        for mask in 0..N {
            let pos = mask.count_ones() + 1;
            for u in 0..n {
                if (mask & (1 << u)) == 0 && (dep[u] & mask) == dep[u] {
                    let nmask = mask | (1 << u);
                    dp[nmask] = dp[nmask].max(dp[mask] + score[u] * pos as i32);
                }
            }
        }
        *dp.iter().max().unwrap()
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
    maximumProfit(n: number, edges: number[][], score: number[]): number {
        const dep: number[] = Array(n).fill(0);
        for (const [u, v] of edges) dep[v] |= 1 << u;
        const N = 1 << n;
        const dp: number[] = Array(N).fill(-Infinity);
        dp[0] = 0;
        for (let mask = 0; mask < N; mask++) {
            const pos = mask.toString(2).split('1').length - 1 + 1;
            for (let u = 0; u < n; u++) {
                if ((mask & (1 << u)) === 0 && (dep[u] & mask) === dep[u]) {
                    const nmask = mask | (1 << u);
                    dp[nmask] = Math.max(dp[nmask], dp[mask] + score[u] * pos);
                }
            }
        }
        return Math.max(...dp);
    }
}

Complexity

  • ⏰ Time complexity: O(n^2 * 2^n), since for each subset (2^n), we check n nodes and each check is O(n) for bit operations.
  • 🧺 Space complexity: O(2^n), for the DP array.