[LeetCode] Perfect Rectangle 完美矩形


 

Given N axis-aligned rectangles where N > 0, determine if they all together form an exact cover of a rectangular region.

Each rectangle is represented as a bottom-left point and a top-right point. For example, a unit square is represented as [1,1,2,2]. (coordinate of bottom-left point is (1, 1) and top-right point is (2, 2)).

Example 1:

rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [3,2,4,4],
  [1,3,2,4],
  [2,3,3,4]
]

Return true. All 5 rectangles together form an exact cover of a rectangular region.

 

Example 2:

rectangles = [
  [1,1,2,3],
  [1,3,2,4],
  [3,1,4,2],
  [3,2,4,4]
]

Return false. Because there is a gap between the two rectangular regions.

Example 3:

rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [1,3,2,4],
  [3,2,4,4]
]

Return false. Because there is a gap in the top center.

Example 4:

rectangles = [
  [1,1,3,3],
  [3,1,4,2],
  [1,3,2,4],
  [2,2,4,4]
]

Return false. Because two of the rectangles overlap with each other.

 

這道題是LeetCode第二周編程比賽的壓軸題目,然而我並沒有做出來,我想了兩種方法都無法通過OJ的大數據集合,第一種方法是對於每一個矩形,我將其拆分為多個面積為1的單位矩形,然后以其左下方的點為標記,用一個哈希表建立每一個單位矩形和遍歷到的矩形的映射,因為每個單位矩形只能屬於一個矩形,否則就會有重疊,我感覺這種思路應該沒錯,但是由於把每一個遍歷到的矩形拆分為單位矩形再建立映射很費時間,尤其是當矩形很大的時候,TLE就很正常了,后來我試的第二種方法是對於遍歷到的每個矩形都和其他所有矩形檢測一遍是否重疊,這種方法也是毫無懸念的TLE。

博主能力有限,只能去論壇中找各位大神的解法,發現下面兩種方法比較fancy,也比較好理解。首先來看第一種方法,這種方法的設計思路很巧妙,利用了mask,也就是位操作Bit Manipulation的一些技巧,下面這張圖來自這個帖子

所有的矩形的四個頂點只會有下面藍,綠,紅三種情況,其中藍表示該頂點周圍沒有其他矩形,T型的綠點表示兩個矩形並排相鄰,紅點表示四個矩形相鄰,那么在一個完美矩形中,藍色的點只能有四個,這是個很重要的判斷條件。我們再來看矩形的四個頂點,我們按照左下,左上,右上,右下的順序來給頂點標號為1,2,4,8,為啥不是1,2,3,4呢,我們注意它們的二進制1(0001),2(0010),4(0100),8(1000),這樣便於我們與和或的操作,我們還需要知道的一個判定條件是,當一個點是某一個矩形的左下頂點時,這個點就不能是其他矩形的左下頂點了,這個條件對於這四種頂點都要成立,那么對於每一個點,如果它是某個矩形的四個頂點之一,我們記錄下來,如果在別的矩形中它又是相同的頂點,那么直接返回false即可,這樣就體現了我們標記為1,2,4,8的好處,我們可以按位檢查1。如果每個點的屬性沒有沖突,那么我們來驗證每個點的mask是否合理,通過上面的分析,我們知道每個點只能是藍,綠,紅三種情況的一種,其中藍的情況是mask的四位中只有一個1,分別就是1(0001),2(0010),4(0100),8(1000),而且藍點只能有四個;那么對於T型的綠點,mask的四位中有兩個1,那么就有六種情況,分別是12(1100), 10(1010), 9(1001), 6(0110), 5(0101), 3(0011);而對於紅點,mask的四位都是1,只有一種情況15(1111),那么我們可以通過直接找mask是1,2,4,8的個數,也可以間接通過找不是綠點和紅點的個數,看是否是四個。最后一個判定條件是每個矩形面積累加和要等於最后的大矩形的面積,那么大矩形的面積我們通過計算最小左下點和最大右上點來計算出來即可, 參見代碼如下:

 

解法一:

