前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >391.完美矩形,如果用扫描线算法你会怎么做

391.完美矩形,如果用扫描线算法你会怎么做

作者头像
我脱下短袖
发布2020-03-11 10:19:55
1.1K0
发布2020-03-11 10:19:55
举报
文章被收录于专栏:算法无遗策算法无遗策
今天分享一个LeetCode题,题号是391,标题是完美矩形,题目标签是Line Sweep [ 扫描线算法 ],题目难度是困难。

题目描述

我们有 N 个与坐标轴对齐的矩形, 其中 N > 0, 判断它们是否能精确地覆盖一个矩形区域。

每个矩形用左下角的点和右上角的点的坐标来表示。例如, 一个单位正方形可以表示为 [1,1,2,2]。( 左下角的点的坐标为 (1, 1) 以及右上角的点的坐标为 (2, 2) )。

示例 1

示例 1:

代码语言:javascript
复制
rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [3,2,4,4],
  [1,3,2,4],
  [2,3,3,4]
]

返回 true。5个矩形一起可以精确地覆盖一个矩形区域。

'◡'

示例 2

示例 2:

代码语言:javascript
复制
rectangles = [
  [1,1,2,3],
  [1,3,2,4],
  [3,1,4,2],
  [3,2,4,4]
]

返回 false。两个矩形之间有间隔,无法覆盖成一个矩形。

'◡'

示例 3

示例 3:

代码语言:javascript
复制
rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [1,3,2,4],
  [3,2,4,4]
]

返回 false。图形顶端留有间隔,无法覆盖成一个矩形。

'◡'

示例 4

示例 4:

代码语言:javascript
复制
rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [1,3,2,4],
  [2,2,4,4]
]

返回 false。因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。

解题

这道题的题目标签是扫描线算法,那我们就按扫描线算法把这道题倒出来。

先简单介绍扫描线算法,扫描线平行于任一坐标轴扫过平面,随着坐标轴的变量而移动,变量的变化方向可负可正。意思是既可以从左到右扫描,也可以从右到左扫描;既可以从上到下扫描,也可以从下到上扫描。

所以俺这里就采用平行于y轴,从左到右扫描坐标图。

然后,再看输入示例,比如是这样的 [1,1,3,3], [3,1,4,2], [3,2,4,4], [1,3,2,4], [2,3,3,4] , 我们可以把它设定成下面这样的:

每个矩形的两个关键点

俺选中了每个矩形的左下和右下的两个端点,当然也可以选每个矩形的左上和右上的两个端点。

因为我们得到的红黑端点是乱序的,要给它先按x轴排序。

注意,如果两个端点的横坐标相等,我们要先判断红黑色点,如果是黑色点就排前面,如果是红色点就排后面,相等于就给它们分组了。不过黑色点和红色点怎么分组呢?

我们可以把黑色点的高度变成负数,红色点的高度还是原值,这样就可以分组了,同时也为后面的出入堆进行了有利的判断。

如果在同一横坐标有多个黑色点的话,就按照y轴排序,另一组的红色点也是。

如果某编程语言的数据结构有可重复且有序的集合的话,可以采用这个集合;如果没有,就想办法实现。

在Java没有这样的集合,但是可以利用TreeSet的Comparator内部类实现,在Comparator内部类重写一个比较方法,其中Triplet保存的是一个红色点或黑色点。

代码语言:javascript
复制
TreeSet<Triplet> triplets = new TreeSet<>((o1, o2) -> {
    if (o1.x != o2.x) return o1.x - o2.x;
    if (o1.h * o2.h < 0) return o1.h < 0 ? -1 : 1; // 正数 和 负数要分类,负数在左,正数在右
    if (o1.y != o2.y) return o1.y - o2.y;
    return 1; // 保证重复且有序 ,例如这个用例:[[0,0,4,1],[0,0,4,1]]
});

这样,triplets就按照要求已经排好序了。

然后进行扫描线的移动,移到第一个矩形的左边界的时候,我们要到这个高度入堆。同时将这同一横坐标的高度全部入堆,然后把堆里的高度全部累加起来,得到一个临时高度。如果临时高度和投影高度不一致的话,则可以直接返回false。

投影

同时,同时也可以创建投影的上界和下界,只要是超出这个范围,那是不满足完美矩形的,可以直接返回false。

我们还要注意临时上界,如果下一个红色点的纵坐标要小于临时上界的话,也可以直接返回flase。

临时上界

如果临时上界等于下一红色点的纵坐标的话,则说明这两个矩形是没有覆盖的,接着将临时上界更新为该横坐标最高的高度。

如果扫描线在不同的横坐标,遇到第一个黑色点的话,则将临时上界更新为该横坐标第一个黑色点的纵坐标,从最低处开始。

依次类推,直到坐标图上矩形的最右边界,这个边界可以不用判断了,因为最右边界的上一边界是满足的,而且每一个都是矩形,左边界满足,右边界如果没有新的矩形的话自然也会满足,所以判断到最右边界还没有返回false,则可以直接返回true。

