2021.11.16LeetCode每日一题——完美矩形

目录

完美矩形

描述

示例 1

示例 2

示例 3

示例 4

提示

方法一:哈希表

方法二:扫描线


完美矩形

描述

给你一个数组 rectangles ,其中 rectangles[i] = [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi, yi) ,右上顶点是 (ai, bi) 。

如果所有矩形一起精确覆盖了某个矩形区域,则返回 true ;否则,返回 false 。

示例 1

输入:rectangles = [[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]]
输出:true
解释:5 个矩形一起可以精确地覆盖一个矩形区域。 

示例 2

输入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]]
输出:false
解释:两个矩形之间有间隔,无法覆盖成一个矩形。

示例 3

输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[3,2,4,4]]
输出:false
解释:图形顶端留有空缺,无法覆盖成一个矩形。

示例 4

输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]]
输出:false
解释:因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。

提示

  • 1 \le rectangles.length \le 2 * 10^4
  • rectangles[i].length == 4
  • -10^5 \le xi, yi, ai, bi \le 10^5

方法一:哈希表

精确覆盖有两个条件:

  • 矩形区域不能有空缺,即矩形区域面积等于所有矩形的面积之和
  • 矩形区域不能有相交区域

面积之和比较好计算,麻烦的是如何判断矩形区域是否重合了。

我们存储矩形的顶点坐标到哈希表中,记录该坐标的出现次数,保证大矩形周围的四个顶点出现次数为1次,其他坐标只能出现2次或者4次。

 

class Solution {
    public boolean isRectangleCover(int[][] rectangles) {
        int[] max = new int[4];//记录外围最大矩形的左下和右上坐标
        max[0] = rectangles[0][0];//对左下坐标进行初始化
        max[1] = rectangles[0][1];
        max[2] = rectangles[0][2];//对右上坐标进行初始化
        max[3] = rectangles[0][3];
        HashMap<Point, Integer> map = new HashMap<>();//存储出现的坐标及出现的次数
        int area=0;//记录所有矩形的面积
        for (int i = 0; i < rectangles.length; i++) {
            area+=(rectangles[i][2]-rectangles[i][0])*(rectangles[i][3]-rectangles[i][1]);//加上当前矩形的面积
            if ((rectangles[i][0] + rectangles[i][1]) < max[0] + max[1]) {//如果新来的矩形超出了当前矩形的最大范围,更新边界
                max[0] = rectangles[i][0];
                max[1] = rectangles[i][1];
            }
            if ((rectangles[i][2] + rectangles[i][3]) > max[2] + max[3]) {//如果新来的矩形超出了当前矩形的最大范围,更新边界
                max[2] = rectangles[i][2];
                max[3] = rectangles[i][3];
            }
            Point point1 = new Point(rectangles[i][0], rectangles[i][1]);//左下的坐标
            Point point2 = new Point(rectangles[i][0], rectangles[i][3]);//左上的坐标
            Point point3 = new Point(rectangles[i][2], rectangles[i][1]);//右下的坐标
            Point point4 = new Point(rectangles[i][2], rectangles[i][3]);//右上的坐标

            map.put(point1,map.getOrDefault(point1,0)+1);//将当前坐标的出现次数+1
            map.put(point2,map.getOrDefault(point2,0)+1);//将当前坐标的出现次数+1
            map.put(point3,map.getOrDefault(point3,0)+1);//将当前坐标的出现次数+1
            map.put(point4,map.getOrDefault(point4,0)+1);//将当前坐标的出现次数+1
        }

        //创建最大矩形的四个角坐标
        Point point1 = new Point(max[0], max[1]);//左下的坐标
        Point point2 = new Point(max[0], max[3]);//左上的坐标
        Point point3 = new Point(max[2], max[1]);//右下的坐标
        Point point4 = new Point(max[2], max[3]);//右上的坐标

        if (area!=(max[2]-max[0])*(max[3]-max[1])||map.getOrDefault(point1,0)!=1||map.getOrDefault(point2,0)!=1||map.getOrDefault(point3,0)!=1||map.getOrDefault(point4,0)!=1){
            return false;
        }

        map.remove(point1);
        map.remove(point2);
        map.remove(point3);
        map.remove(point4);

        for (Map.Entry<Point,Integer> entry : map.entrySet()) {
            if (entry.getValue()!=2&&entry.getValue()!=4){
                return false;
            }
        }
        return true;
    }

    class Point {
    public int x;
    public int y;

    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        return x + y;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Point) {
            Point temp = (Point) obj;
            return this.x == temp.x && this.y == temp.y;
        }
        return false;
    }
}
}

 

方法二:扫描线

将每个矩形 rectangles[i] 看做两条竖直方向的边,使用 (x,y1,y2) 的形式进行存储(其中 y1 代表该竖边的下端点,y2 代表竖边的上端点),同时为了区分是矩形的左边还是右边,再引入一个标识位,即以四元组 (x,y1,y2,flag) 的形式进行存储。

一个完美矩形的充要条件为:对于完美矩形的每一条非边缘的竖边,都「成对」出现(存在两条完全相同的左边和右边);对于完美矩形的两条边缘竖边,最终必然相连成一条完整的(不重叠)的竖边。

如图(红色框的为「完美矩形的边缘竖边」,绿框的为「完美矩形的非边缘竖边」):

image.png

  • 绿色:非边缘竖边必然有成对的左右两条完全相同的竖边重叠在一起;
  • 红色:边缘竖边由于只有单边,必然不重叠,且连接成一条完成的竖边。
class Solution {
    public boolean isRectangleCover(int[][] rectangles) {
        int n = rectangles.length;
        int[][] rs = new int[n * 2][4];
        for (int i = 0, idx = 0; i < n; i++) {
            int[] re = rectangles[i];
            rs[idx++] = new int[]{re[0], re[1], re[3], 1};
            rs[idx++] = new int[]{re[2], re[1], re[3], -1};
        }
        Arrays.sort(rs, (a,b)->{
            if (a[0] != b[0]) return a[0] - b[0];
            return a[1] - b[1];
        });
        n *= 2;
        // 分别存储相同的横坐标下「左边的线段」和「右边的线段」 (y1, y2)
        List<int[]> l1 = new ArrayList<>(), l2 = new ArrayList<>(); 
        for (int l = 0; l < n; ) {
            int r = l;
            l1.clear(); l2.clear();
            // 找到横坐标相同部分
            while (r < n && rs[r][0] == rs[l][0]) r++;
            for (int i = l; i < r; i++) {
                int[] cur = new int[]{rs[i][1], rs[i][2]};
                List<int[]> list = rs[i][3] == 1 ? l1 : l2;
                if (list.isEmpty()) {
                    list.add(cur);
                } else {
                    int[] prev = list.get(list.size() - 1);
                    if (cur[0] < prev[1]) return false; // 存在重叠
                    else if (cur[0] == prev[1]) prev[1] = cur[1]; // 首尾相连
                    else list.add(cur); 
                }
            }
            if (l > 0 && r < n) {
                // 若不是完美矩形的边缘竖边,检查是否成对出现
                if (l1.size() != l2.size()) return false;
                for (int i = 0; i < l1.size(); i++) {
                    if (l1.get(i)[0] == l2.get(i)[0] && l1.get(i)[1] == l2.get(i)[1]) continue;
                    return false;
                }
            } else {
                // 若是完美矩形的边缘竖边,检查是否形成完整一段
                if (l1.size() + l2.size() != 1) return false;
            }
            l = r;
        }
        return true;
    }
}

Guess you like

Origin blog.csdn.net/weixin_39478524/article/details/121349719