class Solution {
public:
    bool isRectangleCover(vector<vector<int>>& rectangles) {
        unordered_map<string, int> m;
        int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN, area = 0, cnt = 0;
        for (auto rect : rectangles) {
            min_x = min(min_x, rect[0]);
            min_y = min(min_y, rect[1]);
            max_x = max(max_x, rect[2]);
            max_y = max(max_y, rect[3]);
            area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
            if (!isValid(m, to_string(rect[0]) + "_" + to_string(rect[1]), 1)) return false; // bottom-left
            if (!isValid(m, to_string(rect[0]) + "_" + to_string(rect[3]), 2)) return false; // top-left
            if (!isValid(m, to_string(rect[2]) + "_" + to_string(rect[3]), 4)) return false; // top-right
            if (!isValid(m, to_string(rect[2]) + "_" + to_string(rect[1]), 8)) return false; // bottom-right
        }
        for (auto it = m.begin(); it != m.end(); ++it) {
            int t = it->second;
            if (t != 15 && t != 12 && t != 10 && t != 9 && t != 6 && t != 5 && t!= 3) {
                ++cnt;
            }
        }
        return cnt == 4 && area == (max_x - min_x) * (max_y - min_y);
    }
    bool isValid(unordered_map<string, int>& m, string corner, int type) {
        int& val = m[corner];
        if (val & type) return false;
        val |= type;
        return true;
    }
};

 

下面這種方法也相當的巧妙, 提出這種算法的大神細心的發現了每種點的規律,每個綠點其實都是兩個頂點的重合,每個紅點都是四個頂點的重合,而每個藍點只有一個頂點,有了這條神奇的性質就不用再去判斷“每個點最多只能是一個矩形的左下,左上,右上,或右下頂點”這條性質了,我們直接用一個set,對於遍歷到的任意一個頂點,如果set中已經存在了,則刪去這個點,如果沒有就加上,這樣最后會把綠點和紅點都濾去,剩下的都是藍點,我們只要看藍點的個數是否為四個,再加上檢測每個矩形面積累加和要等於最后的大矩形的面積即可,參見代碼如下:

 

解法二:

class Solution {
public:
    bool isRectangleCover(vector<vector<int>>& rectangles) {
        unordered_set<string> st;
        int min_x = INT_MAX, min_y = INT_MAX, max_x = INT_MIN, max_y = INT_MIN, area = 0;
        for (auto rect : rectangles) {
            min_x = min(min_x, rect[0]);
            min_y = min(min_y, rect[1]);
            max_x = max(max_x, rect[2]);
            max_y = max(max_y, rect[3]);
            area += (rect[2] - rect[0]) * (rect[3] - rect[1]);
            string s1 = to_string(rect[0]) + "_" + to_string(rect[1]); // bottom-left
            string s2 = to_string(rect[0]) + "_" + to_string(rect[3]); // top-left
            string s3 = to_string(rect[2]) + "_" + to_string(rect[3]); // top-right
            string s4 = to_string(rect[2]) + "_" + to_string(rect[1]); // bottom-right
            if (st.count(s1)) st.erase(s1);
            else st.insert(s1);
            if (st.count(s2)) st.erase(s2);
            else st.insert(s2);
            if (st.count(s3)) st.erase(s3);
            else st.insert(s3);
            if (st.count(s4)) st.erase(s4);
            else st.insert(s4);
        }
        string t1 = to_string(min_x) + "_" + to_string(min_y);
        string t2 = to_string(min_x) + "_" + to_string(max_y);
        string t3 = to_string(max_x) + "_" + to_string(max_y);
        string t4 = to_string(max_x) + "_" + to_string(min_y);
        if (!st.count(t1) || !st.count(t2) || !st.count(t3) || !st.count(t4) || st.size() != 4) return false;
        return area == (max_x - min_x) * (max_y - min_y);
    }
};

 

參考資料:

https://discuss.leetcode.com/topic/56052/really-easy-understanding-solution-o-n-java

https://discuss.leetcode.com/topic/55997/short-java-solution-with-explanation-updated/2

https://discuss.leetcode.com/topic/55923/o-n-solution-by-counting-corners-with-detailed-explaination

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM