数据结构与算法之动态规划: LeetCode 1494. 并行课程 II (Ts版)
·
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;
}
更多推荐
所有评论(0)