主持人调度(二)

好的,以下是一个整理好的关于"主持人调度问题"的CSDN博客文档内容。你可以根据需要进一步调整格式或补充细节。
问题描述
有 n 个活动即将举办,每个活动都有开始时间与结束时间。第 i 个活动的开始时间是 start_i,结束时间是 end_i。为了成功举办每个活动,需要为每个活动分配一名主持人。一位主持人在同一时间只能参与一个活动,并且需要全程参与活动。换句话说,如果一个主持人参与了第 i 个活动,那么他在 (start_i, end_i) 这个时间段内不能参与其他任何活动。目标是求出为了成功举办所有活动,最少需要多少名主持人。
输入格式
n:活动的数量,范围为1 ≤ n ≤ 10^5。startEnd:一个二维数组,startEnd[i][0]表示第i个活动的开始时间,startEnd[i][1]表示第i个活动的结束时间。时间范围为-2^32 ≤ start_i ≤ end_i ≤ 2^31 - 1。
输出格式
- 返回一个整数,表示最少需要的主持人数量。
示例
示例 1:
输入:
c
2, [[1,2], [2,3]]
输出:
c
1
说明:只需要一个主持人就能成功举办这两个活动。
示例 2:
输入:
c
2, [[1,3], [2,4]]
输出:
c
2
说明:需要两个主持人才能成功举办这两个活动。
第一种直观解法
最开始想的传统思维,每一个活动开始前,看看前面的主持人有没有空闲的,有空闲的,就复用,没有空闲的,就再雇佣一个。
c
#include <stdio.h>
#include <stdlib.h>
// 定义活动结构体,每个活动包含开始时间和结束时间
typedef struct {
int start; // 活动的开始时间
int end; // 活动的结束时间
} Activity;
// 比较函数,用于 qsort 排序
int compare(const void* a, const void* b) {
Activity* activity1 = (Activity*)a; // 将 void* 类型转换为 Activity* 类型
Activity* activity2 = (Activity*)b; // 将 void* 类型转换为 Activity* 类型
// 首先按活动的开始时间排序
if (activity1->start < activity2->start) return -1; // 如果活动1的开始时间更早,返回 -1注意可能会越界,所以用比较,而不是相减
if (activity1->start > activity2->start) return 1; // 如果活动1的开始时间更晚,返回 1
// 如果开始时间相同,则按结束时间排序
if (activity1->end < activity2->end) return -1; // 如果活动1的结束时间更早,返回 -1
if (activity1->end > activity2->end) return 1; // 如果活动1的结束时间更晚,返回 1
return 0; // 如果两个活动的开始时间和结束时间都相同,返回 0
}
// 计算最少需要的主持人数量
int minmumNumberOfHost(int n, int** startEnd, int startEndRowLen, int* startEndColLen) {
// 创建一个数组来存储所有活动的开始时间和结束时间
Activity activities[n];
for (int i = 0; i < n; i++) {
activities[i].start = startEnd[i][0]; // 获取第 i 个活动的开始时间
activities[i].end = startEnd[i][1]; // 获取第 i 个活动的结束时间
}
// 使用 qsort 对活动数组进行排序,排序规则由 compare 函数定义
qsort(activities, n, sizeof(Activity), compare);
// 创建一个数组来记录每个主持人的空闲时间
int hosts[n]; // 假设最多可能需要 n 个主持人
int hostCount = 0; // 当前已分配的主持人数量
// 遍历所有活动,尝试为每个活动分配主持人
for (int i = 0; i < n; i++) {
int assigned = 0; // 标记当前活动是否已经分配了主持人
// 尝试复用已分配的主持人
for (int j = 0; j < hostCount; j++) {
// 如果当前活动的开始时间大于等于某个主持人的空闲时间,则可以复用该主持人
if (activities[i].start >= hosts[j]) {
hosts[j] = activities[i].end; // 更新该主持人的空闲时间为当前活动的结束时间
assigned = 1; // 标记当前活动已分配主持人
break; // 跳出循环
}
}
// 如果当前活动无法复用任何已分配的主持人,则新增一名主持人
if (!assigned) {
hosts[hostCount] = activities[i].end; // 新增主持人的空闲时间为当前活动的结束时间
hostCount++; // 主持人数量加 1
}
}
return hostCount; // 返回所需的最少主持人数量
}
代码逻辑详解
-
活动结构体:
- 定义了一个
Activity结构体,包含两个字段:start和end,分别表示活动的开始时间和结束时间。
- 定义了一个
-
比较函数:
compare函数用于qsort排序。它首先按活动的开始时间排序,如果开始时间相同,则按结束时间排序。- 这种排序方式确保了活动按时间顺序排列,便于后续的贪心算法实现。
-
活动数组初始化:
- 遍历输入的二维数组
startEnd,将每个活动的开始时间和结束时间存入activities数组。
- 遍历输入的二维数组
-
排序:
- 使用
qsort对活动数组按排序规则进行排序。
- 使用
-
分配主持人:
- 创建一个数组
hosts用于记录每个主持人的空闲时间。 - 遍历排序后的活动数组,尝试为每个活动分配主持人:
- 如果当前活动的开始时间大于等于某个主持人的空闲时间,则可以复用该主持人,并更新该主持人的空闲时间为当前活动的结束时间。
- 如果无法复用任何已分配的主持人,则新增一名主持人。
- 创建一个数组
-
返回结果:
- 最终返回所需的最少主持人数量。
贪心算法的关键点
- 排序:通过排序确保活动按时间顺序排列,便于贪心选择。
- 复用主持人:尽量复用已有的主持人,避免新增主持人。
- 空闲时间更新:每次分配活动后,更新主持人的空闲时间。
复杂度分析
- 时间复杂度 :
O(n log n),主要由排序操作决定。 - 空间复杂度 :
O(n),用于存储活动数组和主持人数组。
第二种思路
代码解析
c
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b; // 比较函数,用于qsort排序,从小到大排序
}
cmp函数 :这是qsort函数的比较函数,用于对数组进行排序。它将两个指针指向的整数进行比较,返回它们的差值。如果差值为负数,则表示第一个数小于第二个数;如果差值为正数,则表示第一个数大于第二个数。这里实现的是从小到大排序。
c
int minmumNumberOfHost(int n, int** startEnd, int startEndRowLen, int* startEndColLen) {
// 分配内存用于存储所有活动的开始时间和结束时间
int* Start = (int*)malloc(sizeof(int) * n);
int* End = (int*)malloc(sizeof(int) * n);
// 将输入的活动开始时间和结束时间分别存储到Start和End数组中
for (int i = 0; i < n; i++) {
Start[i] = startEnd[i][0]; // 第i个活动的开始时间
End[i] = startEnd[i][1]; // 第i个活动的结束时间
}
// 对开始时间和结束时间分别进行排序
qsort(Start, n, sizeof(int), cmp);
qsort(End, n, sizeof(int), cmp);
Start和End数组:分别存储所有活动的开始时间和结束时间。qsort函数 :对Start和End数组分别进行排序。排序后,Start数组中的元素按从小到大的顺序表示所有活动的开始时间,End数组中的元素按从小到大的顺序表示所有活动的结束时间。
c
int i, j;
for (i = 1, j = 0; i < n; i++) {
if (Start[i] >= End[j]) {
j++; // 如果当前活动的开始时间大于等于某个活动的结束时间,说明可以复用主持人
}
}
- 双指针逻辑 :
i指针遍历所有活动的开始时间(Start数组)。j指针遍历所有活动的结束时间(End数组)。- 对于每个活动的开始时间
Start[i],检查是否存在一个活动的结束时间End[j],使得Start[i] >= End[j]。如果满足条件,说明当前活动可以复用已经结束的活动的主持人,因此j指针向前移动。 - 如果
Start[i] < End[j],说明当前活动无法复用任何已结束的活动的主持人,需要新增一名主持人。
可以理解为每次活动开始的时候,问下前面的活动有没有结束的,赶紧来个主持人,由于end已经排序好了,所以不用担心,只要比较第j个(初始为0)就好了,第一个不行,之后更加完蛋,然后第一个活动主持人如果被叫过去,j++;
c
return i - j; // 返回需要的主持人数量
}
- 返回值 :
i - j表示需要的主持人数量。i是总活动数量,j表示可以复用的活动数量。因此,i - j就是需要新增的主持人数量。
完整代码与注释
c
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b; // 比较函数,用于qsort排序,从小到大排序
}
int minmumNumberOfHost(int n, int** startEnd, int startEndRowLen, int* startEndColLen) {
// 分配内存用于存储所有活动的开始时间和结束时间
int* Start = (int*)malloc(sizeof(int) * n);
int* End = (int*)malloc(sizeof(int) * n);
// 将输入的活动开始时间和结束时间分别存储到Start和End数组中
for (int i = 0; i < n; i++) {
Start[i] = startEnd[i][0]; // 第i个活动的开始时间
End[i] = startEnd[i][1]; // 第i个活动的结束时间
}
// 对开始时间和结束时间分别进行排序
qsort(Start, n, sizeof(int), cmp);
qsort(End, n, sizeof(int), cmp);
// 双指针逻辑:i遍历开始时间,j遍历结束时间
int i, j;
for (i = 1, j = 0; i < n; i++) {
if (Start[i] >= End[j]) {
j++; // 如果当前活动的开始时间大于等于某个活动的结束时间,说明可以复用主持人
}
}
// 返回需要的主持人数量
return i - j; // i是总活动数量,j是可复用的活动数量
}
算法逻辑总结
-
分离和排序:
- 将所有活动的开始时间和结束时间分别存储到两个数组
Start和End中。 - 对
Start和End数组分别进行排序。
- 将所有活动的开始时间和结束时间分别存储到两个数组
-
双指针遍历:
- 使用
i指针遍历所有活动的开始时间。 - 使用
j指针遍历所有活动的结束时间。 - 如果
Start[i] >= End[j],说明当前活动可以复用某个已结束的活动的主持人,j指针向前移动。 - 如果
Start[i] < End[j],说明当前活动需要新增一名主持人。
- 使用
-
计算结果:
- 最终返回
i - j,即需要的主持人数量。
- 最终返回
复杂度分析
- 时间复杂度 :
O(n log n),主要由两次qsort排序操作决定。 - 空间复杂度 :
O(n),用于存储Start和End数组。
示例验证
详细分析例子5, [[1,3],[2,4],[2,8],[3,4],[4,10]],并逐步解释代码是如何处理的。
输入数据
-
活动数量:
n = 5 -
活动的开始时间和结束时间:
[[1,3], [2,4], [2,8], [3,4], [4,10]]
代码执行过程
1. 提取并排序开始时间和结束时间
代码首先将所有活动的开始时间和结束时间分别提取到两个数组 Start 和 End 中,然后对这两个数组分别进行排序。
提取后的数组:
Start:[1, 2, 2, 3, 4]End:[3, 4, 4, 8, 10]
排序后的数组:
Start:[1, 2, 2, 3, 4](排序后不变)End:[3, 4, 4, 8, 10](排序后)
2. 双指针遍历
接下来,代码使用双指针 i 和 j 来判断哪些活动可以复用已有的主持人。
c
int i, j;
for (i = 1, j = 0; i < n; i++) {
if (Start[i] >= End[j]) {
j++; // 如果当前活动的开始时间大于等于某个活动的结束时间,说明可以复用主持人
}
}
双指针遍历过程:
-
初始状态:
i = 1,j = 0Start[i] = 2,End[j] = 32 < 3,说明活动[2,4]无法复用任何已结束的活动的主持人。
-
第二轮:
i = 2,j = 0Start[i] = 2,End[j] = 32 < 3,说明活动[2,8]也无法复用任何已结束的活动的主持人。
-
第三轮:
i = 3,j = 0Start[i] = 3,End[j] = 33 >= 3,说明活动[3,4]可以复用活动[1,3]的主持人,因此j++。
-
第四轮:
i = 4,j = 1Start[i] = 4,End[j] = 44 >= 4,说明活动[4,10]可以复用活动[2,4]的主持人,因此j++。
最终状态:
i = 5(遍历完所有活动)j = 2(有两个活动可以复用已有的主持人)
3. 计算结果
最后,代码返回所需的主持人数量:
c
return i - j; // i 是总活动数量,j 是可以复用的活动数量
计算结果:
i = 5(总活动数量)j = 2(可以复用的活动数量)- 所需主持人数量 =
5 - 2 = 3
结论
对于输入 5, [[1,3],[2,4],[2,8],[3,4],[4,10]],代码的输出是 3,表示需要 3名主持人 来成功举办所有活动。
验证
我们可以通过手动分析来验证这个结果:
- 活动
[1,3]需要一个主持人。 - 活动
[2,4]和[2,8]与[1,3]有时间冲突,因此需要两个新的主持人。 - 活动
[3,4]可以复用[1,3]的主持人。 - 活动
[4,10]可以复用[2,4]的主持人。
因此,总共需要 3名主持人,与代码的输出一致。