1. 程式人生 > >[LeetCode] Random Point in Non-overlapping Rectangles 非重疊矩形中的隨機點

[LeetCode] Random Point in Non-overlapping Rectangles 非重疊矩形中的隨機點

Given a list of non-overlapping axis-aligned rectangles rects, write a function pick which randomly and uniformily picks an integer point in the space covered by the rectangles.

Note:

  1. An integer point is a point that has integer coordinates. 
  2. A point on the perimeter of a rectangle is included in the space covered by the rectangles. 
  3. ith rectangle = rects[i] = [x1,y1,x2,y2], where [x1, y1] are the integer coordinates of the bottom-left corner, and [x2, y2] are the integer coordinates of the top-right corner.
  4. length and width of each rectangle does not exceed 2000.
  5. 1 <= rects.length <= 100
  6. pick return a point as an array of integer coordinates [p_x, p_y]
  7. pick is called at most 10000 times.

Example 1:

Input: 
["Solution","pick","pick","pick"]
[[[[1,1,5,5]]],[],[],[]]
Output: 
[null,[4,1],[4,1],[3,3]]

Example 2:

Input: 
["Solution","pick","pick","pick","pick","pick"]
[[[[-2,-2,-1,-1],[1,0,3,0]]],[],[],[],[],[]]
Output: 
[null,[-1,-2],[2,0],[-2,-1],[3,0],[-2,-2]]

Explanation of Input Syntax:

The input is two lists: the subroutines called and their arguments. Solution's constructor has one argument, the array of rectangles rectspick has no arguments. Arguments are always wrapped with a list, even if there aren't any.

這道題給了我們一些非重疊的矩形,讓我們返回一個這些矩形中的一個隨機的點。那麼博主的第一直覺就是首先要在這些矩形中隨機挑出來一個,然後在這個隨機的矩形中再隨機生成一個點,通過隨機生成一個長和寬即可。博主最開始想到的方法是用rand隨機生成一個 [0, n) 範圍內的數字,n為輸入矩形的個數,這樣就得到了一個隨機的矩形。但是這種方法貌似行不通,會跪在一個很長的輸入測試資料。這使得博主比較困惑了,沒有想出原因是為何,有哪位看官大神們知道的,麻煩留言告知博主哈!哈,已經知道了,參見評論區二樓留言~ 論壇上的解法有一種是用水塘抽樣Reservoir Sampling的方法的,LeetCode之前有過幾道需要用這種方法的題目Random Pick IndexShuffle an ArrayLinked List Random Node。這裡我們使用其來隨機出一個矩形,做法是遍歷所有的矩形,用變數sumArea來計算當前遍歷過的所有矩形面積之和,然後變數area是當前遍歷的矩形的面積,然後我們在當前所有矩形面積之和內隨機生成一個值,如果這個值小於area,那麼選擇當前的矩陣為隨機矩形。這裡相當於一個大小為area的水塘,在這個值之內的話,就更換selected。這個方法是沒啥問題,但是博主還是沒想通為啥不能直接隨機生成矩形的index。當我們拿到隨機矩形後,之後就隨機出寬和高返回即可,參見程式碼如下:

解法一:

class Solution {
public:
    Solution(vector<vector<int>> rects) {
        _rects = rects;
    }
    
    vector<int> pick() {
        int sumArea = 0;
        vector<int> selected;
        for (auto rect : _rects) {
            int area = (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
            sumArea += area;
            if (rand() % sumArea < area) selected = rect;
        }
        int x = rand() % (selected[2] - selected[0] + 1) + selected[0];
        int y = rand() % (selected[3] - selected[1] + 1) + selected[1];
        return {x, y};
    }

private:
    vector<vector<int>> _rects;
};

這道題在論壇上的主流解法其實是這個,我們用TreeMap來建立當前遍歷過的矩形面積之和跟該矩形位置之間的對映。然後當我們求出所有的矩形面積之和後,我們隨機生成一個值,然後在TreeMap中找到第一個大於這個值的矩形,這裡博主還是有疑問,為啥不能直接隨機矩形的位置,而是非要跟面積扯上關係。之後的步驟就跟上面的沒啥區別了,參見程式碼如下:

解法二:

class Solution {
public:
    Solution(vector<vector<int>> rects) {
        _rects = rects;
        _totalArea = 0;
        for (auto rect : rects) {
            _totalArea += (rect[2] - rect[0] + 1) * (rect[3] - rect[1] + 1);
            _areaToIdx.insert({_totalArea, _areaToIdx.size()});
        }
    }
    
    vector<int> pick() {
        int val = rand() % _totalArea;
        int idx = _areaToIdx.upper_bound(val)->second;
        int width = _rects[idx][2] - _rects[idx][0] + 1;
        int height = _rects[idx][3] - _rects[idx][1] + 1;
        return {rand() % width + _rects[idx][0], rand() % height + _rects[idx][1]};
    }

private:
    vector<vector<int>> _rects;
    int _totalArea;
    map<int, int> _areaToIdx;
};

類似題目:

參考資料: