Facebook Pixel

835. Image Overlap

MediumArrayMatrix
Leetcode Link

Problem Description

You have two binary square matrices img1 and img2, both of size n x n, containing only 0s and 1s.

The task is to find the maximum overlap between these two images when you translate (slide) one image over the other. Translation means you can move one image left, right, up, or down by any number of units, then place it on top of the other image. The overlap is calculated by counting how many positions have 1 in both images at the same location after the translation.

Key points to understand:

  • You can only translate (slide) the image - no rotation is allowed
  • Any 1 bits that move outside the matrix boundaries during translation are discarded
  • The overlap count is the number of positions where both images have 1 after one is translated
  • You need to find the maximum possible overlap value

For example, if you have two 3x3 matrices and you slide one of them, you count how many 1s align with 1s in the other matrix. You try different translations and return the highest overlap count you can achieve.

The solution works by considering every possible translation offset (dx, dy) between pairs of 1s in both images. For each 1 at position (i, j) in img1 and each 1 at position (h, k) in img2, the offset (i - h, j - k) represents how much we need to translate img2 to align these two 1s. By counting occurrences of each offset using a hash table, the most frequent offset gives us the maximum overlap.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about what happens when we translate one image to achieve an overlap. When two 1s from different images align, they must have been moved by the same translation offset.

If a 1 at position (i, j) in img1 aligns with a 1 at position (h, k) in img2, it means we've translated img2 by offset (i - h, j - k). The key insight is that all pairs of 1s that align under the same translation will have the exact same offset.

For example, if translating img2 by offset (2, -1) causes three different pairs of 1s to align, then all three pairs will have the offset (2, -1) when we calculate (i - h, j - k) for each pair.

This observation leads us to the solution strategy: instead of trying every possible translation (which would be expensive), we can:

  1. Look at every possible pair of 1s between the two images
  2. Calculate what translation offset would align each pair
  3. Count how many pairs share the same offset

The offset that appears most frequently tells us the translation that creates the maximum number of aligned 1s - which is exactly the maximum overlap we're looking for.

By using a hash table to count occurrences of each offset (dx, dy), we efficiently find which translation produces the most overlapping 1s. The maximum count in this hash table is our answer.

Solution Approach

The implementation uses a hash table to count the frequency of each translation offset:

  1. Initialize a Counter: We use a Counter (hash table) called cnt to track how many times each offset appears.

  2. Iterate through all 1s in img1: We use nested loops to find each position (i, j) where img1[i][j] == 1.

  3. For each 1 in img1, find all 1s in img2: For every 1 found in img1, we scan through all positions (h, k) in img2 to find where img2[h][k] == 1.

  4. Calculate the offset: For each pair of 1s (one from img1 at (i, j) and one from img2 at (h, k)), we calculate the translation offset as (i - h, j - k). This represents how much we need to shift img2 to align these two specific 1s.

  5. Count the offset frequency: We increment the count for this offset in our hash table: cnt[(i - h, j - k)] += 1. Each increment means we found another pair of 1s that would align under this same translation.

  6. Find the maximum overlap: After processing all pairs, the maximum value in cnt represents the largest number of 1s that can be aligned with a single translation. We return this value using max(cnt.values()).

  7. Handle edge case: If there are no 1s in either image (meaning cnt is empty), we return 0.

The time complexity is O(n^4) since we have four nested loops iterating through the matrices. The space complexity is O(n^2) for storing the offsets in the hash table, as there can be at most n^2 different offsets.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with two 3x3 matrices:

img1 = [[1,1,0],      img2 = [[0,0,0],
        [0,1,0],              [0,1,1],
        [0,0,0]]              [0,0,1]]

Step 1: Find all 1s in img1

  • Position (0,0): value is 1
  • Position (0,1): value is 1
  • Position (1,1): value is 1

Step 2: For each 1 in img1, pair with all 1s in img2

For img1[0][0] = 1:

  • Pairs with img2[1][1] = 1: offset = (0-1, 0-1) = (-1, -1)
  • Pairs with img2[1][2] = 1: offset = (0-1, 0-2) = (-1, -2)
  • Pairs with img2[2][2] = 1: offset = (0-2, 0-2) = (-2, -2)

For img1[0][1] = 1:

  • Pairs with img2[1][1] = 1: offset = (0-1, 1-1) = (-1, 0)
  • Pairs with img2[1][2] = 1: offset = (0-1, 1-2) = (-1, -1)
  • Pairs with img2[2][2] = 1: offset = (0-2, 1-2) = (-2, -1)

For img1[1][1] = 1:

  • Pairs with img2[1][1] = 1: offset = (1-1, 1-1) = (0, 0)
  • Pairs with img2[1][2] = 1: offset = (1-1, 1-2) = (0, -1)
  • Pairs with img2[2][2] = 1: offset = (1-2, 1-2) = (-1, -1)

Step 3: Count offset frequencies

Offset (-1, -1): appears 3 times
Offset (-1, -2): appears 1 time
Offset (-2, -2): appears 1 time
Offset (-1, 0):  appears 1 time
Offset (-2, -1): appears 1 time
Offset (0, 0):   appears 1 time
Offset (0, -1):  appears 1 time

