leetcode 掃描線專題 06-leetcode.391 perfect-rectangle 力扣.391 完美矩形

老马啸西风發表於2024-11-18

題目

給你一個陣列 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 個矩形一起可以精確地覆蓋一個矩形區域。

1

示例 2:

輸入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]]
輸出:false
解釋:兩個矩形之間有間隔,無法覆蓋成一個矩形。

2

示例 3:

輸入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]]
輸出:false
解釋:因為中間有相交區域,雖然形成了矩形,但不是精確覆蓋。

3

提示:

1 <= rectangles.length <= 2 * 10^4

rectangles[i].length == 4

-10^5 <= xi < ai <= 10^5

-10^5 <= yi < bi <= 10^5

v1-基本思路 HashMap

思路

完美矩形其實需要符合 2 個條件:

  1. 所有的不重合的點應該只有最後完美大矩形的 4 個頂點

  2. 小矩形的面積之和等於最後的完美大矩形的面積

我們可以用 HashMap 記錄點,出現偶數次的移除。同時累加每一個小矩形的面積。

最後的 4 個點,排序一下,計算出完美矩形的面積。

程式碼

class Solution {
    public boolean isRectangleCover(int[][] rectangles) {
        Map<String, Integer> pointMap = new HashMap<>();

        int area = 0;
        for(int[] ints : rectangles) {
            String one = ints[0]+","+ints[1];
            String two = ints[2]+","+ints[3];
            String three = ints[0]+","+ints[3];
            String four = ints[2]+","+ints[1];

            pointMap.put(one, (pointMap.getOrDefault(one, 0) + 1) % 2);
            pointMap.put(two, (pointMap.getOrDefault(two, 0) + 1) % 2);
            pointMap.put(three, (pointMap.getOrDefault(three, 0) + 1) % 2);
            pointMap.put(four, (pointMap.getOrDefault(four, 0) + 1) % 2);

            int currentArea = (ints[2]-ints[0]) * (ints[3] - ints[1]);
            area += currentArea;
        }

        List<Integer> xList = new ArrayList<>();
        List<Integer> yList = new ArrayList<>();

        for(Map.Entry<String,Integer> entry : pointMap.entrySet()) {
            String key = entry.getKey();
            Integer count = entry.getValue();
            if(count == 1) {
                String[] splits = key.split(",");
                int x = Integer.parseInt(splits[0]);
                int y = Integer.parseInt(splits[1]);

                xList.add(x);
                yList.add(y);
            }
        }

        // 應該有4個點
        if(xList.size() != 4 || yList.size() != 4) {
            return false;
        }

        // 面積計算
        Collections.sort(xList);
        Collections.sort(yList);
        int fourPointArea = (xList.get(3) - xList.get(0)) * (yList.get(3) - yList.get(0));

        if(fourPointArea == area) {
            return true;
        }
        return false;
    }
}

效果

57ms 擊敗36.84%

小結

這種解法其實要求對題目的理解比較深入,屬於【特定解法】。

v2-Set 最佳化

思路

這種透過 Map 計算次數的,其實也可以透過 Set 最佳化一下。

1)如果點不存在,則加入

2)如果存在,則移除

整體思想類似。

還有一個改良點,使我們可以在遍歷所有的點的時候,直接把 4 個頂點確認出來。

也就是 (min_x,min_y) 和 (max_x, max_y) 對應最後的完美節點的左下/右上,從而直接確定面積。

實現

    public boolean isRectangleCover(int[][] rectangles) {
        // 定義事件列表
        int totalArea = 0;
        int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;

        // 頂點集合
        Set<String> points = new HashSet<>();

        for (int[] rect : rectangles) {
            int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];

            // 更新邊界
            minX = Math.min(minX, x1);
            minY = Math.min(minY, y1);
            maxX = Math.max(maxX, x2);
            maxY = Math.max(maxY, y2);

            // 累加面積
            totalArea += (x2 - x1) * (y2 - y1);

            // 更新頂點集合
            String[] corners = {
                    x1 + "," + y1, x1 + "," + y2, x2 + "," + y1, x2 + "," + y2
            };
            for (String corner : corners) {
                if (!points.add(corner)) {
                    points.remove(corner);
                }
            }
        }

        // 頂點檢查:精確覆蓋的矩形應該只有 4 個頂點
        if (points.size() != 4 ||
                !points.contains(minX + "," + minY) ||
                !points.contains(minX + "," + maxY) ||
                !points.contains(maxX + "," + minY) ||
                !points.contains(maxX + "," + maxY)) {
            return false;
        }

        // 檢查總面積是否一致
        int expectedArea = (maxX - minX) * (maxY - minY);
        return expectedArea == totalArea;
    }

效果

39ms 擊敗 68.42%

效果好好一點。

v3-掃描線

思路

做演算法,還是要看三葉!

【宮水三葉】常規掃描線題目

