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

主持人调度(二)

好的,以下是一个整理好的关于"主持人调度问题"的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名主持人,与代码的输出一致。

相关推荐
励志要当大牛的小白菜28 分钟前
ART配对软件使用
开发语言·c++·qt·算法
qq_5139704431 分钟前
力扣 hot100 Day56
算法·leetcode
PAK向日葵1 小时前
【算法导论】如何攻克一道Hard难度的LeetCode题?以「寻找两个正序数组的中位数」为例
c++·算法·面试
爱装代码的小瓶子3 小时前
数据结构之队列(C语言)
c语言·开发语言·数据结构
爱喝矿泉水的猛男4 小时前
非定长滑动窗口(持续更新)
算法·leetcode·职场和发展
YuTaoShao4 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
YouQian7724 小时前
Traffic Lights set的使用
算法
快乐飒男4 小时前
哈希表(c语言)
c语言·哈希算法·散列表
go54631584656 小时前
基于深度学习的食管癌右喉返神经旁淋巴结预测系统研究
图像处理·人工智能·深度学习·神经网络·算法
aramae6 小时前
大话数据结构之<队列>
c语言·开发语言·数据结构·算法