LeetCode 835: Image Overlap – Java Matrix Translation Solution

Find the maximum overlap between two binary images by translating one matrix in every possible direction.

Krishna Shrivastava
12 views
LinkedInGithubX
0
0
LeetCode 835: Image Overlap – Java Matrix Translation Solution
Listen to articleAudio version
Ad

Introduction

What happens when one binary image is shifted over another and the goal is to find the position where the two images overlap the most?

That is exactly the idea behind LeetCode 835: Image Overlap.

Each image is represented as an n × n binary matrix containing only 0s and 1s. One image can be moved left, right, up, or down, but it cannot be rotated.

After every possible translation, the number of positions containing 1 in both images is calculated. The maximum value among all translations is the answer.

Since n is at most 30, a direct simulation of every possible translation is practical.

Question Link

LeetCode 835 – Image Overlap

Understanding the Problem

Consider two images:

Image 1

1 1 0
0 1 0
0 1 0

and:

Image 2

0 0 0
0 1 1
0 0 1

The first image can be translated.

For example, shifting Image 1:

Right → 1
Down → 1

can align several 1s from the two images.

The objective is to find the translation that produces the largest number of overlapping 1s.

Key Observation

A translation can be represented using two values:

row offset
column offset

For example:

row offset = 1
column offset = 1

means that a cell from Image 2 at:

(i, j)

is compared with Image 1 at:

(i + 1, j + 1)

The algorithm tries every possible row and column offset.

For an n × n matrix, each offset ranges from:

-(n - 1) to (n - 1)

This covers every possible way the two images can overlap without requiring rotation.

Approach

The solution is divided into two parts.

Generate Every Translation

The largestOverlap() function generates every possible pair of row and column offsets.

for(int i = -n + 1; i < n; i++)

handles vertical movement.

for(int j = -n + 1; j < n; j++)

handles horizontal movement.

For every pair:

(rowOffset, columnOffset)

the helper function ch() calculates the overlap.

Count the Overlapping Ones

For every cell in img2, the translated position inside img1 is calculated:

int tarrow = i + rowo;
int tarcol = j + colo;

The position is counted only when:

  1. It is still inside img1.
  2. img2[i][j] == 1.
  3. img1[tarrow][tarcol] == 1.

Every such position contributes 1 to the overlap.

Java Solution

class Solution {

// Calculates the overlap for one particular translation.
public int ch(int[][] im1, int[][] im2, int rowo, int colo) {

int max = 0;

// Traverse every cell of img2.
for (int i = 0; i < im2.length; i++) {
for (int j = 0; j < im2.length; j++) {

// Apply the row and column translation.
int tarrow = i + rowo;
int tarcol = j + colo;

// Make sure the translated position
// is still inside img1.
if (tarrow >= 0 &&
tarcol >= 0 &&
tarrow < im1.length &&
tarcol < im1.length &&
im2[i][j] == 1 &&
im1[tarrow][tarcol] == 1) {

// Both images contain 1 at this position.
max++;
}
}
}

return max;
}

public int largestOverlap(int[][] img1, int[][] img2) {

int n = img1.length;

// Stores the maximum overlap found so far.
int an = 0;

// Try every possible row translation.
for (int i = -n + 1; i < n; i++) {

// Try every possible column translation.
for (int j = -n + 1; j < n; j++) {

// Calculate overlap for this translation.
int max = 0;
max = ch(img1, img2, i, j);

// Keep the best overlap.
an = Math.max(max, an);
}
}

return an;
}
}

Visual Dry Run

Consider:

img1 =
1 1 0
0 1 0
0 1 0
img2 =
0 0 0
0 1 1
0 0 1

One useful translation is:

row offset = 1
column offset = 1

The idea is to move Image 2 relative to Image 1 and compare the positions containing 1.

How offset move on matrix?


The visual makes the important idea immediately clear: translation changes the positions being compared, while rotation is never performed.

How the Translation Works

Suppose:

rowo = 1
colo = 1

For a cell in img2:

(i, j)

the corresponding position in img1 becomes:

(i + 1, j + 1)

For example:

img2[1][1]