Step 4: Find maximum The offset (-1, -1) appears 3 times, which is the maximum.

Verification: If we translate img2 by offset (-1, -1) (shift up by 1 and left by 1):

Original img2:        After translation:
[[0,0,0],            [[1,1,0],
 [0,1,1],     →       [0,1,*],
 [0,0,1]]             [*,*,*]]
                      (* means out of bounds)

Overlaying this on img1:

img1:         Translated img2:    Overlap:
[[1,1,0],     [[1,1,0],          3 positions
 [0,1,0],  +   [0,1,*],     =    with 1s in both
 [0,0,0]]      [*,*,*]]          (0,0), (0,1), (1,1)

The maximum overlap is 3.

Solution Implementation

1class Solution:
2    def largestOverlap(self, img1: List[List[int]], img2: List[List[int]]) -> int:
3        """
4        Find the largest overlap between two binary images by sliding one over the other.
5      
6        The algorithm works by:
7        1. Finding all '1' positions in both images
8        2. For each pair of '1's (one from each image), calculating the translation vector
9        3. Counting how many '1's align for each unique translation
10        4. Returning the maximum count
11      
12        Args:
13            img1: First n x n binary matrix
14            img2: Second n x n binary matrix
15          
16        Returns:
17            Maximum number of overlapping 1's after translation
18        """
19        n = len(img1)
20      
21        # Dictionary to count overlaps for each translation vector (row_shift, col_shift)
22        translation_count = Counter()
23      
24        # Iterate through all positions in img1
25        for row1 in range(n):
26            for col1 in range(n):
27                # If current position in img1 contains a 1
28                if img1[row1][col1] == 1:
29                    # Check all positions in img2
30                    for row2 in range(n):
31                        for col2 in range(n):
32                            # If current position in img2 contains a 1
33                            if img2[row2][col2] == 1:
34                                # Calculate the translation vector needed to align
35                                # these two 1's (from img1 position to img2 position)
36                                translation_vector = (row1 - row2, col1 - col2)
37                              
38                                # Increment count for this translation
39                                translation_count[translation_vector] += 1
40      
41        # Return the maximum overlap count, or 0 if no overlaps exist
42        return max(translation_count.values()) if translation_count else 0
43
1class Solution {
2    public int largestOverlap(int[][] img1, int[][] img2) {
3        int n = img1.length;
4        // Map to store translation vectors and their occurrence counts
5        // Key: translation vector [deltaRow, deltaCol], Value: count of overlapping 1s
6        Map<List<Integer>, Integer> translationCount = new HashMap<>();
7        int maxOverlap = 0;
8      
9        // Iterate through all positions with 1s in img1
10        for (int row1 = 0; row1 < n; row1++) {
11            for (int col1 = 0; col1 < n; col1++) {
12                if (img1[row1][col1] == 1) {
13                    // For each 1 in img1, check all 1s in img2
14                    for (int row2 = 0; row2 < n; row2++) {
15                        for (int col2 = 0; col2 < n; col2++) {
16                            if (img2[row2][col2] == 1) {
17                                // Calculate the translation vector needed to align 
18                                // current 1 in img1 with current 1 in img2
19                                List<Integer> translationVector = List.of(row1 - row2, col1 - col2);
20                              
21                                // Increment count for this translation vector and update max
22                                // merge() adds 1 to existing count or initializes to 1 if new
23                                int currentCount = translationCount.merge(translationVector, 1, Integer::sum);
24                                maxOverlap = Math.max(maxOverlap, currentCount);
25                            }
26                        }
27                    }
28                }
29            }
30        }
31      
32        return maxOverlap;
33    }
34}
35
1class Solution {
2public:
3    int largestOverlap(vector<vector<int>>& img1, vector<vector<int>>& img2) {
4        int n = img1.size();
5        // Map to store the frequency of each translation vector (deltaRow, deltaCol)
6        // Key: translation vector, Value: count of overlapping 1s for this translation
7        map<pair<int, int>, int> translationCount;
8        int maxOverlap = 0;
9      
10        // Iterate through all cells with value 1 in img1
11        for (int row1 = 0; row1 < n; ++row1) {
12            for (int col1 = 0; col1 < n; ++col1) {
13                if (img1[row1][col1] == 1) {
14                    // For each 1 in img1, check all 1s in img2
15                    for (int row2 = 0; row2 < n; ++row2) {
16                        for (int col2 = 0; col2 < n; ++col2) {
17                            if (img2[row2][col2] == 1) {
18                                // Calculate the translation vector needed to align
19                                // the current 1 in img1 with the current 1 in img2
20                                int deltaRow = row1 - row2;
21                                int deltaCol = col1 - col2;
22                              
23                                // Increment count for this translation vector and update max
24                                translationCount[{deltaRow, deltaCol}]++;
25                                maxOverlap = max(maxOverlap, translationCount[{deltaRow, deltaCol}]);
26                            }
27                        }
28                    }
29                }
30            }
31        }
32      
33        return maxOverlap;
34    }
35};
36
1/**
2 * Finds the largest overlap between two binary images after translation
3 * @param img1 - First binary image represented as 2D array
4 * @param img2 - Second binary image represented as 2D array
5 * @returns Maximum number of overlapping 1s after optimal translation
6 */
7function largestOverlap(img1: number[][], img2: number[][]): number {
8    const imageSize: number = img1.length;
9  
10    // Map to store translation vectors and their corresponding overlap counts
11    // Key: encoded translation vector (row_offset * 200 + col_offset)
12    // Value: count of overlapping 1s for this translation
13    const translationCountMap: Map<number, number> = new Map();
14  
15    let maxOverlap: number = 0;
16  
17    // Iterate through all positions with value 1 in img1
18    for (let row1 = 0; row1 < imageSize; ++row1) {
19        for (let col1 = 0; col1 < imageSize; ++col1) {
20            if (img1[row1][col1] === 1) {
21              
22                // For each 1 in img1, check all possible overlaps with 1s in img2
23                for (let row2 = 0; row2 < imageSize; ++row2) {
24                    for (let col2 = 0; col2 < imageSize; ++col2) {
25                        if (img2[row2][col2] === 1) {
26                          
27                            // Calculate translation vector from img1 position to img2 position
28                            // Encode as single number: row_offset * 200 + col_offset
29                            // 200 is chosen as it's larger than max possible offset (n < 100)
30                            const translationKey: number = (row1 - row2) * 200 + (col1 - col2);
31                          
32                            // Increment count for this translation vector
33                            const currentCount: number = (translationCountMap.get(translationKey) ?? 0) + 1;
34                            translationCountMap.set(translationKey, currentCount);
35                          
36                            // Update maximum overlap found so far
37                            maxOverlap = Math.max(maxOverlap, currentCount);
38                        }
39                    }
40                }
41            }
42        }
43    }
44  
45    return maxOverlap;
46}
47

Time and Space Complexity

Time Complexity: O(n^4)

The code contains four nested loops:

  • The outer two loops iterate through all positions in img1, each running n times
  • For each position where img1[i][j] = 1, the inner two loops iterate through all positions in img2, each running n times
  • In the worst case where all elements in img1 are 1, we perform n × n × n × n = n^4 operations
  • The dictionary update operation cnt[(i - h, j - k)] += 1 takes O(1) time
  • Finding the maximum value in the counter takes O(n^2) time in the worst case (as there can be at most (2n-1) × (2n-1) different shifts), but this is dominated by the O(n^4) loops

Space Complexity: O(n^2)

  • The cnt Counter dictionary stores shift vectors (i - h, j - k) as keys
  • The possible range of shifts is from -(n-1) to (n-1) in both dimensions
  • This gives us at most (2n-1) × (2n-1) = O(n^2) possible unique shift vectors
  • Each entry in the counter stores a shift vector (tuple of two integers) and a count value
  • Therefore, the space complexity is O(n^2)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Confusion About Which Image to Translate

Many people get confused about whether they're translating img1 or img2. The code calculates (row1 - row2, col1 - col2), which represents translating img2 to align with img1. A common mistake is reversing this logic or inconsistently applying translations.

Solution: Be consistent with your translation direction. The current approach treats img1 as fixed and translates img2. The offset (row1 - row2, col1 - col2) means "how much to shift img2 to align its 1 at (row2, col2) with img1's 1 at (row1, col1)."

2. Attempting to Optimize Prematurely by Only Storing 1's Positions

Some might try to optimize by first extracting all positions of 1's into separate lists, then iterating through these lists. While this seems efficient, it can lead to more complex code and potential indexing errors.

# Problematic approach
ones_img1 = [(i, j) for i in range(n) for j in range(n) if img1[i][j] == 1]
ones_img2 = [(i, j) for i in range(n) for j in range(n) if img2[i][j] == 1]
# This adds complexity without significant performance gain for typical inputs

Solution: The straightforward nested loop approach is cleaner and easier to understand. Only optimize if performance becomes an actual issue with large inputs.

3. Forgetting to Handle Empty Counter

If either image contains no 1's, the translation_count dictionary will be empty, and calling max(translation_count.values()) will throw a ValueError.

Solution: Always check if the counter is non-empty before calling max():

return max(translation_count.values()) if translation_count else 0

4. Misunderstanding Translation vs. Rotation

Some might think they need to consider rotations or flips of the images. The problem only allows translations (sliding), not rotations.

Solution: Focus only on horizontal and vertical shifts. Each translation is uniquely defined by a (row_shift, col_shift) pair.

5. Incorrect Offset Calculation

A common error is calculating the offset as (row2 - row1, col2 - col1) instead of (row1 - row2, col1 - col2). This reversal would give incorrect results as it represents the opposite translation direction.

Solution: Remember that the offset represents how to move img2 to align with img1. If a 1 in img1 is at position (3, 4) and a 1 in img2 is at position (1, 2), we need to shift img2 by (3-1, 4-2) = (2, 2) to align these 1's.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More