每日一题——主持人调度(二)

主持人调度(二)

好的,以下是一个整理好的关于"主持人调度问题"的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;  // 返回所需的最少主持人数量
}

代码逻辑详解

  1. 活动结构体

    • 定义了一个 Activity 结构体,包含两个字段:startend,分别表示活动的开始时间和结束时间。
  2. 比较函数

    • compare 函数用于 qsort 排序。它首先按活动的开始时间排序,如果开始时间相同,则按结束时间排序。
    • 这种排序方式确保了活动按时间顺序排列,便于后续的贪心算法实现。
  3. 活动数组初始化

    • 遍历输入的二维数组 startEnd,将每个活动的开始时间和结束时间存入 activities 数组。
  4. 排序

    • 使用 qsort 对活动数组按排序规则进行排序。
  5. 分配主持人

    • 创建一个数组 hosts 用于记录每个主持人的空闲时间。
    • 遍历排序后的活动数组,尝试为每个活动分配主持人:
      • 如果当前活动的开始时间大于等于某个主持人的空闲时间,则可以复用该主持人,并更新该主持人的空闲时间为当前活动的结束时间。
      • 如果无法复用任何已分配的主持人,则新增一名主持人。
  6. 返回结果

    • 最终返回所需的最少主持人数量。

贪心算法的关键点

  • 排序:通过排序确保活动按时间顺序排列,便于贪心选择。
  • 复用主持人:尽量复用已有的主持人,避免新增主持人。
  • 空闲时间更新:每次分配活动后,更新主持人的空闲时间。

复杂度分析

  • 时间复杂度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);
  • StartEnd数组:分别存储所有活动的开始时间和结束时间。
  • qsort函数 :对StartEnd数组分别进行排序。排序后,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是可复用的活动数量
}

算法逻辑总结

  1. 分离和排序

    • 将所有活动的开始时间和结束时间分别存储到两个数组StartEnd中。
    • StartEnd数组分别进行排序。
  2. 双指针遍历

    • 使用i指针遍历所有活动的开始时间。
    • 使用j指针遍历所有活动的结束时间。
    • 如果Start[i] >= End[j],说明当前活动可以复用某个已结束的活动的主持人,j指针向前移动。
    • 如果Start[i] < End[j],说明当前活动需要新增一名主持人。
  3. 计算结果

    • 最终返回i - j,即需要的主持人数量。

复杂度分析

  • 时间复杂度O(n log n),主要由两次qsort排序操作决定。
  • 空间复杂度O(n),用于存储StartEnd数组。

示例验证

详细分析例子5, [[1,3],[2,4],[2,8],[3,4],[4,10]],并逐步解释代码是如何处理的。

输入数据
  • 活动数量:n = 5

  • 活动的开始时间和结束时间:

    [[1,3], [2,4], [2,8], [3,4], [4,10]]
    
代码执行过程
1. 提取并排序开始时间和结束时间

代码首先将所有活动的开始时间和结束时间分别提取到两个数组 StartEnd 中,然后对这两个数组分别进行排序。

提取后的数组

  • 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. 双指针遍历

接下来,代码使用双指针 ij 来判断哪些活动可以复用已有的主持人。

c 复制代码
int i, j;
for (i = 1, j = 0; i < n; i++) {
    if (Start[i] >= End[j]) {
        j++;  // 如果当前活动的开始时间大于等于某个活动的结束时间,说明可以复用主持人
    }
}

双指针遍历过程

  1. 初始状态

    • i = 1j = 0
    • Start[i] = 2End[j] = 3
    • 2 < 3,说明活动 [2,4] 无法复用任何已结束的活动的主持人。
  2. 第二轮

    • i = 2j = 0
    • Start[i] = 2End[j] = 3
    • 2 < 3,说明活动 [2,8] 也无法复用任何已结束的活动的主持人。
  3. 第三轮

    • i = 3j = 0
    • Start[i] = 3End[j] = 3
    • 3 >= 3,说明活动 [3,4] 可以复用活动 [1,3] 的主持人,因此 j++
  4. 第四轮

    • i = 4j = 1
    • Start[i] = 4End[j] = 4
    • 4 >= 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. 活动 [1,3] 需要一个主持人。
  2. 活动 [2,4][2,8][1,3] 有时间冲突,因此需要两个新的主持人。
  3. 活动 [3,4] 可以复用 [1,3] 的主持人。
  4. 活动 [4,10] 可以复用 [2,4] 的主持人。

因此,总共需要 3名主持人,与代码的输出一致。

相关推荐
南宫生12 分钟前
力扣每日一题【算法学习day.130】
java·学习·算法·leetcode
柠石榴13 分钟前
【练习】【类似于子集问题】力扣491. 非递减子序列/递增子序列
c++·算法·leetcode·回溯
干饭高手17 分钟前
Day9,Hot100(图论)
python·leetcode·图论
!!!52521 分钟前
Java实现斗地主-做牌以及对牌排序
java·算法
sjsjs1127 分钟前
【数据结构-并查集】力扣1722. 执行交换操作后的最小汉明距离
数据结构·算法·leetcode
Ronin-Lotus30 分钟前
程序代码篇---C/C++中的变量存储位置
c语言·c++···静态区·文字常量区·变量存储位置
CoderIsArt39 分钟前
生成一个立方体贴图(Cube Map)
算法·sharpgl
且听风吟ayan1 小时前
leetcode day20 滑动窗口209+904
算法·leetcode·c#
m0_675988231 小时前
Leetcode350:两个数组的交集 II
算法·leetcode·数组·哈希表·python3
_Itachi__1 小时前
LeetCode 热题 100 160. 相交链表
算法·leetcode·链表