將每個矩形 rectangles[i] 看做兩條豎直方向的邊,使用 (x,y1,y2) 的形式進行儲存(其中 y1 代表該豎邊的下端點,y2 代表豎邊的上端點),同時為了區分是矩形的左邊還是右邊,再引入一個標識位,即以四元組 (x,y1,y2,flag) 的形式進行儲存。

一個完美矩形的充要條件為:對於完美矩形的每一條非邊緣的豎邊,都「成對」出現(存在兩條完全相同的左邊和右邊重疊在一起);對於完美矩形的兩條邊緣豎邊,均獨立為一條連續的(不重疊)的豎邊。

如圖(紅色框的為「完美矩形的邊緣豎邊」,綠框的為「完美矩形的非邊緣豎邊」):

掃描線

綠色:非邊緣豎邊必然有成對的左右兩條完全相同的豎邊重疊在一起;

紅色:邊緣豎邊由於只有單邊,必然不重疊,且連線成一條完成的豎邊。

實現

class Solution {
    public boolean isRectangleCover(int[][] rectangles) {
        int len = rectangles.length*2, ids = 0;
        int[][] re = new int [len][4];
        //初始化re陣列,組成[橫座標,縱座標下頂點,縱座標上頂點,矩形的左邊or右邊標誌]
        for(int[] i:rectangles){
            re[ids++] = new int[]{i[0],i[1],i[3],1};
            re[ids++] = new int[]{i[2],i[1],i[3],-1};
        }
        //排序,按照橫座標進行排序,橫座標相等就按縱座標排序
        Arrays.sort(re,(o1,o2)-> o1[0]!=o2[0]?o1[0]-o2[0]:o1[1]-o2[1]);

        //操作每一個頂點,判斷是否符合要求
        for(int i = 0; i < len;){
            //如果該邊是矩形的左邊界,就加入left
            List<int[]> left = new ArrayList<>();
            //如果該邊是矩形的左邊界,就加入right
            List<int[]> right = new ArrayList<>();
            //標誌該邊是不是 矩形的左邊
            boolean flag = i == 0;
            //判斷橫座標相同情況下的邊
            int x = i;
            while(x<len&&re[x][0]==re[i][0]) x++;
            //判斷該橫座標的 邊是不是符合要求
            while(i<x){
                //因為是引用資料型別,所以可以直接操作list,相當於操作left或者right
                List<int[]> list = re[i][3]==1?left:right;
                if(list.isEmpty()){
                    list.add(re[i++]);
                }else{
                    int[] pre = list.get(list.size()-1);
                    int[] cur = re[i++];
                    //有重疊 直接放回false
                    if(cur[1]<pre[2]) return false;
                    if(cur[1]==pre[2]) pre[2] = cur[2];
                    else list.add(cur);
                }
            }
            //判斷邊是中間邊還是邊界邊
            if(!flag&&x<len){
                //如果是中間邊 判斷左右是不是相等
                if(left.size()!=right.size()) return false;
                for(int j = 0; j < left.size(); ++j){
                    if(left.get(j)[2]==right.get(j)[2]&&left.get(j)[1]==right.get(j)[1]) continue;
                    return false;
                }
            } else {
                //如果是邊界邊判斷是不是一條
                if (left.size()!=1&&right.size()==0||left.size()==0&&right.size()!=1) return false;
            }
        }
        return true;
    }
}

效果

25ms 擊敗 94.74%

小結

感覺有一個順序的問題,這一題實際上是多矩形的重疊問題。

應該先學習一下 T836 + T223 + T850 可能再做這一題就會比較自然。

開源地址

為了便於大家學習,所有實現均已開源。歡迎 fork + star~

https://github.com/houbb/leetcode

掃描線專題

leetcode 掃描線專題 06-掃描線演算法(Sweep Line Algorithm)

leetcode 掃描線專題 06-leetcode.218 the-skyline-problem 力扣.218 天際線問題

leetcode 掃描線專題 06-leetcode.252 meeting room 力扣.252 會議室

leetcode 掃描線專題 06-leetcode.253 meeting room ii 力扣.253 會議室 II

leetcode 掃描線專題 06-leetcode.1851 minimum-interval-to-include-each-query 力扣.1851 包含每個查詢的最小區間

leetcode 掃描線專題 06-leetcode.223 rectangle-area 力扣.223 矩形面積

leetcode 掃描線專題 06-leetcode.3047 find-the-largest-area-of-square-inside-two-rectangles 力扣.3047 求交集區域的最大正方形面積

leetcode 掃描線專題 06-leetcode.391 perfect-rectangle 力扣.391 完美矩形

leetcode 掃描線專題 06-leetcode.836 rectangle-overlap 力扣.836 矩形重疊

leetcode 掃描線專題 06-leetcode.850 rectangle-area 力扣.850 矩形面積 II

相關文章