From d9b81bc5f1847ad6fd1c2fd83ac5a63f7b4b2235 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 13 Sep 2026 05:09:14 +0000 Subject: [PATCH] feat: add solution for 0835. Image Overlap --- problems/0835-image-overlap/analysis.md | 81 +++++++++++ problems/0835-image-overlap/problem.md | 65 +++++++++ .../solution_daily_20260913.go | 56 ++++++++ .../solution_daily_20260913_test.go | 126 ++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 problems/0835-image-overlap/analysis.md create mode 100644 problems/0835-image-overlap/problem.md create mode 100644 problems/0835-image-overlap/solution_daily_20260913.go create mode 100644 problems/0835-image-overlap/solution_daily_20260913_test.go diff --git a/problems/0835-image-overlap/analysis.md b/problems/0835-image-overlap/analysis.md new file mode 100644 index 0000000..8547f3c --- /dev/null +++ b/problems/0835-image-overlap/analysis.md @@ -0,0 +1,81 @@ +# 0835. Image Overlap + +[LeetCode Link](https://leetcode.com/problems/image-overlap/) + +Difficulty: Medium +Topics: Array, Matrix +Acceptance Rate: 65.5% + +## Hints + +### Hint 1 + +The problem hands you a search space that is smaller than it first appears. You are told you may slide the image "any number of units" in any direction, but a shift that pushes every `1` off the board is useless. Start by asking: how many *distinct* translations can actually produce a non-zero overlap? Once you can enumerate the candidates, brute force over them becomes a legitimate strategy — the interesting work is in deciding what to enumerate and how to score each candidate cheaply. + +### Hint 2 + +There are two ways to attack this, and they lead to different complexities. + +The first is to enumerate translations directly: for each vertical shift `dr` and horizontal shift `dc` in the range `[-(n-1), n-1]`, count the positions where the shifted `img1` and `img2` both hold a `1`. That is `O(n^2)` shifts, each costing `O(n^2)` to score. + +The second flips the loop inside out. Instead of asking "for this shift, how many `1`s line up?", ask "for this *pair* of `1`s, which shift would align them?" Think about what a single `1` at `(i, j)` in `img1` and a single `1` at `(k, l)` in `img2` tell you about a translation. + +### Hint 3 + +A `1` at `img1[i][j]` lands on top of a `1` at `img2[k][l]` under exactly **one** translation: the offset `(k - i, l - j)`. So every ordered pair of ones *votes* for precisely one shift. + +That means you can collect the coordinates of all `1`s in each image, take every pair across the two lists, compute the offset, and tally the votes in a hash map. The offset with the most votes is the best translation, and its vote count *is* the overlap — because the overlap under a given shift is by definition the number of ones that align under it, which is exactly the number of pairs that voted for it. No shifting of matrices required; you never move a single bit. + +## Approach + +The key reframing is to stop thinking about sliding matrices and start thinking about **pairs of ones voting for offsets**. + +**Why the vote count equals the overlap.** Fix a translation `(dr, dc)` meaning "move `img1` down by `dr` and right by `dc`." Under it, the `1` at `img1[i][j]` moves to `(i + dr, j + dc)`. It contributes to the overlap exactly when `img2[i + dr][j + dc] == 1`. Setting `k = i + dr` and `l = j + dc`, that condition is "there is a one at `img2[k][l]`," and the offset is recovered as `dr = k - i`, `dc = l - j`. So the ones contributing to the overlap at `(dr, dc)` are in bijection with the pairs `((i, j), (k, l))` whose coordinate difference is `(dr, dc)`. Counting pairs by their difference and taking the maximum therefore gives the maximum overlap directly. + +Note this also handles the "bits translated outside the borders are erased" rule for free. A one that slides off the grid has no partner in `img2` to pair with, so it simply never casts a vote. There is no clamping or bounds arithmetic to get wrong. + +**Algorithm.** + +1. Scan `img1` once and collect the coordinates of every `1` into a slice `ones1`. Do the same for `img2` into `ones2`. +2. If either slice is empty, return `0` immediately — no alignment is possible. +3. For each `a` in `ones1` and each `b` in `ones2`, compute the offset `(b.row - a.row, b.col - a.col)` and increment `count[offset]` in a map. +4. Return the largest value in `count`. + +Because the offsets live in `[-(n-1), n-1]` on both axes, the key can be packed into a single `int` — for instance `(dr + n) * (2*n) + (dc + n)` — which keeps the map keys compact and avoids hashing a struct. Either works; the packed integer is a little faster and is what the solution uses. + +**Walking Example 1.** With + +``` +img1 = [[1,1,0], img2 = [[0,0,0], + [0,1,0], [0,1,1], + [0,1,0]] [0,0,1]] +``` + +the ones are `ones1 = [(0,0), (0,1), (1,1), (2,1)]` and `ones2 = [(1,1), (1,2), (2,2)]`. + +Consider the votes cast for offset `(1, 1)`, i.e. down 1 and right 1: + +- `(0,0)` vs `(1,1)` → difference `(1,1)` ✓ +- `(0,1)` vs `(1,2)` → difference `(1,1)` ✓ +- `(1,1)` vs `(2,2)` → difference `(1,1)` ✓ +- `(2,1)` would need a partner at `(3,2)`, which is off the board, so it contributes nothing. + +Offset `(1,1)` receives 3 votes, and no other offset beats it, so the answer is `3` — matching the problem's explanation of translating right 1 and down 1. + +**On complexity and why this is the right trade.** The naive shift-enumeration approach is `O(n^4)`, which at `n = 30` is 810,000 operations — perfectly acceptable here. The pair-voting approach is `O(m1 * m2)` where `m1` and `m2` are the counts of ones. In the worst case (both images entirely ones) that is also `O(n^4)`, so asymptotically the two tie. The practical win is that voting scales with the *density of ones* rather than the matrix size, so sparse images — the common case — finish far faster. It is worth being honest that this problem's small constraint (`n <= 30`) means brute force passes comfortably; the pair-voting insight is the reason the problem is interesting, not a necessity for getting accepted. + +## Complexity Analysis + +Time Complexity: O(m1 · m2), where `m1` and `m2` are the numbers of `1`s in `img1` and `img2`. This is bounded by O(n⁴) in the worst case (both matrices entirely ones) and drops to near O(n²) for sparse inputs, since the initial scan of both matrices costs O(n²) regardless. + +Space Complexity: O(m1 + m2) to store the coordinate lists, plus O(min(m1 · m2, n²)) for the offset-count map — there are at most `(2n - 1)²` distinct offsets, so the map is O(n²) bounded. Overall O(n²). + +## Edge Cases + +- **Either image is all zeros.** With no ones in one of the images there are no pairs, so no votes are cast and the map stays empty. Returning the max over an empty map is a classic crash or wrong-answer source in Go (the zero value of the running max works here, but only if you initialize it to `0` rather than to something like the first map entry). The solution guards with an early return of `0`. +- **Both images are all zeros.** Example 3 in the problem. Same reasoning as above; the answer is `0`, not `n²`. Zeros aligning with zeros does not count as overlap. +- **`n == 1`.** The smallest legal input. Only the zero offset `(0, 0)` is possible; the answer is `1` if both cells are `1` (Example 2) and `0` otherwise. +- **Negative offsets.** Translations go up and left as well as down and right, so `dr` and `dc` range over `[-(n-1), n-1]`. If you pack the offset into an integer key, you must bias by `+n` (or similar) before packing, or negative components will collide with positive ones and silently inflate counts. +- **No translation at all is best.** The identity shift `(0, 0)` is a legitimate candidate and must be included in the search — pairs where `a == b` naturally produce offset `(0, 0)`, so the voting approach covers it without a special case. +- **Both images entirely ones.** The dense worst case, `900 × 900 = 810,000` pairs at `n = 30`. Still fast, but it is the input to keep in mind when reasoning about the runtime. The answer is `n²` via the zero offset. +- **Asymmetry of the roles.** The offset is `img2`-coordinate minus `img1`-coordinate, and the problem is symmetric (translating `img1` right is equivalent to translating `img2` left), so consistency matters more than direction — just do not mix the subtraction order between the row and column components. diff --git a/problems/0835-image-overlap/problem.md b/problems/0835-image-overlap/problem.md new file mode 100644 index 0000000..baa68b6 --- /dev/null +++ b/problems/0835-image-overlap/problem.md @@ -0,0 +1,65 @@ +--- +number: "0835" +frontend_id: "835" +title: "Image Overlap" +slug: "image-overlap" +difficulty: "Medium" +topics: + - "Array" + - "Matrix" +acceptance_rate: 6550.9 +is_premium: false +created_at: "2026-09-13T05:06:57.815154+00:00" +fetched_at: "2026-09-13T05:06:57.815154+00:00" +link: "https://leetcode.com/problems/image-overlap/" +date: "2026-09-13" +--- + +# 0835. Image Overlap + +You are given two images, `img1` and `img2`, represented as binary, square matrices of size `n x n`. A binary matrix has only `0`s and `1`s 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:** + +![](https://assets.leetcode.com/uploads/2020/09/09/overlap1.jpg) + + + **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. + ![](https://assets.leetcode.com/uploads/2020/09/09/overlap_step1.jpg) + The number of positions that have a 1 in both images is 3 (shown in red). + ![](https://assets.leetcode.com/uploads/2020/09/09/overlap_step2.jpg) + + +**Example 2:** + + + **Input:** img1 = [[1]], img2 = [[1]] + **Output:** 1 + + +**Example 3:** + + + **Input:** img1 = [[0]], img2 = [[0]] + **Output:** 0 + + + + +**Constraints:** + + * `n == img1.length == img1[i].length` + * `n == img2.length == img2[i].length` + * `1 <= n <= 30` + * `img1[i][j]` is either `0` or `1`. + * `img2[i][j]` is either `0` or `1`. diff --git a/problems/0835-image-overlap/solution_daily_20260913.go b/problems/0835-image-overlap/solution_daily_20260913.go new file mode 100644 index 0000000..a204c80 --- /dev/null +++ b/problems/0835-image-overlap/solution_daily_20260913.go @@ -0,0 +1,56 @@ +package main + +// Approach: pair-voting on translation offsets. +// +// A 1 at img1[i][j] can only land on a 1 at img2[k][l] under exactly one +// translation: the offset (k-i, l-j). So every cross pair of ones votes for a +// single offset, and the number of votes an offset receives is precisely the +// overlap produced by that translation. Collect the coordinates of the ones in +// each image, tally offsets in a map, and return the largest tally. +// +// Ones that would slide off the grid simply have no partner to pair with, so +// the "bits translated outside the borders are erased" rule needs no explicit +// bounds handling. +// +// Time: O(m1*m2) where m1, m2 are the counts of ones (O(n^4) worst case). +// Space: O(n^2) — at most (2n-1)^2 distinct offsets. +func largestOverlap(img1 [][]int, img2 [][]int) int { + n := len(img1) + if n == 0 { + return 0 + } + + ones1 := onesOf(img1) + ones2 := onesOf(img2) + if len(ones1) == 0 || len(ones2) == 0 { + return 0 + } + + // Offsets range over [-(n-1), n-1] on both axes, so bias by n before + // packing into a single int key to keep negative and positive shifts apart. + counts := make(map[int]int) + best := 0 + for _, a := range ones1 { + for _, b := range ones2 { + key := (b[0]-a[0]+n)*(2*n) + (b[1] - a[1] + n) + counts[key]++ + if counts[key] > best { + best = counts[key] + } + } + } + return best +} + +// onesOf returns the [row, col] coordinates of every 1 in the matrix. +func onesOf(img [][]int) [][2]int { + ones := make([][2]int, 0, len(img)*len(img)) + for i, row := range img { + for j, v := range row { + if v == 1 { + ones = append(ones, [2]int{i, j}) + } + } + } + return ones +} diff --git a/problems/0835-image-overlap/solution_daily_20260913_test.go b/problems/0835-image-overlap/solution_daily_20260913_test.go new file mode 100644 index 0000000..1eb927d --- /dev/null +++ b/problems/0835-image-overlap/solution_daily_20260913_test.go @@ -0,0 +1,126 @@ +package main + +import "testing" + +func TestSolution(t *testing.T) { + tests := []struct { + name string + img1 [][]int + img2 [][]int + expected int + }{ + { + name: "example 1: translate right 1 and down 1", + img1: [][]int{{1, 1, 0}, {0, 1, 0}, {0, 1, 0}}, + img2: [][]int{{0, 0, 0}, {0, 1, 1}, {0, 0, 1}}, + expected: 3, + }, + { + name: "example 2: single cell, both ones", + img1: [][]int{{1}}, + img2: [][]int{{1}}, + expected: 1, + }, + { + name: "example 3: single cell, both zeros", + img1: [][]int{{0}}, + img2: [][]int{{0}}, + expected: 0, + }, + { + name: "edge case: n=1 with mismatched cells", + img1: [][]int{{1}}, + img2: [][]int{{0}}, + expected: 0, + }, + { + name: "edge case: img1 is all zeros", + img1: [][]int{{0, 0}, {0, 0}}, + img2: [][]int{{1, 1}, {1, 1}}, + expected: 0, + }, + { + name: "edge case: img2 is all zeros", + img1: [][]int{{1, 1}, {1, 1}}, + img2: [][]int{{0, 0}, {0, 0}}, + expected: 0, + }, + { + name: "edge case: identical dense images need no translation", + img1: [][]int{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, + img2: [][]int{{1, 1, 1}, {1, 1, 1}, {1, 1, 1}}, + expected: 9, + }, + { + name: "edge case: negative offset, translate up and left", + img1: [][]int{{0, 0, 0}, {0, 1, 1}, {0, 0, 1}}, + img2: [][]int{{1, 1, 0}, {0, 1, 0}, {0, 1, 0}}, + expected: 3, + }, + { + name: "edge case: single one each, opposite corners", + img1: [][]int{{1, 0}, {0, 0}}, + img2: [][]int{{0, 0}, {0, 1}}, + expected: 1, + }, + { + name: "edge case: 4x4 pure horizontal translation", + img1: [][]int{{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}, + img2: [][]int{{0, 0, 0, 0}, {0, 0, 1, 1}, {0, 0, 0, 1}, {0, 0, 0, 0}}, + expected: 3, + }, + { + name: "edge case: identity shift beats every other offset", + img1: [][]int{{1, 0, 1}, {0, 1, 0}, {1, 0, 1}}, + img2: [][]int{{1, 0, 1}, {0, 1, 0}, {1, 0, 1}}, + expected: 5, + }, + { + name: "edge case: disjoint single ones in a 3x3 grid", + img1: [][]int{{0, 0, 0}, {0, 0, 0}, {0, 0, 1}}, + img2: [][]int{{1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, + expected: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := largestOverlap(tt.img1, tt.img2) + if result != tt.expected { + t.Errorf("largestOverlap(%v, %v) = %v, want %v", tt.img1, tt.img2, result, tt.expected) + } + }) + } +} + +// TestSolutionSymmetry checks the documented symmetry of the problem: sliding +// img1 one way is equivalent to sliding img2 the other way, so swapping the +// arguments must not change the answer. +func TestSolutionSymmetry(t *testing.T) { + tests := []struct { + name string + img1 [][]int + img2 [][]int + }{ + { + name: "example 1 swapped", + img1: [][]int{{1, 1, 0}, {0, 1, 0}, {0, 1, 0}}, + img2: [][]int{{0, 0, 0}, {0, 1, 1}, {0, 0, 1}}, + }, + { + name: "sparse corners", + img1: [][]int{{1, 0, 0}, {0, 0, 0}, {0, 0, 1}}, + img2: [][]int{{0, 0, 1}, {0, 1, 0}, {0, 0, 0}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + forward := largestOverlap(tt.img1, tt.img2) + backward := largestOverlap(tt.img2, tt.img1) + if forward != backward { + t.Errorf("overlap not symmetric: largestOverlap(img1, img2) = %v, largestOverlap(img2, img1) = %v", forward, backward) + } + }) + } +}