diff --git a/problems/1840-maximum-building-height/analysis.md b/problems/1840-maximum-building-height/analysis.md new file mode 100644 index 0000000..71b8844 --- /dev/null +++ b/problems/1840-maximum-building-height/analysis.md @@ -0,0 +1,84 @@ +# 1840. Maximum Building Height + +[LeetCode Link](https://leetcode.com/problems/maximum-building-height/) + +Difficulty: Hard +Topics: Array, Math, Sorting +Acceptance Rate: 50.1% + +## Hints + +### Hint 1 + +`n` can be up to `10^9`, so you cannot build an array of heights and simulate every building. Notice that the number of *restrictions* is small (at most `10^5`). The interesting events only happen at the restricted buildings (plus building `1`, which is fixed at height `0`). Think about how to reason only about those "anchor" points and what happens *between* them. + +### Hint 2 + +A restriction does not act in isolation. Because adjacent buildings differ by at most `1`, a low cap on one building forces nearby buildings to be low too. Sort the restrictions by index and ask: given building `a` is capped at height `Ha`, what is the tightest cap that propagates to a later building `b`? This is a relaxation/propagation idea — sweep left-to-right, then right-to-left, tightening each cap from its neighbors. + +### Hint 3 + +Once every anchor has its *final, fully-propagated* maximum height, consider a single gap between two consecutive anchors `(idA, hA)` and `(idB, hB)`. Heights can climb up from both sides and meet at a peak in the middle. With `gap = idB - idA`, the peak you can reach is `(hA + hB + gap) / 2` (integer division — slopes of `+1` from each side meeting in the middle). The answer is the maximum peak over all consecutive gaps. + +## Approach + +We treat the problem as constraint propagation over a small set of "anchor" buildings. + +**Step 1 — Collect anchors.** +The only buildings that matter are the restricted ones, building `1` (forced to height `0`), and building `n` (it can be the tallest, climbing freely on the right). Build a list of `(id, maxHeight)` points: + +- Add `(1, 0)` — building 1 is forced to `0`. +- Keep every given restriction. +- If building `n` is not already restricted, add `(n, n-1)`. The cap `n-1` is the largest height physically reachable at index `n` (starting from `0` and rising by at most `1` per step), so it never over-constrains; it just gives us an anchor on the right edge. + +Sort all anchors by `id`. + +**Step 2 — Forward propagation (left to right).** +A building can be at most `1` taller than its left neighbor for each step between them. So for consecutive anchors `i-1` and `i`: + +``` +h[i] = min(h[i], h[i-1] + (id[i] - id[i-1])) +``` + +This pushes the unavoidable consequences of low caps (and the forced `0` at building 1) rightward. + +**Step 3 — Backward propagation (right to left).** +Symmetrically, a cap on a later building limits earlier ones: + +``` +h[i] = min(h[i], h[i+1] + (id[i+1] - id[i])) +``` + +After both passes, every anchor holds its true maximum achievable height. + +**Step 4 — Find the peak in each gap.** +Between two adjacent anchors `(idA, hA)` and `(idB, hB)` with `gap = idB - idA`, heights rise with slope `+1` from each side. The tallest point in the gap is: + +``` +peak = (hA + hB + gap) / 2 // integer division +``` + +The overall answer is the maximum of these peaks across all consecutive pairs. The anchor heights themselves are included automatically because a pair where one side is the taller anchor still yields at least that height. + +**Worked example (Example 1):** `n = 5`, `restrictions = [[2,1],[4,1]]`. +Anchors after adding `(1,0)` and `(5,4)`: `[(1,0),(2,1),(4,1),(5,4)]`. +Forward pass leaves them `[0,1,1,2]`; backward pass keeps them `[0,1,1,2]`. +Gaps give peaks `1`, `(1+1+2)/2 = 2`, `(1+2+1)/2 = 2`. Maximum is **2**. ✅ + +This problem is genuinely a Hard — the propagation-both-ways plus the meet-in-the-middle peak formula is the crux. If you derived the `(hA + hB + gap) / 2` idea on your own, that is the hardest part done. + +## Complexity Analysis + +Let `m` be the number of restrictions. + +Time Complexity: O(m log m) — dominated by sorting the anchors. The two propagation passes and the peak scan are each O(m). +Space Complexity: O(m) — for the list of anchor points. + +## Edge Cases + +- **No restrictions (`restrictions = []`):** Only anchors are `(1,0)` and the added `(n, n-1)`. The peak formula yields `n-1`, the unrestricted maximum (Example 2). +- **Building `n` already restricted:** Do not add a synthetic `(n, n-1)`; use the real cap, otherwise you would ignore a genuine constraint (Example 3 has `(10,3)`). +- **A restriction with `maxHeight` larger than reachable:** Forward/backward propagation tightens it down to what is physically achievable, so an overly generous cap never inflates the answer. +- **Out-of-order restrictions in the input:** Always sort by `id` first; the propagation logic assumes ascending indices. +- **Integer division in the peak formula:** `(hA + hB + gap) / 2` must use floor division — when the meeting point falls between two buildings, the achievable integer height is the floor. +- **Large `n` (up to `10^9`):** Never allocate per-building storage; everything is keyed off the `O(m)` anchors. Heights and indices fit comfortably in Go's `int` (64-bit on common platforms). diff --git a/problems/1840-maximum-building-height/problem.md b/problems/1840-maximum-building-height/problem.md new file mode 100644 index 0000000..2378ce3 --- /dev/null +++ b/problems/1840-maximum-building-height/problem.md @@ -0,0 +1,79 @@ +--- +number: "1840" +frontend_id: "1840" +title: "Maximum Building Height" +slug: "maximum-building-height" +difficulty: "Hard" +topics: + - "Array" + - "Math" + - "Sorting" +acceptance_rate: 5014.0 +is_premium: false +created_at: "2026-06-20T04:49:28.135969+00:00" +fetched_at: "2026-06-20T04:49:28.135969+00:00" +link: "https://leetcode.com/problems/maximum-building-height/" +date: "2026-06-20" +--- + +# 1840. Maximum Building Height + +You want to build `n` new buildings in a city. The new buildings will be built in a line and are labeled from `1` to `n`. + +However, there are city restrictions on the heights of the new buildings: + + * The height of each building must be a non-negative integer. + * The height of the first building **must** be `0`. + * The height difference between any two adjacent buildings **cannot exceed** `1`. + + + +Additionally, there are city restrictions on the maximum height of specific buildings. These restrictions are given as a 2D integer array `restrictions` where `restrictions[i] = [idi, maxHeighti]` indicates that building `idi` must have a height **less than or equal to** `maxHeighti`. + +It is guaranteed that each building will appear **at most once** in `restrictions`, and building `1` will **not** be in `restrictions`. + +Return _the**maximum possible height** of the **tallest** building_. + + + +**Example 1:** + +![](https://assets.leetcode.com/uploads/2021/04/08/ic236-q4-ex1-1.png) + + + **Input:** n = 5, restrictions = [[2,1],[4,1]] + **Output:** 2 + **Explanation:** The green area in the image indicates the maximum allowed height for each building. + We can build the buildings with heights [0,1,2,1,2], and the tallest building has a height of 2. + +**Example 2:** + +![](https://assets.leetcode.com/uploads/2021/04/08/ic236-q4-ex2.png) + + + **Input:** n = 6, restrictions = [] + **Output:** 5 + **Explanation:** The green area in the image indicates the maximum allowed height for each building. + We can build the buildings with heights [0,1,2,3,4,5], and the tallest building has a height of 5. + + +**Example 3:** + +![](https://assets.leetcode.com/uploads/2021/04/08/ic236-q4-ex3.png) + + + **Input:** n = 10, restrictions = [[5,3],[2,5],[7,4],[10,3]] + **Output:** 5 + **Explanation:** The green area in the image indicates the maximum allowed height for each building. + We can build the buildings with heights [0,1,2,3,3,4,4,5,4,3], and the tallest building has a height of 5. + + + + +**Constraints:** + + * `2 <= n <= 109` + * `0 <= restrictions.length <= min(n - 1, 105)` + * `2 <= idi <= n` + * `idi` is **unique**. + * `0 <= maxHeighti <= 109` diff --git a/problems/1840-maximum-building-height/solution.go b/problems/1840-maximum-building-height/solution.go new file mode 100644 index 0000000..4c99b6f --- /dev/null +++ b/problems/1840-maximum-building-height/solution.go @@ -0,0 +1,72 @@ +package main + +// 1840. Maximum Building Height +// +// Approach: constraint propagation over "anchor" buildings. +// +// n can be up to 1e9, so we never materialize all heights. Only the restricted +// buildings, building 1 (forced to height 0), and building n (which can be the +// tallest) matter. We: +// 1. Build anchor points (id, maxHeight), adding (1, 0) and, if missing, +// (n, n-1) — the largest height physically reachable at index n. +// 2. Sort by id, then propagate caps left-to-right and right-to-left so each +// anchor holds its true maximum achievable height (adjacent buildings differ +// by at most 1). +// 3. For each gap between consecutive anchors, heights rise with slope +1 from +// both sides and meet at peak = (hA + hB + gap) / 2. The answer is the max +// peak over all gaps. +// +// Time: O(m log m) for m restrictions (sorting dominates). +// Space: O(m). + +import "sort" + +func maxBuilding(n int, restrictions [][]int) int { + // Build anchor list with fresh inner slices so we never mutate the input. + anchors := make([][2]int, 0, len(restrictions)+2) + anchors = append(anchors, [2]int{1, 0}) // building 1 is forced to height 0 + hasN := false + for _, r := range restrictions { + anchors = append(anchors, [2]int{r[0], r[1]}) + if r[0] == n { + hasN = true + } + } + if !hasN { + // n-1 is the tallest height index n could ever reach; never over-constrains. + anchors = append(anchors, [2]int{n, n - 1}) + } + + sort.Slice(anchors, func(i, j int) bool { + return anchors[i][0] < anchors[j][0] + }) + + m := len(anchors) + + // Forward propagation: a building is at most 1 taller per step than its left neighbor. + for i := 1; i < m; i++ { + diff := anchors[i][0] - anchors[i-1][0] + if cap := anchors[i-1][1] + diff; cap < anchors[i][1] { + anchors[i][1] = cap + } + } + + // Backward propagation: a later cap limits earlier buildings too. + for i := m - 2; i >= 0; i-- { + diff := anchors[i+1][0] - anchors[i][0] + if cap := anchors[i+1][1] + diff; cap < anchors[i][1] { + anchors[i][1] = cap + } + } + + // Meet-in-the-middle peak for every gap between consecutive anchors. + ans := 0 + for i := 1; i < m; i++ { + gap := anchors[i][0] - anchors[i-1][0] + peak := (anchors[i-1][1] + anchors[i][1] + gap) / 2 + if peak > ans { + ans = peak + } + } + return ans +} diff --git a/problems/1840-maximum-building-height/solution_test.go b/problems/1840-maximum-building-height/solution_test.go new file mode 100644 index 0000000..13da748 --- /dev/null +++ b/problems/1840-maximum-building-height/solution_test.go @@ -0,0 +1,64 @@ +package main + +import "testing" + +func TestSolution(t *testing.T) { + tests := []struct { + name string + n int + restrictions [][]int + expected int + }{ + { + name: "example 1: caps at buildings 2 and 4", + n: 5, + restrictions: [][]int{{2, 1}, {4, 1}}, + expected: 2, + }, + { + name: "example 2: no restrictions, climb freely", + n: 6, + restrictions: [][]int{}, + expected: 5, + }, + { + name: "example 3: unsorted restrictions with cap at n", + n: 10, + restrictions: [][]int{{5, 3}, {2, 5}, {7, 4}, {10, 3}}, + expected: 5, + }, + { + name: "edge case: smallest n with no restrictions", + n: 2, + restrictions: [][]int{}, + expected: 1, + }, + { + name: "edge case: building n hard-capped at zero", + n: 5, + restrictions: [][]int{{5, 0}}, + expected: 2, + }, + { + name: "edge case: generous cap tightened by propagation", + n: 5, + restrictions: [][]int{{3, 0}}, + expected: 2, + }, + { + name: "edge case: large n unrestricted", + n: 1000000000, + restrictions: [][]int{}, + expected: 999999999, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := maxBuilding(tt.n, tt.restrictions) + if result != tt.expected { + t.Errorf("maxBuilding(%d, %v) = %d, want %d", tt.n, tt.restrictions, result, tt.expected) + } + }) + } +}