动画:扫描线移动过程

视频大小:1.15M,比Gif格式要小,可放心看

Java代码,从左到右扫描
代码语言:javascript
复制
import java.util.*;

class Solution {
    private class Triplet {
        int x;
        int y;
        int h;

        Triplet(int x, int y, int h) {
            this.x = x;
            this.y = y;
            this.h = h;
        }
    }

    public boolean isRectangleCover(int[][] rectangles) { // 左上角 右下角坐标
        // 将 rectangles 转换 三元组 [x y h]
        TreeSet<Triplet> triplets = new TreeSet<>((o1, o2) -> {
            if (o1.x != o2.x) return o1.x - o2.x;
            if (o1.h * o2.h < 0) return o1.h < 0 ? -1 : 1; // 正数 和 负数要分类,负数在左,正数在右
            if (o1.y != o2.y) return o1.y - o2.y;
            return 1; // 保证重复且有序 ,例如这个用例:[[0,0,4,1],[0,0,4,1]]
        });
        for (int[] rectangle : rectangles) {
            triplets.add(new Triplet(rectangle[0], rectangle[1], rectangle[3] - rectangle[1])); // 左端点的高度为正
            triplets.add(new Triplet(rectangle[2], rectangle[1], rectangle[1] - rectangle[3])); // 右端点的高度为负
        }
        // 创建优先队列
        Queue<Integer> queue = new PriorityQueue<>();

        // 边界上下 宽度
        int high = 0, low = 0, width = 0;
        // 记录横坐标的变化
        int axis = triplets.first().x;

        // 以第一个左边界作为标准

        low = triplets.first().y;
        for (Triplet triplet : triplets) {
            if (axis == triplet.x) {
                high = triplet.y + triplet.h;
            } else break;
        }
        width = high - low;

        // 临时宽度; 临时上界
        int cur_w = 0, cur_h = Integer.MIN_VALUE;

        // 正数入堆 负数出堆
        for (Triplet triplet : triplets) {
            // 判断 是否出界
            if (triplet.x < low) return false;
            if (triplet.y + triplet.h > high) return false;

            if (axis != triplet.x) {
                if (cur_w != width) return false;
                cur_h = triplet.y;
                axis = triplet.x;
            } else if (triplet.y < cur_h) {
                return false;
            }

            if (triplet.h > 0) cur_h = triplet.y + triplet.h;

            if (triplet.h > 0) queue.add(triplet.h);
            else if (triplet.h < 0) queue.remove(-triplet.h);
            cur_w += triplet.h;
        }
        return true;
    }
}
Java代码,从下到上扫描
代码语言:javascript
复制
public boolean isRectangleCover(int[][] rectangles) {
    TreeSet<Triplet> triplets = new TreeSet<>((o1, o2) -> {
        if (o1.y != o2.y) return o1.y - o2.y;
        if (o1.h * o2.h < 0) return o1.h < 0 ? -1 : 1; // 正数 和 负数要分类,负数在左,正数在右
        if (o1.x != o2.x) return o1.x - o2.x;
        return 1; // 保证重复且有序 ,例如这个用例:[[0,0,4,1],[0,0,4,1]]
    });
    for (int[] rectangle : rectangles) {
        triplets.add(new Triplet(rectangle[0], rectangle[1], rectangle[2] - rectangle[0])); // 左端点的高度为正
        triplets.add(new Triplet(rectangle[0], rectangle[3], rectangle[0] - rectangle[2])); // 右端点的高度为负
    }
    // 创建优先队列
    Queue<Integer> queue = new PriorityQueue<>();
    int l = 0, r = 0, width = 0;
    // 记录纵坐标的变化
    int axis = triplets.first().y;

    l = triplets.first().x;
    for (Triplet triplet : triplets) {
        if (axis == triplet.y) {
            r = triplet.x + triplet.h;
        } else break;
    }
    width = r - l;
    int cur_w = 0, cur_len = Integer.MIN_VALUE;

    // 正数入堆 负数出堆
    for (Triplet triplet : triplets) {
        // 判断 是否出界
        if (triplet.x < l) return false;
        if (triplet.x + triplet.h > r) return false;

        if (axis != triplet.y) {
            if (cur_w != width) return false;
            cur_len = triplet.x;
            axis = triplet.y;
        } else if (triplet.x < cur_len) {
            return false;
        }

        if (triplet.h > 0) cur_len = triplet.x + triplet.h;

        if (triplet.h > 0) queue.add(triplet.h);
        else if (triplet.h < 0) queue.remove(-triplet.h);
        cur_w += triplet.h;
    }

    return true;
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 算法无遗策 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目描述
  • 解题
    • 动画:扫描线移动过程
      • Java代码,从左到右扫描
        • Java代码,从下到上扫描
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档