Two images A
and B
are given, represented as binary, square matrices of the same size. (A binary matrix has only 0s and 1s as values.)
We translate one image however we choose (sliding it left, right, up, or down any number of units), and place it on top of the other image. After, the overlap of this translation is the number of positions that have a 1 in both images.
(Note also that a translation does not include any kind of rotation.)
What is the largest possible overlap?
Example 1:
Input: A = [[1,1,0], [0,1,0], [0,1,0]] B = [[0,0,0], [0,1,1], [0,0,1]] Output: 3 Explanation: We slide A to right by 1 unit and down by 1 unit.
Notes:
1 <= A.length = A[0].length = B.length = B[0].length <= 30
0 <= A[i][j], B[i][j] <= 1
這道題給了我們兩個用大小相同的二維數組表示的圖像,里面只有0或1,問我們經過任意平移后,能產生的最大重疊是多少,這里只計算值為1的重疊。給的例子中,我們只要將圖像A向右和向下平移一位,就能得到3個重疊。那么首先來思考 brute force 的方法,對於一個 nxn 大小的數組,其實其能平移的情況是有限的,水平和豎直方向分別有n種移動方式,那么總共有 nxn 種移動方法,那么我們只要對於每種移動方式后,都計算一下重疊的個數,那么就一定可以找出最大值來。需要注意的是,A和B分別都需要移動 nxn 次,我們可以使用一個子函數來專門統計重疊個數,需要傳入橫向縱向的平移量 rowOffset 和 colOffset,那么只需讓其中一個數組減去偏移量后跟另一個數組對應位置的值相乘,由於只有0和1,若相乘為1的話,就說明有重疊,直接累加即可,參見代碼如下:
解法一:
class Solution { public: int largestOverlap(vector<vector<int>>& A, vector<vector<int>>& B) { int res = 0, n = A.size(); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { res = max(res, max(count(A, B, i, j), count(B, A, i, j))); } } return res; } int count(vector<vector<int>>& A, vector<vector<int>>& B, int rowOffset, int colOffset) { int sum = 0, n = A.size(); for (int i = rowOffset; i < n; ++i) { for (int j = colOffset; j < n; ++j) { sum += A[i][j] * B[i - rowOffset][j - colOffset]; } } return sum; } };
我們還可以換一種思路,由於只有值為1的地方才有可能重疊,所以我們只關心A和B中值為1的地方,將其坐標位置分別存入兩個數組 listA 和 listB 中。由於對於A和B中的任意兩個1的位置,肯定有一種方法能將A平移到B,平移的方法就是橫向平移其橫坐標之差,豎向平移其縱坐標之差。由於其是一一對應關系,所以只要是橫縱坐標差相同的兩對兒位置,一定是在同一次平移上。那么我們就需要一個 HashMap 來建立坐標差值和其出現次數之間的映射,為了降維,將橫縱坐標之差轉為字符串,然后中加上個橫杠分隔開,這樣只要組成了相同的字符串,那么一定就是在同一個平移上,計數器自增1。最后在 HashMap 中找到最大的值即可,參見代碼如下:
解法二:
class Solution { public: int largestOverlap(vector<vector<int>>& A, vector<vector<int>>& B) { int res = 0, n = A.size(); vector<vector<int>> listA, listB; unordered_map<string, int> diffCnt; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (A[i][j] == 1) listA.push_back({i, j}); if (B[i][j] == 1) listB.push_back({i, j}); } } for (auto a : listA) { for (auto b : listB) { ++diffCnt[to_string(a[0] - b[0]) + "-" + to_string(a[1] - b[1])]; } } for (auto diff : diffCnt) { res = max(res, diff.second); } return res; } };
我們可以優化一下空間,可以將二維坐標加碼成一個數字,一般的做法都是將 (i, j) 變成 i*n + j,但是這道題卻不行,因為我們算橫縱坐標的差值時想直接相減,這種加碼方式會使得橫縱坐標之間互相干擾。由於題目中給了n的范圍,不會超過 30,所以我們可以給橫坐標乘以 100,再加上縱坐標,即 i*100 + j,這種加碼方式萬無一失。然后還是要用 HashMap 來建立坐標差值和其出現次數之間的映射,不過這次就簡單多了,不用轉字符串了,直接用數字相減即可,最后返回 HashMap 中最大的統計數,參見代碼如下:
解法三:
class Solution { public: int largestOverlap(vector<vector<int>>& A, vector<vector<int>>& B) { int res = 0, n = A.size(); vector<int> listA, listB; unordered_map<int, int> diffCnt; for (int i = 0; i < n * n; ++i) { if (A[i / n][i % n] == 1) listA.push_back(i / n * 100 + i % n); if (B[i / n][i % n] == 1) listB.push_back(i / n * 100 + i % n); } for (int a : listA) { for (int b : listB) { ++diffCnt[a - b]; } } for (auto diff : diffCnt) { res = max(res, diff.second); } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/835
參考資料:
https://leetcode.com/problems/image-overlap/
https://leetcode.com/problems/image-overlap/discuss/177485/Java-Easy-Logic
https://leetcode.com/problems/image-overlap/discuss/130623/C%2B%2BJavaPython-Straight-Forward
https://leetcode.com/problems/image-overlap/discuss/138976/A-generic-and-easy-to-understand-method