会议室调度算法集锦

920.会议室I

同LeetCode 252

  • 对会议安排的list按 开始时间从小到大排序
  • 遍历当前的list,每次取当前的会议与下一个会议,如果当前的会议的结束时间> 下一个会议的开始时间,说明冲突了,因为当前的会议还没结束,下一个会议却开始了,这样没办法去参加下一个会议
        public boolean canAttendMeetings(List<Interval> list) {
            list.sort(((o1, o2) -> o1.start - o2.start));
            for (int i = 0; i < list.size() - 1; i++) {
                Interval curr = list.get(i), next = list.get(i + 1);
                if (curr.end > next.start) return false;
            }
            return true;
        }

919.会议室 II

同LeetCode 253

  • 对会议安排的list按 开始时间从小到大排序
  • 准备一个优先队列pq, 装每次会议的结束时间end,按从小到大排序(默认也是从小到大)
  • 遍历list, 如果当前会议的开始时间比pq中的堆顶元素大,表名堆顶的这个会议已经过期了,需要被弹出
  • 加入当前处理到的会议的end结束时间
  • 返回pq的大小
        public int minMeetingRooms(List<Interval> list) {
            list.sort(((o1, o2) -> o1.start - o2.start));
            PriorityQueue<Integer> pq = new PriorityQueue<>(((o1, o2) -> o1 - o2));
            pq.offer(list.get(0).end);
            for (int i = 1; i < list.size(); i++) {
                if (list.get(i).start > pq.peek()) {
                    pq.poll();
                }
                pq.offer(list.get(i).end);
            }
            return pq.size();
        }

1897.会议室 3

  • 准备一个vis数组,对当前的会议的开始时间和结束时间进行预处理,开始时间 + 1 , 结束时间 -1,顺便在此遍历过程中记下maxn(最大的天数,所有会议结束的最大数)
  • 准备一个sum数组,如果当前时间点位空,置为0,时间点位1,置为1
  • sum数组的前缀和
  • 查找的区间为[l,r],求sum[l],sum[l+1]...sum[r-1]都是0的,也就是sum[l-1]==sum[r-1]的结果,这段时间是可以插入会议的
        public boolean[] meetingRoomIII(int[][] intervals, int rooms, int[][] ask) {
            int[] sum = new int[50005];//根据数据范围设定,否则的话则是下面的maxn
            int[] vis = new int[50005];
            int n = ask.length;
            int maxn = 0;//最大的天数,所有会议结束的最大数
            for (int i = 0; i < intervals.length; i++) {
                vis[intervals[i][0]]++;
                vis[intervals[i][1]]--;
                maxn = Math.max(maxn, intervals[i][1]);
            }
            //遍历ask更新 maxn
            for (int i = 0; i < n; i++) maxn = Math.max(maxn, ask[i][1]);
            int temp = 0;
            for (int i = 1; i <= maxn; i++) {
                temp += vis[i];
                if (temp < rooms) sum[i] = 0;
                else sum[i] = 1;
            }
            for (int i = 1; i <= maxn; i++) sum[i] += sum[i - 1];
            boolean[] ans = new boolean[n];
            for (int i = 0; i < n; i++) {
                int s = ask[i][0], e = ask[i][1];
                if (sum[s - 1] == sum[e - 1]) ans[i] = true;
                else ans[i] = false;
            }
            return ans;
        }

300.会议室4

预处理+ DP + 二分加速

        //整理成Pair 存开始时间,结束时间,该会议的价值
        class Pair {
            int start, end, value;

            public Pair(int start, int end, int value) {
                this.start = start;
                this.end = end;
                this.value = value;
            }
        }

        public int maxValue(int[][] meeting, int[] value) {
            List<Pair> list = new ArrayList<>();
            int n = meeting.length;
            //重新组装会议,否则meeting被排序后,与value数组的顺序对应不起来
            for (int i = 0; i < n; i++) list.add(new Pair(meeting[i][0], meeting[i][1], value[i]));
            list.sort(((o1, o2) -> o1.end - o2.end));//按会议的结束时间从小到大排序
            //dp[i]表示会议[0...i]之间 ,及之前的方案所能获取到的价值的最大值
            int[] dp = new int[n];
            for (int i = 0; i < n; i++) {
                Pair curr = list.get(i);
                if (i == 0) {//0时候表示第一个会议,单独处理,肯定能参加,价值即这个会议的价值
                    dp[i] = curr.value;
                } else {
                    int idx = binarySearch(list, i - 1, curr.start);
                    //如果会议对应的索引为-1 表示没有找到合适的会议 那当前的dp[i]是这个会议的价值与之前的[0...i-1]会议价值的最大值
                    if (idx == -1) dp[i] = Math.max(dp[i - 1], curr.value);
                    //如果找到了这样的会议,就回归到01背包问题,参加当前会议或者不参加当前会议,取最大值
                    //参加当前会议,之前所能取到的就是idx这个索引对应的价值 curr.value + dp[idx]
                    //不参加当前会议,就是前一个会议的总价值顺延到当前会议 即 dp[i-1]
                    else dp[i] = Math.max(curr.value + dp[idx], dp[i - 1]);
                }
            }
            return dp[n - 1];
        }


        /*
            找到list中结束时间比target(当前的会议的开始时间) 小的会议的编号,没有返回-1
         */
        private int binarySearch(List<Pair> list, int r, int target) {
            int l = 0;
            while (l < r) {
                int m = l + (r - l + 1) / 2;//取到右中位数 「左动取左,右动取右」
                if (list.get(m).end > target) r = m - 1;
                else if (list.get(m).end <= target) l = m;
            }
            System.out.printf("%d\n",l);
            //在最后需要拦一道,如[[10,40],[20,50],[30,45],[40,60]] 这个case,
            // l的默认值是0 但是如果大于target的时候,不符合,返回-1
            return list.get(l).end <= target ? l : -1;
        }

1353. 最多可以参加的会议数目

  • 1.对会议按开始时间从小到大排序
  • 2.准备一个优先队列,存储每次进来的会议的结束时间,排序按结束时间越早越靠前
  • 3.准备一个变量day 表示从第1天开始累加,队列里有会议就参加,ans++ 队列为空,continue掉
  • 4.因为每天只能参加一个会议,需要清掉那些过期会议(这些会议是没有来得及参加的)
        public int maxEvents(int[][] events) {
            Arrays.sort(events, ((o1, o2) -> o1[0] - o2[0]));
            PriorityQueue<Integer> pq = new PriorityQueue<>(((o1, o2) -> o1 - o2));
            int day = 0, i = 0, n = events.length, ans = 0;
            while (i < n || !pq.isEmpty()) {
                day++;
                while (!pq.isEmpty() && pq.peek() < day) pq.poll();
                while (i < n && events[i][0] == day) {
                    pq.offer(events[i][1]);
                    i++;
                }
                if (pq.isEmpty()) continue;//处理上面的case3 i>n 的时候,退出
                ans++;
                pq.poll();
            }
            return ans;
        }

1751. 最多可以参加的会议数目 II

方法1:暴力递归

TLE

  • 先将events按会议的开始时间从小到大排序,然后还是递归
  • 准备一个函数helper,返回处理到当前curr会议所能获得的最大价值
    • events : 会议的二元数组
    • curr: 当前遍历到的会议的下标,从第0个会议开始
    • n: 会议的数量
    • k : 当前还剩余的可以参加会议的次数或是机会-
 public int maxValue(int[][] events, int k) {
            int n = events.length;
            //因为递归是自顶向下,按会议的开始时间从小到大排序,区别于自底向上的dp 填表的方式
            Arrays.sort(events, (o1, o2) -> o1[0] - o2[0]);
            return helper(events, 0, n, k);
        }

        private int helper(int[][] events, int curr, int n, int k) {
            //退出递归的出口,当curr到末尾或者剩余参加会议的机会或是次数k用完了
            if (curr >= n || k <= 0) return 0;
            int i = 0;//找curr之后满足条件最靠近curr这个会议的 下个会议 可以二分来加速
            for (i = curr + 1; i < n; i++) {
                if (events[i][0] > events[curr][1]) break;
            }
            //参加当前的会议,下个会议是 i 此 i 会议开始的时间大于当前会议 curr的结束时间 此时k用掉了一次,-1
            //不参加当前会议,跳到下个会议  此时k没有使用 保持不变
            int ans = Math.max(helper(events, i, n, k - 1) + events[curr][2], helper(events, curr + 1, n, k));
            return ans;
        }
方法2:自顶向下记忆化递归(Top-down)
  • 准备一个memo[curr][k] : 表示处理到curr这个会议,还剩余k次参加会议机会所能获取的价值的最大值
Integer[][] memo;

public int maxValue(int[][] events, int k) {
    int n = events.length;
   //因为递归是自顶向下,按会议的开始时间从小到大排序,区别于自底向上的dp 填表的方式
    Arrays.sort(events, (o1, o2) -> o1[0] - o2[0]);
    memo = new Integer[n + 1][k + 1];
    return helper(events, 0, n, k);
}

