概念 & 应用
概念:不需要检测每一时刻,只需要检测起点或者终点的位置!(交点变化的位置只有起点 或者 终点)
应用:
- 区间问题
区间问题
区间问题肯定按照区间的起点或者终点进行排序!!!
「区间问题」template:“上下车”
- 解法一:将
(start, +val)和(end, -val)sort后进行扫描线- 解法二:priority queue(heap)
391. 数飞机(Medium)
Solu:扫描线上 - 下车问题
Code:
class Solution:
def countOfAirplanes(self, airplanes):
arr = []
for interval in airplanes:
arr.append((interval.start, 1))
arr.append((interval.end, -1))
arr.sort() # 时间相同时,降落优先于起飞
count, ans = 0, 0
for i in range(len(arr)):
count += arr[i][1]
ans = max(ans, count)
return ans
252. 会议室(Easy)
Solu 1:扫描线+上下车
同上,略
Code 1:
class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
arr = []
for start, end in intervals:
arr.append((start, 1))
arr.append((end, -1))
arr.sort()
count = 0
for _, v in arr:
count += v
if count > 1:
return False
return True
Solu 2:
看是否存在meeting2's start < meeting1's end(严格小于)
Code 2:
class Solution:
def canAttendMeetings(self, intervals: List[List[int]]) -> bool:
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i - 1][1]:
return False
return True
253. 会议室 II(Medium)
Solu 1:扫描线 + 上下车问题
max{#同一时刻需要的会议室} = min{#能够满足整个会议安排的会议室}
Code 1:
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
arr = []
for start, end in intervals:
arr.append((start, 1))
arr.append((end, -1))
arr.sort()
rooms = 0
ans = 0
for _, v in arr:
rooms += v
ans = max(rooms, ans)
return ans
Solu 2:双指针
如果存在overlap(start[i] < end[j]) -> 需要新开一个会议室
Code 2:双指针 + 上下车
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
start_arr, end_arr = [], []
for start, end in intervals:
start_arr.append(start)
end_arr.append(end)
start_arr.sort()
end_arr.sort()
start, end, room, ans = 0, 0, 0, 0
while start < len(start_arr) and end < len(end_arr):
if start_arr[start] < end_arr[end]: # overlap -> use a new meeting room
room += 1
start += 1
else:
room -= 1
end += 1
ans = max(ans, room)
return ans
或者
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
start_arr, end_arr = [], []
for start, end in intervals:
start_arr.append(start)
end_arr.append(end)
start_arr.sort()
end_arr.sort()
end, room = 0, 0
for start in start_arr:
if start < end_arr[end]:
room += 1
else: # no overlap -> reuse this room
end += 1
return room
56. 合并区间(Medium)
Solu:
对整个intervals按照start_time进行sort(不需要考虑end_time)
if overlaps:
merge intervals
else:
insert a new interval
Code:
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort()
merged = []
cur = intervals[0]
for next in intervals[1:]:
if next[0] <= cur[1]: # overlap -> merge
cur[1] = max(cur[1], next[1])
else:
merged.append(cur)
cur = next
return merged + [cur]
57. 插入区间(Medium)
Solu:
- 时间还太早
cur[1] < newInterval[0]:cur还没有和newInterval产生关联,直接appendcur - 时间过去了
cur[0] > newInterval[1]:后续的所有intervals都不会再和newInterval产生交集,直接append - 有overlap,merge
newIntervalandcur
Code:
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
intervals.append([sys.maxsize, sys.maxsize]) # add sentinel
res = []
for i, cur in enumerate(intervals):
if cur[1] < newInterval[0]:
res.append(cur)
elif cur[0] > newInterval[1]:
res.append(newInterval)
res += intervals[i:]
break
else:
newInterval[0] = min(cur[0], newInterval[0])
newInterval[1] = max(cur[1], newInterval[1])
return res[:-1]
1272. 删除区间(Medium)
Solu:
- no overlap -> 不会被影响,直接append
cur - 有overlap -> 分别去append
cur中没有被toBeRemovedcover掉的头和尾
Code:
class Solution:
def removeInterval(self, intervals: List[List[int]], toBeRemoved: List[int]) -> List[List[int]]:
res = []
for cur in intervals:
if cur[0] > toBeRemoved[1] or cur[1] < toBeRemoved[0]: # no overlap
res.append(cur)
else: # overlap
if cur[0] < toBeRemoved[0]:
res.append([cur[0], toBeRemoved[0]])
if cur[1] > toBeRemoved[1]:
res.append([toBeRemoved[1], cur[1]])
return res
435. 无重叠区间(Medium)
Solu 1:Greedy + 反向思考
min{#使整个区间没有重叠的移除区间} = max{不重叠区间}
- 按照每个
interval的end进行sort - 把所有和当前区间
cur有overlap的intervals都remove
Code 1:
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals = sorted(intervals, key=lambda x: x[1]) # 按照end进行sort
count = 1 # 最大不重叠区间数
curEnd = intervals[0][1]
for i in range(1, len(intervals)):
if intervals[i][0] >= curEnd: # no overlap
count += 1
curEnd = intervals[i][1]
return len(intervals) - count
Solu2:Greedy + 正向思考
- if two conflicts, always remove the later one, in order to leave more space for the later.
Code 2:
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals = sorted(intervals, key=lambda x: x[1])
count = 0
curEnd = intervals[0][1]
for i in range(1, len(intervals)):
if intervals[i][0] >= curEnd: # no overlap
curEnd = intervals[i][1]
else:
count += 1
return count
1288. 删除被覆盖区间(Medium)
Solu:
intervals sort by:
- start time increasing
- end time decreasing
保证只有index小的可以覆盖index大的
Code:
class Solution:
def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: (x[0], -x[1]))
count = 0 # #intervals need to be removed
curEnd = intervals[0][1]
for i in range(1, len(intervals)):
if intervals[i][1] <= curEnd:
count += 1
else:
curEnd = intervals[i][1]
return len(intervals) - count
352. 将数据流变为多个不相交区间(Hard)
Solu:
3种case:
- 需要add this new interval x3:
- merge with both
prevandnext - merge with
prev - merge with
next
- merge with both
- 不需要add x2:
- 本身已经含有这个interval
- 这个interval被
prev或者next完全cover了
Code:
class SummaryRanges {
private TreeSet<int[]> set = null;
public SummaryRanges() {
set = new TreeSet<>((a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);
}
public void addNum(int val) {
int[] interval = new int[] {val, val};
int[] prev = set.lower(interval), next = set.higher(interval);
if (set.contains(interval)
|| (prev != null && prev[1] >= val)
|| (next != null && next[0] <= val)) return; // no need to add
if (prev != null
&& prev[1] + 1 == val
&& next != null
&& next[0] - 1 == val) { // merge two intervals
prev[1] = next[1];
set.remove(next);
} else if (prev != null && prev[1] + 1 == val) prev[1] = val; // extend prev interval
else if (next != null && next[0] - 1 == val) next[0] = val; // extend next interval
else set.add(interval); // add new interval
}
public int[][] getIntervals() {
List<int[]> list = new ArrayList<>();
set.forEach(a -> list.add(a));
return list.toArray(new int[list.size()][]);
}
}
Common Slot
双指针i, j分别遍历slots1和slots2
1229. 安排会议日程(Medium)
Solu:双指针
找到第一个valid common slot
相离:
相交:
包含:
包含:
相交:
相离:
- case 1,6:相离,没有相交的区间
- case 2,3:
A的尾永远大于B的尾。所以相交的区间为[min(A[0], B[0]), B[1]] - case 4,5:
B的尾永远大于A的尾。所以相交的区间为[min(A[0], B[0]), A[1]]
Code:
class Solution:
def minAvailableDuration(self, slots1: List[List[int]], slots2: List[List[int]], duration: int) -> List[int]:
slots1.sort()
slots2.sort()
i, j = 0, 0
while i < len(slots1) and j < len(slots2):
start, end = max(slots1[i][0], slots2[j][0]), min(slots1[i][1], slots2[j][1])
if end - start >= duration:
return [start, start + duration]
# not overlapping or current intersection is too small
if slots1[i][1] < slots2[j][1]:
i += 1
else:
j += 1
return []
986. 区间列表的交集(Medium)
Solu:
双指针,分析同上。略
Code:
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
i, j = 0, 0
res = []
while i < len(firstList) and j < len(secondList):
start, end = max(firstList[i][0], secondList[j][0]), min(firstList[i][1], secondList[j][1])
if start <= end: # exist intersection
res.append([start, end])
if firstList[i][1] > secondList[j][1]:
j += 1
else:
i += 1
return res
759. 员工空闲时间(Hard)
Solu:
- 将所有interval按照
start升序排序 - 一旦
curInterval.end < nextInterval.start,则必然有空隙[curInterval.end, nextInterval.start](因为nextNextInterval.start ≥ nextInterval.start必然成立)
Code:
class Interval:
def __init__(self, start: int = None, end: int = None):
self.start = start
self.end = end
class Solution:
def employeeFreeTime(self, schedule: '[[Interval]]') -> '[Interval]':
pq, res = [], []
for intervals in schedule:
for interval in intervals:
heapq.heappush(pq, [interval.start, interval.end])
cur_end = heapq.heappop(pq)[1]
while pq:
next_start, next_end = pq[0]
if next_start > cur_end: # 必然存在空隙, 加入答案
res.append(Interval(cur_end, next_start))
cur_end = heapq.heappop(pq)[1]
else: # 不存在空隙, merge两个区间
cur_end = max(cur_end, heapq.heappop(pq)[1])
return res
天际线
218. 天际线问题(Hard)
Solu:
- 根据位置进行sort;相同位置下高度最大的楼层优先访问
- 遇到一个楼“开始”(
height > 0),加入pq;遇到一个楼“结束”(height < 0),把这幢楼的高度从pq中删除 - 如果删除/添加一栋楼,会使得当前最大高度
curMax产生变化,则一条新的skyline产生
Code:
class Solution {
public List<List<Integer>> getSkyline(int[][] buildings) {
List<List<Integer>> res = new ArrayList<>();
List<int[]> heights = new ArrayList<>();
for (int[] building : buildings) {
heights.add(new int[] {building[0], building[2]});
heights.add(new int[] {building[1], -building[2]});
}
heights.sort((a, b) -> a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]); // 当同一位置上,楼层最高的先被访问
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
pq.offer(0); // sentinel
int prevMax = 0;
for (int[] height : heights) {
if (height[1] > 0) pq.offer(height[1]); // 遇到新房子,加入pq,从高到低
else pq.remove(-height[1]); // 遇到老房子,从pq中删除
int curMax = pq.peek();
if (prevMax != curMax) { // 检查是否对当前的最大高度产生影响
res.add(Arrays.asList(height[0], curMax)); // 如果是,则一条新的skyline产生
prevMax = curMax;
}
}
return res;
}
}
几何
391. 完美矩形(Hard)
Solu:HashSet
- 「完美矩形」的性质 x2:
- 正好4个「外围顶点」(不与其他任何顶点重叠或覆盖)
∑area = S(4个角落顶点形成的封闭区域)
Code:
class Solution:
def isRectangleCover(self, rectangles: List[List[int]]) -> bool:
# validity:4个"外围顶点" + ∑area = 4个角落顶点形成的封闭区域的面积
seen = set()
areas = 0
for i, rect in enumerate(rectangles):
p1, p2, p3, p4 = (rect[0], rect[1]), (rect[2], rect[1]), (rect[2], rect[3]), (rect[0], rect[3])
points = [p1, p2, p3, p4]
for point in points:
if point in seen:
seen.remove(point)
else:
seen.add(point)
areas += (rect[2] - rect[0]) * (rect[3] - rect[1])
if len(seen) != 4:
return False
seen = sorted(seen, key=lambda x: (x[0], x[1]))
return areas == (seen[3][0] - seen[0][0]) * (seen[3][1] - seen[0][1])
Reference: