基本上就是Binary Search
简单二分
因为题目的height只有100种可能,我的naive的想法是统计每个height的width,然后遍历到每个点(x, y)
的时候,把height>=y
的每个width数组都二分查找一遍……
from collections import defaultdict
from bisect import bisect_left
class Solution:
def countRectangles(self, rectangles: List[List[int]], points: List[List[int]]) -> List[int]:
rectangles = sorted(rectangles, key=lambda x: (x[1], x[0]))
heights, widths = [], []
for l, h in rectangles:
if not heights or h != heights[-1]:
heights.append(h)
widths.append([])
widths[-1].append(l)
H = len(heights)
ans = []
for x, y in points:
i = bisect_left(heights, y)
cnt = 0
for j in range(i, H):
arr = widths[j]
k = bisect_left(arr, x)
cnt += len(arr) - k
ans.append(cnt)
return ans
排序:
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)
二分查找:第二个循环由于height只有100以内,所以可以近似为常数次。因此对每个point,复杂度
O
(
l
o
g
N
)
O(logN)
O(logN),加一起
O
(
M
l
o
g
N
)
O(MlogN)
O(MlogN)
有序数组+二分
理想情况下,如果我们只通过一次二分查找就可以获得包含这个点的矩形数量就最好了。
如何做到呢?我们需要一个所有横坐标在当前点右边的矩形的高度的有序数组。
一种方法是:先把矩形和点都按横坐标大小从小到大排,然后先初始化一个所有矩形高度的有序数组。
注意: 这里高度是可以重复的,并且这个高度的序列和矩形数据不需要一一对应。
然后开始遍历每个点,当遍历到某个点的时候,显然,横坐标在该点左边的点都是没用的了,需要从上面那个有序数组里删除。怎么删除呢?直接把它对应的高度pop出来一个就可以了。
正是因为有序数组里高度和矩形没有强绑定关系,比如我现在有10个高度为2的矩形,所以在数组里会有10个2,而并不关心这些高度为2的矩形的宽度是多少。现在要删除矩形(5, 2),其实从数组里任意删掉一个2就可以了。
from collections import defaultdict
from bisect import bisect_left
class Solution:
def countRectangles(self, rectangles: List[List[int]], points: List[List[int]]) -> List[int]:
rectangles.sort()
points = sorted([(p, i) for i, p in enumerate(points)])
n, m = len(rectangles), len(points)
heights = list(sorted([h for l, h in rectangles]))
ans = [0] * m
i = 0
for (x, y), j in points:
while i<n and rectangles[i][0] < x:
heights.pop(bisect_left(heights, rectangles[i][1]))
i += 1
ans[j] = len(heights) - bisect_left(heights, y)
return ans
- 找到删除位置是 O ( l o g N ) O(logN) O(logN)的,但由于python里的list不是真正的链表,其实删除操作是 O ( N ) O(N) O(N)的。删除操作最多执行N次,这部分 O ( N 2 ) O(N^2) O(N2)
- 计算矩形个数是 O ( l o g N ) O(logN) O(logN)的,每遍历一个点执行一次,所以是 O ( M l o g N ) O(MlogN) O(MlogN)
所以如果从复杂度分析上看,第二种方法反而看起来不快。但事实上针对LC的test,这种方法更快hhh
反过来也可以有另一种实现方式:
- 把矩形和点都按横坐标从大到小排序
- 高度的有序数组初始化为空:
[]
- 遍历每个点,把横坐标在它右边的矩形的高度都插入有序数组
- 计算数量的时候同样还是直接二分查找即可
from collections import defaultdict
from bisect import insort, bisect_left
class Solution:
def countRectangles(self, rectangles: List[List[int]], points: List[List[int]]) -> List[int]:
rectangles.sort(reverse=True)
points = sorted([(p, i) for i, p in enumerate(points)], reverse=True)
n, m = len(rectangles), len(points)
heights = []
ans = [0] * m
i = 0
for (x, y), j in points:
while i<n and rectangles[i][0] >= x:
insort(heights, rectangles[i][1])
i += 1
ans[j] = len(heights) - bisect_left(heights, y)
return ans