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[i−1][j]events[i][2]+dp[idx][j−1]不参加第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