矩陣中求子矩形

問題描述

有一個 n * n的矩形,其中每個元素只可能是0 or 1。比如如下矩陣A:

1 1 0 0
0 0 1 1
1 0 1 0
1 1 0 1

其中 A[0][0], A[0][1], A[3][0], A[3][1] 這四個元素恰好是一個長方形的四個角,且每個元素都是1. 因此稱這四個元素圍成了一個子矩形。

現在需要判斷,給定的矩陣A中,判斷是否存在這樣四個元素,都是1,且恰好圍成一個子矩形?

解決方案

暴力法

顯然,只需要枚舉 top-left,還有 bottom-right 就可以確定出這四個元素了。top-left [i1, j1], bottom-right [i2, j2]。僞代碼如下:

for i1 <- 0 to n-1
    for j1 <- 0 to n-1
        for i2 <- i1 + 1 to n-1
            for j2 <- j2 + 1 to n-1
                check_are_all_one(A[i1][j1], A[i1][j2], A[i2][j1], A[i2][j2])

T(n) = O(n^4)

改進方案1

可以只需要枚舉 j1, j2, 然後 i 從 0 ~ n-1 掃描一遍就行。

for j1 <- 0 to n-1
    for j2 <- j1+1 to n-1
        one_pair_count <- 0
        for i <- 0 to n-1
            if A[i][j1] & A[i][j2] then
                one_pair_count <- one_pair_count + 1
        if one_pair_count >= 2 then // 只需要出現過兩次A[i][j1], A[i][j2]都是1,就可以由他們圍成一個矩形
            return true
return false

時間複雜度爲 T(n) = O(n^3)

改進方案2

這個方案可以將複雜度改進爲 O(n^2),但是需要引入輔助空間,mark[n][n]
A矩陣:

1 1 0 0
0 0 1 1
1 0 1 0
1 1 0 1

從A的第一行開始往下掃,
Row 1: 1 1 0 0
這裏意味着第一列(column 1,簡稱C1) 和第二列(column 2,簡稱C2)可以構成一個待求矩形的上邊。對應的mark表爲:

    C1  C2  C3  C4
C1  0   1   0   0
C2  1   0   0   0
C3  0   0   0   0
C4  0   0   0   0

如果Row1 是: 1 1 0 1,那麼意味着 C1 和 C2,C2 和 C3,C1 和 C3都可以構成矩形的上邊。此時對應的mark表爲:

    C1  C2  C3  C4
C1  0   1   0   1
C2  1   0   0   1
C3  0   0   0   0
C4  1   1   0   0

掃描第二行,Row2:0 0 1 1,C3, C4爲1,更新 mark[3-1][4-1]=1, mark[4-1][3-1]=1
mark表更新爲:

    C1  C2  C3  C4
C1  0   1   0   0
C2  1   0   0   0
C3  0   0   0   1
C4  0   0   1   0

掃描第三行:Row3: 1 0 1 0, C1,C3爲1,更新 mark[1-1][3-1]=1, mark[3-1][1-1]=1
mark表更新爲:

    C1  C2  C3  C4
C1  0   1   1   0
C2  1   0   0   0
C3  1   0   0   1
C4  0   0   1   0

掃描第四行,Row4:1 1 0 1,C1,C2,C4爲1,更新mark[1-1][2-1]=1, mark[2-1][1-1]=1, mark[1-1][4-1]=1, mark[4-1][1-1]=1, mark[2-1][4-1]=1, mark[4-1][2-1]=1
mark表更新爲

    C1  C2  C3  C4
C1  0   2   0   1
C2  2   0   0   1
C3  0   0   0   0
C4  1   1   0   0

此時發現 mark[0][1] = 2,即意味着第一列和第二列有兩次掃描中,都匹配上了,這樣的話,選擇第一列和第二列作爲矩形的左右列即可,然後從上往下掃一次(O(n)時間),即可求得矩形上下邊界

總的時間複雜度分析:

  • (1)總共掃描 n 行,每行都需要從中獲取數值爲1的列,這裏的複雜度爲 T1(n) = n * O(n) = O(n^2).
  • (2)更新mark表,雖然每行獲取的列需要兩兩配對,更新mark表,好像總的更新次數不太確定。其實,mark表可以斷定,總的更新次數不會超過n^2,一旦出現某個值 >= 2,即可以得出結論,不需要再更新mark。因此T2(n) = O(n^2)
  • (3)通過(2)當求得左右列邊界的時候,再逐行掃一次,即可確定上下邊界。

代碼如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>

using namespace std;

class Solution {
public:
    void solve() {
        int m[][4] = {
            {1,1,0,0},
            {0,0,1,1},
            {1,0,1,0},
            {1,1,0,1}
        };
        vector<vector<int> > matrix;
        for (int i = 0; i < 4; i++) {
            vector<int> row;
            for (int j = 0; j < 4; j++) row.push_back(m[i][j]);
            matrix.push_back(row);
        }
        cout << "check: " << checkHasRectangle(matrix) << endl;
    }
    bool checkHasRectangle(vector<vector<int> > &matrix) {
        int n = matrix.size(); // n*n matrix
        vector<vector<int> > mark(n, vector<int>(n, 0)); // mark 2D array

        int target_c1 = -1, target_c2 = -1; // target column c1, column c2 boundary
        int target_r1 = -1, target_r2 = -1; // target row r1, row r2 boundary
        for (int i = 0; i < n; i++) {
            vector<int> hold; // to hold the column index whose value at this row is 1
            for (int j = 0; j < n; j++) if (matrix[i][j]) hold.push_back(j);
            for (int j1 = 0; j1 < hold.size(); j1++) {
                for (int j2 = 0; j2 < hold.size(); j2++) {
                    if (j1 != j2) mark[hold[j1]][hold[j2]] ++;
                    // get the result 
                    if (mark[hold[j1]][hold[j2]] >= 2) {
                        target_c1 = hold[j1], target_c2 = hold[j2];
                        // go to find up and bottom boundary. 
                        for (int k = 0; k < n; k++) {
                            if (matrix[k][target_c1] & matrix[k][target_c2]) {
                                if (target_r1 == -1) target_r1 = k;
                                else target_r2 = k;
                            }
                        }
                        // cout 
                        printf("Four elements: (%d,%d), (%d,%d), (%d,%d), (%d,%d)\n", 
                            target_r1, target_c1, target_r1, target_c2, 
                            target_r2, target_c1, target_r2, target_c2);
                        return true;
                    }
                }
            }
        }
        return false;
    }
};

int main() {
    Solution solution;
    solution.solve();
    return 0;
}
發佈了118 篇原創文章 · 獲贊 17 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章