[leetcode 双周赛 11] 1229 安排会议日程

1229 Meeting Scheduler 安排会议日程

问题描述

你是一名行政助理, 手里有两位客户的空闲时间表: slots1slots2, 以及会议的预计持续时间 duration, 请你为他们安排合适的会议时间.

「会议时间」是两位客户都有空参加, 并且持续时间能够满足预计时间 duration最早的时间间隔.

如果没有满足要求的会议时间, 就请返回一个 空数组.

「空闲时间」的格式是 [start, end], 由开始时间 start 和结束时间 end 组成, 表示从 start 开始, 到 end 结束.

题目保证数据有效: 同一个人的空闲时间不会出现交叠的情况, 也就是说, 对于同一个人的两个空闲时间 [start1, end1][start2, end2], 要么 start1 > end2, 要么 start2 > end1.

**示例 1: **

**输入: ** slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 8
**输出: ** [60,68]

**示例 2: **

**输入: **slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 12
**输出: **[]

**提示: **

  • 1 <= slots1.length, slots2.length <= 10^4
  • slots1[i].length, slots2[i].length == 2
  • slots1[i][0] < slots1[i][1]
  • slots2[i][0] < slots2[i][1]
  • 0 <= slots1[i][j], slots2[i][j] <= 10^9
  • 1 <= duration <= 10^6

思路

  • 读题
  1. 同一客户的时间表不会交叉
  2. 会议时间在两用户空闲时间段交叉处, 即min(end1, end2) - max(start1, start2) > 0
  3. 如果当前比较的两个时间段不符合, 则保留end更大的时间段, 让对方下移

双指针遍历(排序)

使用双指针分别指向两个用户的空闲时间表(排序), 两两比较min(end1, end2) - max(start1, start2) >= duration

如果当前比较的两个时间段不符合, 则保留end更大的时间段, 让对方下移

交叉时间段的规律

规律: 将所有时间段的起点和终点放置在x轴上, 并予以分别标注(slot1的起点为0, 终点为2/slot2的起点为1, 终点为4), 遍历这条轴, 如果连续遇到两个不同用户的起点, 则表示必定发生了交叉

代码实现

双指针遍历

class Solution {
    public List<Integer> minAvailableDuration(int[][] slots1, int[][] slots2, int duration) {
        // 对两个时间表 进行顺序排序
        Arrays.sort(slots1, Comparator.comparingInt(s -> s[0]));
        Arrays.sort(slots2, Comparator.comparingInt(s -> s[0]));

        // c1 指针, 指向当前遍历slot1的位置
        // c2 指针, 指向当前遍历slot1的位置
        // l 当前遍历的两时间段 起点的最大值
        // r 当前遍历的两时间段 终点的最小值
        int c1 = 0, c2 = 0, len1 = slots1.length, len2 = slots2.length, l, r;
        // s1 slot1当前遍历时间段
        // s2 slot2当前遍历时间段
        int[] s1, s2;

        List<Integer> ans = new ArrayList<>();
        while (c1 < len1 && c2 < len2) {
            s1 = slots1[c1];
            s2 = slots2[c2];
            l = Math.max(s1[0], s2[0]);
            r = Math.min(s1[1], s2[1]);
            // 两个时间段的交集符合条件 (r-l)>=duration
            System.out.printf("[r:%d, l:%d]:(%d) <-- s1:[%d, %d], s2:[%d, %d]
",
                    r, l, (r - l), s1[0], s1[1], s2[0], s2[1]);
            if (r - l >= duration) {
                ans.add(l);
                ans.add(l + duration);
                break;
            } else if (s1[1] == r) {
                // 此时 slot1的终点比slot2的小 slot2更有机会 有slot1其后的时间段交叉
                // slot1需要移动到下一个时间段
                c1++;
            } else {
                c2++;
            }
        }

        return ans;
    }
}
  • 可以不使用Arrays提供的工具方法, 手写一个伴随1维数组排序的2维数组
/**
 * 使用堆排进行二维数组排序
 *
 * @param arrs 被排序二维数组
 */
public static void sort2DArray(int[][] arrs) {
    if (Objects.isNull(arrs)) {
        System.out.println("arrs is not a array");
        return;
    }

    int len = arrs.length, d = arrs[0].length, needD = 2;
    if (d != needD) {
        System.out.printf("arrs is not a 2d array [d:%d]
", d);
        return;
    }

    int end = len - 1, root = 0, d1 = 0, d2 = 1;
    while (end >= root) {
        for (int child = end; child > root; child--) {
            int parent = (child - 1) >>> 1;
            if (arrs[child][d1] > arrs[parent][d1]) {
                swap(child, parent, arrs, d1);
                swap(child, parent, arrs, d2);
            } else if (arrs[child][d1] == arrs[parent][d1] && arrs[child][d2] > arrs[parent][d2]) {
                swap(child, parent, arrs, d2);
            }
        }
        swap(end, root, arrs, d1);
        swap(end, root, arrs, d2);
        end--;
    }
}

private static void swap(int c, int p, int[][] arrs, int d) {
    int temp = arrs[c][d];
    arrs[c][d] = arrs[p][d];
    arrs[p][d] = temp;
}

交叉时间段的规律

  • 使用自定义Pair类 作为排序的基本单位 使得终点伴随起点排序
class Solution {
    /**
     * 一种统一的标示
     */
    private static int s1Start = 0, s2Start = 1, s1End = 2, s2End = 3;

    /**
     * pair<时间点, 标识>对
     * 可以排序时间点的同时, 以此改变标识的位置
     */
    class Pair {
        /**
         * 存储的时间点
         */
        int element;
        /**
         * 时间间断开始 0 1
         * 时间间断结束 2 3
         */
        int flag;

        Pair(int element, int flag) {
            this.element = element;
            this.flag = flag;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Pair pair = (Pair) o;
            return element == pair.element &&
                    flag == pair.flag;
        }

        @Override
        public int hashCode() {
            return Objects.hash(element, flag);
        }

        @Override
        public String toString() {
            return "Pair{" +
                    "element=" + element +
                    ", flag=" + flag +
                    '}';
        }
    }

    public List<Integer> minAvailableDuration(int[][] slots1, int[][] slots2, int duration) {
        // 存储pair对 注意Comparator不能只写element 添加数据时 会以此作为元素是否重复的判断依据!
        // TreeSet 带排序的Set容器
        Set<Pair> pairs = new TreeSet<>((o1, o2) -> {
            // 在时间点相同的情况下 根据 起点>终点 的顺序排序
            if (o1.element == o2.element) {
                return o1.flag - o2.flag;
            }
            return o1.element - o2.element;
        });

        // 初始化
        for (int[] s1 : slots1) {
            pairs.add(new Pair(s1[0], s1Start));
            pairs.add(new Pair(s1[1], s1End));
        }
        for (int[] s2 : slots2) {
            pairs.add(new Pair(s2[0], s2Start));
            pairs.add(new Pair(s2[1], s2End));
        }

        // ans 最终返回结果
        List<Integer> ans = new ArrayList<>();
        // s1 遍历到slot1最近的起点 即当前遍历位置的最近slot1起点
        // s2 遍历到slot2最近的起点
        // cnt 以及遍历到的起点 为2时 如果接下来是终点 则表示必定有交集!
        int s1 = 0, s2 = 0, cnt = 0;
        for (Pair pair : pairs) {
            System.out.printf("%s [s1:%d s2:%d] cnt:%d
", pair, s1, s2, cnt);
            if (pair.flag == s1Start) {
                s1 = pair.element;
                cnt++;
            } else if (pair.flag == s2Start) {
                s2 = pair.element;
                cnt++;
            } else if (pair.flag == s1End || pair.flag == s2End) {
                // 连续两个起点, 必定有一交集
                if (cnt == 2) {
                    // near 离当前遍历终点最近的起点(越大越近)
                    int near = Math.max(s1, s2), e = pair.element;
                    System.out.printf("%s [s1:%d s2:%d] near:%d
", pair, s1, s2, near);
                    if (near + duration <= e) {
                        ans.add(near);
                        ans.add(near + duration);
                        break;
                    }
                }
                // 这个终点不符合 相当于有一起点无用 还需要继续找下一交集
                cnt--;
            }
        }

        return ans;
    }
}

参考资源

c++ 扫描法

原文地址:https://www.cnblogs.com/slowbirdoflsh/p/11736490.html