問題描述
有一個 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;
}