1. 程式人生 > 其它 >6 最小覆蓋矩形(Smallest Rectangle Enclosing Black Pixels)

6 最小覆蓋矩形(Smallest Rectangle Enclosing Black Pixels)

目錄

1 題目

  最小覆蓋矩形(Smallest Rectangle Enclosing Black Pixels)

lintcode:題號——600,難度——hard

2 描述

  一個由二進位制矩陣表示的圖,0 表示白色畫素點,1 表示黑色畫素點。黑色畫素點是聯通的,即只有一塊黑色區域。畫素是水平和豎直連線的,給一個黑色畫素點的座標 (x, y) ,返回囊括所有黑色畫素點的矩陣的最小面積。

  樣例1:

輸入:["0010","0110","0100"],x=0,y=2
輸出:6
解釋:

矩陣左上角座標是(0, 1), 右下角的座標是(2, 2)

  樣例2:

輸入:["1110","1100","0000","0000"], x = 0, y = 1
輸出:6
解釋:

矩陣左上角座標是(0, 0), 右下角座標是(1, 3)

3 思路

  從答案反向找思路,要得到面積,需要橫向跨度和縱向跨度,橫向需要分別找到最左邊和最右邊,縱向需要分別找到最上面和最下面,題中給出了一個種子點,即其中一個黑子所在的位置,從種子點分別向上、下、左、右四個方向看,例如向左的情況,需要找到最左端的黑子,而最左端的黑子不一定與種子點在同一行,所以我們要考慮整個左半矩陣圖,由於只要找到最左端的橫向座標即可,不用關心列座標,可以將左半矩陣圖縱向壓縮排同一行,這樣只要該列存在任何黑子,則將壓縮後形成的點標記為黑,因為所有的黑子是聯通的,最後向左看的情況一定會形成“oooxxx”形式的數列,使用二分法即可找到最左端邊緣。
  按照相同的思路可以找到其他三個方向的邊緣,然後得到橫縱跨度,再得到面積。

  1. 考慮左半矩陣,以每列是否存在黑子為條件,二分查詢最左邊緣;
  2. 分別得到四個方向的邊緣;
  3. 計算橫縱跨度,得到面積。

  擴充套件。

其實題目中給出的種子點有點奇怪,按照常理來判斷給不給這個種子點的位置,都是能得到答案的,題中直接給出了,簡化了這一步。題目也可以使用廣度優先搜尋來做,可以思考一下。

3.1 圖解

輸入:["0000100","0111100","0001100","0011110","0000000"],
x = 2, y = 4
輸出:20

graph TD X[0表示白子,1和X表示黑子,X為種子點<br/>'0, 0, 0, 0, 1, 0, 0'<br/>'0, 1, 1, 1, 1, 0, 0'<br/>'0, 0, 0, 1, X, 0, 0'<br/>'0, 0, 1, 1, 1, 1, 0'<br/>'0, 0, 0, 0, 0, 0, 0'] X -- 左半矩陣 --> A['0, 0, 0, 0, 1'<br/>'0, 1, 1, 1, 1'<br/>'0, 0, 0, 1, X'<br/>'0, 0, 1, 1, 1'<br/>'0, 0, 0, 0, 0'] X -- 上半矩陣 --> B['0, 0, 0, 0, 1, 0, 0'<br/>'0, 1, 1, 1, 1, 0, 0'<br/>'0, 0, 0, 1, X, 0, 0'] X -- 右半矩陣 --> C['1, 0, 0'<br/>'1, 0, 0'<br/>'X, 0, 0'<br/>'1, 1, 0'<br/>'0, 0, 0'] X -- 下半矩陣 --> D['0, 0, 0, 1, X, 0, 0'<br/>'0, 0, 1, 1, 1, 1, 0'<br/>'0, 0, 0, 0, 0, 0, 0'] A --> A0[列中出現一個黑子1,則壓縮後的點為1] A0 -- 分列 --> A1[0<br/>0<br/>0<br/>0<br/>0] A0 -- 分列 --> A2[0<br/>1<br/>0<br/>0<br/>0] A0 -- 分列 --> A3[0<br/>1<br/>0<br/>1<br/>0] A0 -- 分列 --> A4[0<br/>1<br/>1<br/>1<br/>0] A0 -- 分列 --> A5[1<br/>1<br/>X<br/>1<br/>0] A1 -- 壓縮 --> A11[0] A2 -- 壓縮 --> A21[1] A3 -- 壓縮 --> A31[1] A4 -- 壓縮 --> A41[1] A5 -- 壓縮 --> A51[1] A11 --> AA['0, 1, 1, 1, 1'] A21 --> AA A31 --> AA A41 --> AA A51 --> AA AA -- 二分查詢得到左邊緣<br/>下標都是指在整體矩陣中的下標 --> AAA[下標1] B -- 分行,壓縮 --> BB['1'<br/>'1'<br/>'1'] BB -- 上邊緣 --> BBB[下標0] C -- 分列,壓縮 --> CC['1, 1, 0'] CC -- 右邊緣 --> CCC[下標5] D -- 分行,壓縮 --> DD['1'<br/>'1'<br/>'0'] DD -- 下邊緣 --> DDD[下標3] AAA --> AC[跨度5] CCC --> AC BBB --> BD[跨度4] DDD --> BD AC --> R[面積20] BD --> R

3.2 時間複雜度

  演算法在每一個方向使用一次二分法,假定矩陣圖有m行n列,相同維度的二分法耗時可以整體來算,橫向上二分法操作的時間複雜度為O(log n),縱向上二分法操作的時間複雜度為O(log m)
  橫向的二分法每次判斷都包含一次對列的遍歷操作的時間複雜度O(m),縱向的二分法每次判斷都包含一次對行的遍歷的時間複雜度O(m)
  橫向二分法總耗時O(m * log n),縱向二分法總耗時O(n * log m)
  演算法的時間複雜度為O(m * log n + n * log m),其中m為矩陣圖的行數,n為矩陣圖的列數。

3.3 空間複雜度

  演算法的空間複雜度為O(1)

4 原始碼

  注意事項:

  1. 題目給出的種子點座標(x, y),x表示二維陣列的行序號,y表示二維陣列的列序號,所以x是縱向的距離,y是橫向的距離,這點和直角座標系的x方向和y方向剛好相反,很容易想歪,思路要清晰;
  2. 求面積的時候,注意一個點代表一距離,不是一條邊代表一距離。

  C++版本:

/**
 * @param image: 代表包含'0'和'1'為元素的二進位制矩陣圖
 * @param x: 初始黑點的縱座標
 * @param y: 初始黑點的橫座標
 * @return: 最小覆蓋矩形的面積
 */
int minArea(vector<vector<char>> &image, int x, int y) {
    // write your code here
    if (image.empty() || image.front().empty()) // 橫縱都不能為空
    {
        return 0;
    }

    int maxRow = image.size() - 1;
    int maxColumn = image.front().size() - 1;

    // 計算四個方向最邊緣點的下標
    int left = calLeftEdge(image, 0, y);
    int right = calRightEdge(image, y, maxColumn);
    int top = calTopEdge(image, 0, x);
    int bottom = calBottomEdge(image, x, maxRow);

    return (right - left + 1 ) * (bottom - top + 1);
}

// 計算最左邊緣黑點的橫座標
int calLeftEdge(vector<vector<char>> & image, int start, int end)
{
    int mid = 0;
    while (start + 1 < end)
    {
        mid = start + (end - start) / 2;
        if (isColWhite(image, mid))
        {
            start = mid;
        }
        else
        {
            end = mid;
        }
    }

    if (isColWhite(image, start))
    {
        return end;
    }
    else
    {
        return start;
    }
}

// 計算最右邊緣黑點的橫座標
int calRightEdge(vector<vector<char>> & image, int start, int end)
{
    int mid = 0;
    while (start + 1 < end)
    {
        mid = start + (end - start) / 2;
        if (isColWhite(image, mid))
        {
            end = mid;
        }
        else
        {
            start = mid;
        }
    }

    if (isColWhite(image, end))
    {
        return start;
    }
    else
    {
        return end;
    }
}

// 計算最上邊緣黑點的縱座標
int calTopEdge(vector<vector<char>> & image, int start, int end)
{
    int mid = 0;
    while (start + 1 < end)
    {
        mid = start + (end - start) / 2;
        if (isRowWhite(image, mid))
        {
            start = mid;
        }
        else
        {
            end = mid;
        }
    }

    if (isRowWhite(image, start))
    {
        return end;
    }
    else
    {
        return start;
    }
}

// 計算最下邊緣黑點的縱座標
int calBottomEdge(vector<vector<char>> & image, int start, int end)
{
    int mid = 0;
    while (start + 1 < end)
    {
        mid = start + (end - start) / 2;
        if (isRowWhite(image, mid))
        {
            end = mid;
        }
        else
        {
            start = mid;
        }
    }

    if (isRowWhite(image, end))
    {
        return start;
    }
    else
    {
        return end;
    }
}

// 判斷行中是否全為白點
bool isRowWhite(vector<vector<char>> & image, int row)
{
    for (int i = 0; i < image.front().size(); i++)
    {
        if (image.at(row).at(i) == '1')
        {
            return false;
        }
    }

    return true;
}

// 判斷列中是否全為白點
bool isColWhite(vector<vector<char>> & image, int col)
{
    for (int i = 0; i < image.size(); i++)
    {
        if (image.at(i).at(col) == '1')
        {
            return false;
        }
    }

    return true;
}