LeetCode 记录-850. 矩形面积 II

352 阅读4分钟

LeetCode 记录-850. 矩形面积 II

我的解法

思路

image.png

首先,我的想法是,因为正方形可能会有重叠,所以想要通过序列化每个111*1的正方形,来判断是否重复。但是看到(0=<x<=1090=<x<=10^9),就打消了这个想法,考虑到这样的空间复杂度和时间复杂度都会特别的高。 第二个想法是,因为看到(1<=rectangles.length<=2001<=rectangles.length<=200)。假如设计的算法和 length 呈正比,那么复杂度就会比较低。 想不出什么复杂度比较低的方法,暴力一点就是每每添加一个矩形,就和当前矩形前面的所有矩形对比比较一下,去除掉没有重叠的面积。但这样复杂度应该会比较高。 所以就准备看答案啦。


官方解法 1: 离散化 + 扫描线 + 使用简单数组实时维护

思路

我们先解释扫描线的概念:想象一条竖直的直线从平面的最左端扫到最右端,在扫描的过程中,直线上的一些线段会被给定的矩形覆盖。将这些覆盖的线段长度进行积分,就可以得到矩形的面积之和。每个矩形有一个左边界和一个右边界,在扫描到矩形的左边界时,覆盖的长度可能会增加;在扫描到矩形的右边界时,覆盖的长度可能会减少。如果给定了 n 个矩形,那么覆盖的线段长度最多变化 2n 次,此时我们就可以将两次变化之间的部分合并起来,一起计算:即这一部分矩形的面积,等于覆盖的线段长度,乘以扫描线在水平方向移动过的距离。

因此,我们可以首先将所有矩形的左右边界按照横坐标进行排序,这样就确定了扫描线扫描的顺序。随后我们遍历这些左右边界,一次性地处理掉一批横坐标相同的左右边界,对应地增加或者减少覆盖的长度。在这之后,下一个未遍历到的坐右边界的横坐标,减去这一批左右边界的横坐标,就是扫描线在水平方向移动过的距离。

那么我们如何维护「覆盖的线段长度」呢?这里同样可以使用到离散化的技巧(扫描线就是一种离散化的技巧,将大范围的连续的坐标转化成 2n 个离散的坐标)。由于矩形的上下边界也只有 2n 个,它们会将 y 轴分成 2n+1 个部分,中间的 2n−1 个部分均为线段,会被矩形覆盖到(最外侧的 2 个部分为射线,不会被矩形覆盖到),并且每一个线段要么完全被覆盖,要么完全不被覆盖。因此我们可以使用两个长度为 2n−1 的数组 seg 和 length,其中 seg[i] 表示第 i 个线段被矩形覆盖的次数,length[i] 表示第 i 个线段的长度。当扫描线遇到一个左边界时,我们就将左边界覆盖到的线段对应的 seg[i] 全部加 1;遇到一个右边界时,我们就将右边界覆盖到的线段对应的 seg[i] 全部减 1。在处理掉一批横坐标相同的左右边界后,seg[i] 如果大于 0,说明它被覆盖,我们累加所有的 length[i],即可得到「覆盖的线段长度」。

总体思路和代码看了一下,大致理解了思路,对现在的我来说蛮困难,所以先不看方法 2 了。

代码

class Solution:
    def rectangleArea(self, rectangles: List[List[int]]) -> int:
        hbound = set()
        for rect in rectangles:
            # 下边界
            hbound.add(rect[1])
            # 上边界
            hbound.add(rect[3])

        hbound = sorted(hbound)
        m = len(hbound)
        # 「思路与算法部分」的 length 数组并不需要显式地存储下来
        # length[i] 可以通过 hbound[i+1] - hbound[i] 得到
        seg = [0] * (m - 1)

        sweep = list()
        for i, rect in enumerate(rectangles):
            # 左边界
            sweep.append((rect[0], i, 1))
            # 右边界
            sweep.append((rect[2], i, -1))
        sweep.sort()

        ans = i = 0
        while i < len(sweep):
            j = i
            while j + 1 < len(sweep) and sweep[i][0] == sweep[j + 1][0]:
                j += 1
            if j + 1 == len(sweep):
                break

            # 一次性地处理掉一批横坐标相同的左右边界
            for k in range(i, j + 1):
                _, idx, diff = sweep[k]
                left, right = rectangles[idx][1], rectangles[idx][3]
                for x in range(m - 1):
                    if left <= hbound[x] and hbound[x + 1] <= right:
                        seg[x] += diff

            cover = 0
            for k in range(m - 1):
                if seg[k] > 0:
                    cover += (hbound[k + 1] - hbound[k])
            ans += cover * (sweep[j + 1][0] - sweep[j][0])
            i = j + 1

        return ans % (10**9 + 7)

复杂度分析

时间复杂度

O(n^2),其中 n 是矩形的个数。

空间复杂度

O(n),即为扫描线需要使用的空间。