Image Overlap
MedPrompt
You are given two images, img1 and img2, represented as binary, square matrices of size n x n. A binary matrix has only 0s and 1s as values.
We translate one image however we choose by sliding all the 1 bits left, right, up, and/or down any number of units. We then place it on top of the other image. We can then calculate the overlap by counting the number of positions that have a 1 in both images.
Note also that a translation does not include any kind of rotation. Any 1 bits that are translated outside of the matrix borders are erased.
Return the largest possible overlap.
Example 1:
Input: img1 = [[1,1,0],[0,1,0],[0,1,0]], img2 = [[0,0,0],[0,1,1],[0,0,1]]
Output: 3
Explanation: We translate img1 to right by 1 unit and down by 1 unit.
The number of positions that have a 1 in both images is 3 (shown in red).Example 2:
Input: img1 = [[1]], img2 = [[1]]
Output: 1Example 3:
Input: img1 = [[0]], img2 = [[0]]
Output: 0
Constraints:
n == img1.length == img1[i].lengthn == img2.length == img2[i].length1 <= n <= 30img1[i][j]is either0or1.img2[i][j]is either0or1.
Approaches
3 approaches with complexity analysis and trade-offs.
This is the most straightforward approach. We can simulate every possible translation of img1 and, for each translation, count the number of overlapping 1s with img2. The maximum count found among all translations is the answer.
Algorithm
- Initialize
maxOverlap = 0. - Get the dimension
nof the images. - Loop for vertical shift
dyfrom-(n-1)ton-1. - Loop for horizontal shift
dxfrom-(n-1)ton-1.- Initialize
currentOverlap = 0. - Loop for row
rfrom0ton-1. - Loop for column
cfrom0ton-1.- Calculate the translated coordinates in
img2:tr = r + dyandtc = c + dx. - Check if
(tr, tc)is within the bounds of then x ngrid. - If it is, and both
img1[r][c]andimg2[tr][tc]are1, incrementcurrentOverlap.
- Calculate the translated coordinates in
- Update
maxOverlap = max(maxOverlap, currentOverlap).
- Initialize
- Return
maxOverlap.
Walkthrough
We consider all possible relative translations between the two images. A translation can be represented by a vector (dx, dy), where dx is the horizontal shift and dy is the vertical shift.
To ensure we cover all possibilities of overlap, the shifts dx and dy must range from -(n-1) to n-1, where n is the dimension of the matrix. This gives (2n-1) * (2n-1) possible translations.
For each translation (dx, dy):
- We initialize a counter for the current overlap to zero.
- We iterate through every cell
(r, c)ofimg1. - We calculate the corresponding cell's coordinates
(r + dy, c + dx)inimg2's grid. - If this translated coordinate is within the bounds of
img2(i.e.,0 <= r + dy < nand0 <= c + dx < n), and bothimg1[r][c]andimg2[r + dy][c + dx]are1, we increment the overlap counter.
We keep track of the maximum overlap found across all translations and return it.
class Solution { public int largestOverlap(int[][] img1, int[][] img2) { int n = img1.length; int maxOverlap = 0; for (int dy = -n + 1; dy < n; dy++) { for (int dx = -n + 1; dx < n; dx++) { maxOverlap = Math.max(maxOverlap, countOverlap(img1, img2, dx, dy)); } } return maxOverlap; } private int countOverlap(int[][] img1, int[][] img2, int dx, int dy) { int n = img1.length; int count = 0; for (int r = 0; r < n; r++) { for (int c = 0; c < n; c++) { int tr = r + dy; int tc = c + dx; if (tr >= 0 && tr < n && tc >= 0 && tc < n) { if (img1[r][c] == 1 && img2[tr][tc] == 1) { count++; } } } } return count; }}Complexity
Time
O(n^4). There are `O(n^2)` possible translations (`(2n-1) * (2n-1)`). For each translation, we iterate through the `n x n` grid to count the overlap, which takes `O(n^2)` time.
Space
O(1). We only use a few variables to store the shifts and counts, so the space is constant.
Trade-offs
Pros
Simple to understand and implement.
Requires minimal extra space.
Cons
Very inefficient, especially for larger
n.Performs many redundant checks on cells that contain
0s and cannot contribute to the overlap.
The brute-force approach is inefficient because it iterates over all n x n cells for every shift, regardless of whether they contain 1s or 0s. We can optimize this by only considering the cells that contain 1s, as these are the only ones that can contribute to the overlap.
Algorithm
- Create a list
ones1to store coordinates of1s inimg1. - Create a list
ones2to store coordinates of1s inimg2. - Populate
ones1andones2by iterating through the input matrices. - If either list is empty, return
0. - Initialize a hash map
vectorCountsto store(translation_vector -> count). - Initialize
maxOverlap = 0. - For each point
p1 = (r1, c1)inones1:- For each point
p2 = (r2, c2)inones2:- Calculate the vector
v = (r2 - r1, c2 - c1). - Increment the count for
vinvectorCounts. - Update
maxOverlapwith the new count forv.
- Calculate the vector
- For each point
- Return
maxOverlap.
Walkthrough
The core idea is to reframe the problem. Instead of trying every possible shift, we consider pairs of 1s, one from img1 and one from img2, and determine the translation vector that would make them overlap.
First, we iterate through both img1 and img2 to create lists of coordinates for all the 1s. Let's call these ones1 and ones2.
Then, we iterate through every pair of points (p1, p2) where p1 is from ones1 and p2 is from ones2.
For each pair p1 = (r1, c1) and p2 = (r2, c2), we calculate the translation vector (dr, dc) = (r2 - r1, c2 - c1) that would align p1 with p2.
We use a hash map to store the frequency of each translation vector. The key can be a string representation of the vector (e.g., "dr,dc") and the value is its count.
After counting the occurrences of all possible translation vectors, the maximum value in the hash map represents the largest number of 1s that can be aligned simultaneously, which is the largest overlap. If either image has no 1s, the overlap is 0.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; class Solution { public int largestOverlap(int[][] img1, int[][] img2) { int n = img1.length; List<int[]> ones1 = new ArrayList<>(); List<int[]> ones2 = new ArrayList<>(); for (int r = 0; r < n; r++) { for (int c = 0; c < n; c++) { if (img1[r][c] == 1) { ones1.add(new int[]{r, c}); } if (img2[r][c] == 1) { ones2.add(new int[]{r, c}); } } } if (ones1.isEmpty() || ones2.isEmpty()) { return 0; } Map<String, Integer> vectorCounts = new HashMap<>(); int maxOverlap = 0; for (int[] p1 : ones1) { for (int[] p2 : ones2) { String vector = (p2[0] - p1[0]) + "," + (p2[1] - p1[1]); vectorCounts.put(vector, vectorCounts.getOrDefault(vector, 0) + 1); maxOverlap = Math.max(maxOverlap, vectorCounts.get(vector)); } } return maxOverlap; }}Complexity
Time
O(n^2 + N1 * N2), where `N1` and `N2` are the number of `1`s in `img1` and `img2` respectively. The `O(n^2)` part is for finding the coordinates of the `1`s. The `O(N1 * N2)` part is for iterating through all pairs of `1`s. In the worst case, `N1` and `N2` can be `O(n^2)`, leading to `O(n^4)` complexity.
Space
O(N1 + N2). We need space to store the lists of coordinates and the hash map. `N1` and `N2` are the number of `1`s in each image. The map can store up to `min(N1 * N2, (2n-1)^2)` distinct vectors. In the worst case, this is `O(n^2)`.
Trade-offs
Pros
Much more efficient than brute-force for sparse matrices (matrices with few
1s).Avoids unnecessary computations involving zero-valued cells.
Cons
The worst-case time complexity is still
O(n^4)if the matrices are dense (full of1s).The space complexity can be high in the worst case, up to
O(n^2).
Given the constraint n <= 30, we can represent each row of the n x n matrix as a single 32-bit integer. This allows us to use fast bitwise operations to calculate the overlap for each row, leading to a more efficient solution with a better worst-case time complexity.
Algorithm
- Get the dimension
n. - Create two integer arrays,
b1andb2, of sizen. - For each row
rfrom0ton-1:- Convert
img1[r]andimg2[r]to integers and store them inb1[r]andb2[r].
- Convert
- Initialize
maxOverlap = 0. - Loop for
dyfrom-(n-1)ton-1. - Loop for
dxfrom-(n-1)ton-1.- Initialize
currentOverlap = 0. - Loop for row
r2from0ton-1.- Calculate the corresponding
img1row indexr1 = r2 - dy. - If
r1is valid (i.e.,0 <= r1 < n):- Get the integer for
img1's row:row1_val = b1[r1]. - Apply horizontal shift
dx:shifted_row1 = (dx >= 0) ? (row1_val >> dx) : (row1_val << -dx). - Get the integer for
img2's row:row2_val = b2[r2]. - Calculate overlap for this row pair:
Integer.bitCount(shifted_row1 & row2_val). - Add this to
currentOverlap.
- Get the integer for
- Calculate the corresponding
- Update
maxOverlap = max(maxOverlap, currentOverlap).
- Initialize
- Return
maxOverlap.
Walkthrough
First, we transform the input matrices img1 and img2 into arrays of integers. For each matrix, we create an array of size n, where the i-th element stores the integer representation of the i-th row. This is done by iterating through the row and using bit shifting. For example, row_val = (row_val << 1) | bit.
After this preprocessing, we iterate through all possible translations (dx, dy), similar to the brute-force approach.
For a given translation (dx, dy), we calculate the total overlap. We iterate through the rows of img2 (from r2 = 0 to n-1). The corresponding row in img1 is r1 = r2 - dy.
If r1 is a valid row index for img1, we take the integer representation of that row, b1[r1]. We then apply the horizontal shift dx to this integer using bitwise shift operators (>> for right shift, << for left shift).
The overlap for this pair of rows is the number of common 1s after shifting. This can be calculated by performing a bitwise AND between the shifted img1 row and the img2 row (shifted_b1_row & b2[r2]) and then counting the number of set bits in the result. Java's Integer.bitCount() is perfect for this.
We sum these row-wise overlaps to get the total overlap for the translation (dx, dy) and return the maximum found.
class Solution { public int largestOverlap(int[][] img1, int[][] img2) { int n = img1.length; int[] b1 = new int[n]; int[] b2 = new int[n]; for (int r = 0; r < n; r++) { for (int c = 0; c < n; c++) { b1[r] = (b1[r] << 1) | img1[r][c]; b2[r] = (b2[r] << 1) | img2[r][c]; } } int maxOverlap = 0; for (int dy = -n + 1; dy < n; dy++) { for (int dx = -n + 1; dx < n; dx++) { int currentOverlap = 0; for (int r2 = 0; r2 < n; r2++) { int r1 = r2 - dy; if (r1 >= 0 && r1 < n) { int shifted_b1_row; if (dx >= 0) { shifted_b1_row = b1[r1] >> dx; } else { shifted_b1_row = b1[r1] << (-dx); } currentOverlap += Integer.bitCount(shifted_b1_row & b2[r2]); } } maxOverlap = Math.max(maxOverlap, currentOverlap); } } return maxOverlap; }}Complexity
Time
O(n^3). The initial data transformation takes `O(n^2)`. The main part consists of two loops for shifts `(dx, dy)` giving `O(n^2)` translations, and for each, an inner loop over `n` rows. The operations inside the innermost loop are constant time.
Space
O(n). We use two arrays of size `n` to store the integer representations of the rows.
Trade-offs
Pros
Significantly faster worst-case performance than the other approaches.
Cleverly uses hardware-level bit operations for efficient computation.
Cons
The logic can be slightly more complex to grasp than the straightforward brute-force method.
It's only applicable because
nis small enough (n <= 30) to fit a row into a standard integer type (32-bit).
Solutions
Solution
class Solution {public int largestOverlap(int[][] img1, int[][] img2) { int n = img1.length; Map<List<Integer>, Integer> cnt = new HashMap<>(); int ans = 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (img1[i][j] == 1) { for (int h = 0; h < n; ++h) { for (int k = 0; k < n; ++k) { if (img2[h][k] == 1) { List<Integer> t = List.of(i - h, j - k); ans = Math.max(ans, cnt.merge(t, 1, Integer : : sum)); } } } } } } return ans; }}class Solution {public: int largestOverlap(vector<vector<int>> &img1, vector<vector<int>> &img2) { int n = img1.size(); map<pair<int, int>, int> cnt; int ans = 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (img1[i][j]) { for (int h = 0; h < n; ++h) { for (int k = 0; k < n; ++k) { if (img2[h][k]) { ans = max(ans, ++cnt[{i - h, j - k}]); } } } } } } return ans; }};class Solution: def largestOverlap(self, img1: List[List[int]], img2: List[List[int]]) -> int: n = len(img1) cnt = Counter() for i in range(n): for j in range(n): if img1[i][j]: for h in range(n): for k in range(n): if img2[h][k]: cnt[(i - h, j - k)] += 1 return max(cnt . values()) if cnt else 0Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.