private int helper(int[][] events, int curr, int n, int k) {
  //退出递归的出口,当curr到末尾或者剩余参加会议的机会或是次数k用完了
    if (curr >= n || k <= 0) return 0;
  //如果已经被计算过,直接返回
    if (memo[curr][k] != null) return memo[curr][k];
    int i = 0;//找curr之后满足条件最靠近curr这个会议的 下个会议 可以二分来加速
    for (i = curr + 1; i < n; i++) {
        if (events[i][0] > events[curr][1]) break;
    }
    //参加当前的会议,下个会议是 i 此 i 会议开始的时间大于当前会议 curr的结束时间 此时k用掉了一次,-1
    //不参加当前会议,跳到下个会议  此时k没有使用 保持不变
    int ans = Math.max(helper(events, i, n, k - 1) + events[curr][2], helper(events, curr + 1, n, k));
    System.out.printf("%d\n", ans);
    return memo[curr][k] = ans;
}
方法3:自底向上填表DP(Bottom-up)
  • dp[i][j]表示在前i个会议中,最多只能参加j个会议,所能获取到的最大价值
  • 当不参加第i个会议的时候,当前值等于参加前面i-1个会议所能获取到的最大价值,能参加j个会议
  • 当参加第i个会议的时候,需要找最靠近i的且其结束时间比i的开始时间小的那个会议,记为idx,参加这个前idx个会议,并有j-1(因为已经决定参加第i个会议,用掉了一次参加会议的机会),所能带来的最大价值 + 第i个会议所能带来的最大价值

d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] 不参加第i个会议的时候 e v e n t s [ i ] [ 2 ] + d p [ i d x ] [ j − 1 ] 参加第i个会议,idx是第i个会议之前,最靠近i的且其结束时间比i的开始时间小 dp[i][j]= max\begin{cases} dp[i-1][j]& \text{不参加第i个会议的时候}\\ events[i][2]+dp[idx][j-1]& \text{参加第i个会议,idx是第i个会议之前,最靠近i的且其结束时间比i的开始时间小} \end{cases} dp[i][j]=max{dp[i1][j]events[i][2]+dp[idx][j1]不参加第i个会议的时候参加第i个会议,idx是第i个会议之前,最靠近i的且其结束时间比i的开始时间小

        class Pair {
            //开始时间,结束时间,会议价值
            int s, e, v;

            public Pair(int s, int e, int v) {
                this.s = s;
                this.e = e;
                this.v = v;
            }
        }


        public int maxValue(int[][] events, int K) {
            Arrays.sort(events, (o1, o2) -> o1[1] - o2[1]);
            int N = events.length;
            //预处理组装
            Pair[] p = new Pair[N + 1];
            for (int i = 1; i <= N; i++) p[i] = new Pair(events[i - 1][0], events[i - 1][1], events[i - 1][2]);
            //`dp[i][j]`表示在前`i`个会议中,最多只能参加`j`个会议,所能获取到的最大价值
            int[][] dp = new int[N + 1][K + 1];
            for (int i = 1; i <= N; i++) {
                for (int j = 1; j <= K; j++) {
                    //不参加i这个会议
                    dp[i][j] = dp[i - 1][j];
                    //二分找最靠近i的且其结束时间比i的开始时间小的那个会议,返回l这个会议下标
                    int l = 0, r = i - 1;
                    while (l < r) {
                        int m = l + (r - l + 1) / 2;//取右中位数
                        if (p[m].e < p[i].s) l = m;
                        else r = m - 1;//右动
                    }
                    //参加i这个会议
                    dp[i][j] = Math.max(dp[i][j], p[i].v + dp[l][j - 1]);
                }
            }
//            PrintUtils.printMatrix(dp);
            return dp[N][K];
        }

不组装静态类写法

      public int maxValue(int[][] events, int K) {
            int N = events.length;
            Arrays.sort(events, (o1, o2) -> o1[1] - o2[1]);
            // //`dp[i][j]`表示在前`i`个会议中,最多只能参加`j`个会议,所能获取到的最大价值
            int[][] dp = new int[N + 1][K + 1];
            for (int i = 1; i <= N; i++) {
                int[] curr = events[i - 1];
                int s = curr[0], e = curr[1], v = curr[2];
                int l = 1, r = i - 1;
                while (l < r) {
                    int m = l + (r - l + 1) / 2;
                    //dp的下标于events错开了一位,这里m-1
                    if (events[m - 1][1] < s) l = m;
                    else r = m - 1;
                }
//                System.out.printf("l:%d  r:%d\n", l, r);
                //最后需要拦一道,确保得到的 会议的结束时间是小于 当前会议的开始时间 ,拿到这个会议的下标 p
                int p = events[l - 1][1] < s ? r : 0;
//                System.out.println(p);
                //遍历前这 K次参加会议的机会,参加当前i 会议 与不参加当前i会议
                for (int j = 1; j <= K; j++) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[p][j - 1] + v);
                }
            }
//            PrintUtils.printMatrix(dp);
            return dp[N][K];
        }
435

TODO

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值