is compared with:

img1[2][2]

If both contain 1, the overlap count increases.

The boundary check is important because some translations move cells outside the matrix.

For example, a large positive offset could produce:

tarrow >= im1.length

In that case, the translated cell is outside the image and must not be counted.

Dry Run of the Helper Function

Suppose the translation is:

rowo = 1
colo = 1

For every (i, j) in img2, the code calculates:

tarrow = i + rowo;
tarcol = j + colo;

Then it checks:

img2[i][j] == 1

and:

img1[tarrow][tarcol] == 1

Only when both are true does the overlap increase.

Conceptually:

Image 2 cell
Apply translation
Corresponding Image 1 cell
Are both values 1?
Yes → overlap++

After checking every cell, ch() returns the overlap for that particular translation.

Exploring All Possible Translations

For a matrix of size n, the row offset is explored from:

-(n - 1)

to:

n - 1

The same is done for the column offset.

For example, when:

n = 3

the possible offsets are:

-2, -1, 0, 1, 2

Therefore, all combinations are tested:

(-2,-2) (-2,-1) (-2,0) ...
(-1,-2) (-1,-1) (-1,0) ...
( 0,-2) ( 0,-1) ( 0,0) ...
...

Each pair represents one possible translation.

The largest overlap among all of them becomes the final answer.

Why Boundary Checking Is Necessary

Imagine shifting an image to the right.

Some cells will move beyond the right edge of the matrix.

Those cells effectively disappear.

The same happens when moving:

  1. left
  2. up
  3. down

Therefore, before accessing:

im1[tarrow][tarcol]

the solution verifies:

tarrow >= 0
tarcol >= 0
tarrow < im1.length
tarcol < im1.length

This prevents an ArrayIndexOutOfBoundsException and correctly handles pixels that move outside the image.

Why Rotation Is Not Needed

The problem allows only translation.

That means the relative structure of the 1s never changes.

For example:

1 0
0 1

can move:

← →
↑ ↓

but it cannot become:

0 1
1 0

through rotation.

This is why simply testing row and column offsets is enough.

Complexity Analysis

There are:

(2n - 1)

possible row offsets and the same number of column offsets.

Therefore, the number of translations is:

(2n - 1)²

For every translation, the entire n × n matrix is scanned.

That takes:

O(n²)

So the overall complexity is:

O((2n - 1)² × n²)

which simplifies to:

O(n⁴)

Space Complexity

The solution uses only a few variables apart from the input matrices.

Therefore, the extra space is:

O(1)

Why O(n⁴) Is Acceptable Here

At first glance, O(n⁴) may look expensive.

However, the constraint is:

n ≤ 30

This is a relatively small matrix.

The number of translations is at most:

(2 × 30 - 1)² = 59² = 3481

and each translation checks at most:

30 × 30 = 900

cells.

So the total amount of work remains manageable.

This is a good example of an important DSA principle:

The best algorithm depends not only on the Big-O notation, but also on the actual constraints.

Interview Tip

When dealing with two matrices or grids and the problem allows one of them to move, a useful first question is:

Can the movement be represented using coordinates or offsets?

Here, every possible movement can be represented with:

(row offset, column offset)

Once that representation is identified, the problem becomes a straightforward simulation.

A good mental pattern is:

Movement
Represent movement as coordinates
Try every valid movement
Compare overlapping positions
Keep the maximum

This pattern can also appear in problems involving:

  1. Grid matching
  2. Pattern alignment
  3. 2D simulations
  4. Image processing
  5. Coordinate transformations

Conclusion

LeetCode 835 is a useful matrix simulation problem that demonstrates how a seemingly visual problem can be converted into simple coordinate calculations.

The main idea is to represent every possible translation using a row offset and column offset. For each translation, every cell of the second image is mapped to its corresponding position in the first image, and overlapping 1s are counted.

Because the matrix size is limited to 30 × 30, the O(n⁴) brute-force simulation is practical and keeps the implementation straightforward.

The most important takeaway is the coordinate-based way of thinking: when an object moves on a grid, represent that movement as an offset and systematically test the valid positions.

Ai Assistant Kas