1494. 并行课程 II

描述

  • 给你一个整数 n 表示某所大学里课程的数目,编号为 1 到 n ,数组 relations 中, relations[i] = [xi, yi] 表示一个先修课的关系,也就是课程 xi 必须在课程 yi 之前上。同时你还有一个整数 k

  • 在一个学期中,你 最多 可以同时上 k 门课,前提是这些课的先修课在之前的学期里已经上过了

  • 请你返回上完所有课最少需要多少个学期。题目保证一定存在一种上完所有课的方式

示例 1

输入:n = 4, relations = [[2,1],[3,1],[1,4]], k = 2
输出:3 

解释:上图展示了题目输入的图。在第一个学期中,我们可以上课程 2 和课程 3 。然后第二个学期上课程 1 ,第三个学期上课程 4 。

示例 2

输入:n = 5, relations = [[2,1],[3,1],[4,1],[1,5]], k = 2
输出:4 

解释:上图展示了题目输入的图。一个最优方案是:第一学期上课程 2 和 3,第二学期上课程 4 ,第三学期上课程 1 ,第四学期上课程 5

示例 3

输入:n = 11, relations = [], k = 2
输出:6

提示

  • 1 <= n <= 15
  • 1 <= k <= n
  • 0 <= relations.length <= n * (n-1) / 2
  • relations[i].length == 2
  • 1 < = x i , y i < = n 1 <= x_i, y_i <= n 1<=xi​,yi​<=n
  • x i ! = y i x_i != y_i xi​!=yi​

Typescript 版算法实现


1 ) 方案1:动态规划 + 状态压缩

function minNumberOfSemesters(n: number, relations: number[][], k: number): number {
    const dp = new Array(1 << n).fill(Infinity);
    const need = new Array(1 << n).fill(0);
    for (const edge of relations) {
        need[(1 << (edge[1] - 1))] |= 1 << (edge[0] - 1);
    }
    dp[0] = 0;
    for (let i = 1; i < (1 << n); ++i) {
        need[i] = need[i & (i - 1)] | need[i & (-i)];
        if ((need[i] | i) !== i) {
            continue;
        }
        const valid = i ^ need[i];
        if (bitCount(valid) <= k) {
            dp[i] = Math.min(dp[i], dp[i ^ valid] + 1);
        } else {
            for (let sub = valid; sub; sub = (sub - 1) & valid) {
                if (bitCount(sub) <= k) {
                    dp[i] = Math.min(dp[i], dp[i ^ sub] + 1);
                }
            }
        }
    }
    return dp[(1 << n) - 1];
}

function bitCount(n) {
    let count = 0;
    while (n) {
        count++;
        n &= n - 1;
    }
    return count;
}

2 ) 方案2:深度优先+记忆化搜索

function minNumberOfSemesters(n: number, relations: number[][], k: number): number {
    const pre1: number[] = new Array(n).fill(0);
    for (const r of relations) {
        pre1[r[1] - 1] |= 1 << (r[0] - 1); // r[1] 的先修课程集合,下标改从 0 开始
    }

    const u = (1 << n) - 1; // 全集
    const memo: number[] = new Array(1 << n).fill(-1); // -1 表示没有计算过

    const dfs = (i: number): number => {
        if (i === 0) { // 空集
            return 0;
        }
        if (memo[i] !== -1) { // 之前算过了
            return memo[i];
        }

        let res = Infinity;
        const ci = u ^ i; // i 的补集
        let i1 = 0;
        for (let j = 0; j < n; j++) {
            const p = pre1[j];
            if ((i >> j & 1) > 0 && (p | ci) === ci) { // p 在 i 的补集中,可以学(否则这学期一定不能学)
                i1 |= 1 << j;
            }
        }

        if (countBits(i1) <= k) { // 如果个数小于 k,则可以全部学习,不再枚举子集
            res = dfs(i ^ i1) + 1;
        } else {
            for (let j = i1; j > 0; j = (j - 1) & i1) { // 枚举 i1 的子集 j
                if (countBits(j) === k) {
                    res = Math.min(res, dfs(i ^ j) + 1);
                }
            }
        }

        memo[i] = res; // 记忆化
        return res;
    };

    return dfs(u);
}

// 辅助函数:计算二进制中 1 的个数
function countBits(x: number): number {
    let count = 0;
    while (x > 0) {
        count += x & 1;
        x >>= 1;
    }
    return count;
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