和谐程序组
问题背景
在一个计算机系统中,存在多个待运行的程序。部分程序之间存在互斥关系,意味着它们不能同时运行。我们需要从所有程序中找出一个符合特定规则的"和谐"子集。
核心定义
-
互斥(Mutually Exclusive):
如果两个程序是互斥的,它们就不能被放入同一个程序组中。
-
和谐程序组(Harmonious Program Group):
一个程序的集合,如果其中任意两个程序之间都不存在互斥关系,那么这个集合就被称为一个"和谐程序组"。
筛选规则
我们需要在所有可能的"和谐程序组"中,通过以下两条规则筛选出唯一的目标程序组:
- 规则 1 (数量最多) : 首先,目标程序组的程序数量必须是最多的。
- 规则 2 (运行时间之和最小) : 如果满足规则1的程序组有多个(即数量最多的和谐程序组不唯一),则在这些候选项中,选择那个所有程序运行时间之和最小的。
任务要求
给定所有程序的运行时间 appRunTimes
和一个描述互斥关系的列表 mutexs
,请找出最终符合上述双重规则的目标和谐程序组,并返回该组所有程序的运行时间总和。
输入格式
-
appRunTimes
: 第一个参数,一个整数数组。- 数组的下标
i
表示程序编号 ,值为appRunTimes[i]
,表示对应程序的运行时间。 1 <= appRunTimes.length <= 20
1 <= appRunTimes[i] <= 100
- 数组的下标
-
mutexs
: 第二个参数,一个二维整数数组,代表互斥程序对列表。mutexs
中的每个元素[prog1, prog2]
表示程序prog1
和prog2
之间存在互斥关系。1 <= mutexs.length <= appRunTimes.length * (appRunTimes.length - 1) / 2
0 <= prog1, prog2 < appRunTimes.length
(程序编号与appRunTimes
的下标对应)。
输出格式
- 一个整数,表示最终选出的那个和谐程序组的所有程序运行时间之和。
样例说明
样例 1
-
输入:
appRunTimes = [1, 2, 10]
mutexs = [[0, 1]]
(注意:根据题目解释,我们假设互斥对指的是程序编号/数组下标)
-
输出 :
11
-
解释:
-
程序信息:
- 程序 0: 运行时间 1
- 程序 1: 运行时间 2
- 程序 2: 运行时间 10
-
互斥关系: 程序 0 和程序 1 互斥。
-
寻找所有和谐程序组:
{0, 2}
(程序0和程序2不互斥){1, 2}
(程序1和程序2不互斥){0}
,{1}
,{2}
(单个程序的组总是和谐的)- ... 其他如
{0, 1}
因互斥被排除。
-
应用规则 1 (数量最多) :
- 数量最多的和谐程序组包含 2 个程序。符合条件的组是
{0, 2}
和{1, 2}
。
- 数量最多的和谐程序组包含 2 个程序。符合条件的组是
-
应用规则 2 (运行时间之和最小) :
-
计算这两个候选组的运行时间之和:
- 组
{0, 2}
的和:appRunTimes[0] + appRunTimes[2] = 1 + 10 = 11
。 - 组
{1, 2}
的和:appRunTimes[1] + appRunTimes[2] = 2 + 10 = 12
。
- 组
-
11 < 12
,因此选择{0, 2}
。
-
-
最终结果 : 返回其运行时间之和
11
。
-
(注:原样例解释中 "程序1+程序3" 的说法可能存在歧义,此处以上述基于数组下标的解释为准,该解释能严谨地推导出样例结果。)
样例 2
-
输入:
appRunTimes = [1]
mutexs = []
-
输出 :
1
-
解释:
- 只有一个程序(程序 0),运行时间为 1。
- 没有互斥关系。
- 唯一且最大的和谐程序组就是
{0}
本身。 - 其运行时间之和为 1。
思考过程
-
问题的本质是什么?
-
我们需要从所有可能的"和谐程序组"(即内部没有互斥对的程序集合)中,找出一个"最佳"的组。
-
"最佳"的定义有两条,按优先级排序:
- 程序数量最多。
- 在数量最多的前提下,运行时间总和最小。
-
-
如何表示一个"程序组"?
- 既然总程序数
n
不超过 20,我们可以用一个整数的二进制位来表示一个程序组(子集)。一个int
类型有 32 位,足够了。 - 例如,如果我们有 5 个程序(编号 0 到 4),那么整数
13
的二进制是01101
。 - 我们可以规定,从右往左,第
i
位是 1 表示程序i
在这个组里,是 0 表示不在。 - 那么
01101
就代表了包含程序 {0, 2, 3} 的程序组。 - 这样,从
0
到2^n - 1
的所有整数就唯一地对应了所有可能的程序组。
- 既然总程序数
-
如何高效地判断 互斥 关系?
- 在检查一个程序组是否"和谐"时,我们需要快速判断任意两个程序是否互斥。
- 最好的方法是预处理
mutexs
数据,将其存入一个邻接矩阵 (一个二维布尔数组isMutex[n][n]
)。 isMutex[i][j] = true
表示程序i
和j
互斥。由于互斥是双向的,所以isMutex[j][i]
也应该是true
。- 这样,判断
i
和j
是否互斥就变成了 O(1) 的数组查询。
java
import java.util.Arrays;
class Solution {
/**
* 寻找最佳和谐程序组并返回其总运行时间。
*
* @param appRunTimes 一个数组,其索引 i 代表程序编号,值代表运行时间。
* @param mutexs 一个二维数组,每个元素 [p1, p2] 代表一对互斥的程序。
* @return 满足条件的最佳和谐程序组的运行时间之和。
*/
public int findBestGroupSum(int[] appRunTimes, int[][] mutexs) {
int n = appRunTimes.length; // 程序的总数
// 1. 构建互斥关系的邻接矩阵,方便 O(1) 查询
// isMutex[i][j] = true 表示程序 i 和 j 互斥
boolean[][] isMutex = new boolean[n][n];
for (int[] pair : mutexs) {
// 注意:题目给的程序编号可能是 1-based 或 0-based。
// 从样例看,[1,2,10] 和 [[1,2]],程序编号是 0,1,2。所以 mutexs 里的编号也是 0-based。
// 如果是 1-based,则需要减 1。这里我们按 0-based 处理。
int p1 = pair[0];
int p2 = pair[1];
isMutex[p1][p2] = true;
isMutex[p2][p1] = true; // 互斥关系是双向的
}
// 2. 初始化用于存储最佳结果的变量
int maxSize = 0; // 记录找到的最大的和谐程序组的大小
int minSumForMaxSize = 0; // 记录在 maxSize 下,最小的运行时间总和
// 3. 使用位掩码遍历所有 2^n 个可能的程序子集
// mask 的范围从 1 到 (1 << n) - 1。我们从 1 开始,因为空集不可能是答案。
// (1 << n) 等价于 2^n
for (int mask = 1; mask < (1 << n); mask++) {
// a. 检查当前子集 (由 mask 代表) 是否和谐
boolean isHarmonious = true;
// 遍历所有可能的程序对 (i, j)
for (int i = 0; i < n; i++) {
// 检查程序 i 是否在当前子集中
// (mask >> i) & 1 == 1 是一个检查第 i 位是否为 1 的标准技巧
if (((mask >> i) & 1) == 1) {
for (int j = i + 1; j < n; j++) {
// 检查程序 j 是否也在当前子集中
if (((mask >> j) & 1) == 1) {
// 如果 i 和 j 都在子集中,检查它们是否互斥
if (isMutex[i][j]) {
isHarmonious = false; // 发现互斥对,此子集不和谐
break; // 内层循环没必要继续了
}
}
}
}
if (!isHarmonious) {
break; // 外层循环也没必要继续了
}
}
// 如果当前子集不和谐,跳过,继续检查下一个 mask
if (!isHarmonious) {
continue;
}
// b. 如果子集是和谐的,计算它的大小和运行时间总和
int currentSize = Integer.bitCount(mask); // bitCount() 快速计算整数中 1 的个数
int currentSum = 0;
for (int i = 0; i < n; i++) {
if (((mask >> i) & 1) == 1) { // 如果程序 i 在子集中
currentSum += appRunTimes[i];
}
}
// c. 根据规则更新最佳结果
// 规则1:程序数量最多
if (currentSize > maxSize) {
maxSize = currentSize; // 找到了一个更大的和谐组
minSumForMaxSize = currentSum; // 直接更新,因为这是目前最大组的唯一总和
}
// 规则2:如果数量相同,则运行时间总和最小
else if (currentSize == maxSize) {
minSumForMaxSize = Math.min(minSumForMaxSize, currentSum); // 取更小的总和
}
}
// 4. 返回最终结果
return minSumForMaxSize;
}
}
class Main {
public static void main(String[] args) {
Solution solver = new Solution();
// 样例1
int[] appRunTimes1 = {1, 2, 10};
int[][] mutexs1 = {{1, 2}};
int result1 = solver.findBestGroupSum(appRunTimes1, mutexs1);
System.out.println("样例 1 输出: " + result1); // 预期: 11
// 解释:
// 和谐组: {0}, {1}, {2}, {0,2}
// 大小为1的组: {0}(1), {1}(2), {2}(10)
// 大小为2的组: {0,2}(11)
// 最大大小为2,其唯一总和是11
// 样例2
int[] appRunTimes2 = {1};
int[][] mutexs2 = {};
int result2 = solver.findBestGroupSum(appRunTimes2, mutexs2);
System.out.println("样例 2 输出: " + result2); // 预期: 1
// 解释: 和谐组: {0}。最大大小1,总和1
}
}