1840. Maximum Building Height
題目 / Problem
中文: 你要在一條直線上建造 n 棟建築,編號 1 到 n。高度規則:
- 每棟高度是非負整數。
- 第 1 棟高度必須是 0。
- 相鄰兩棟的高度差不能超過 1。
另外有一個二維陣列 restrictions,restrictions[i] = [idᵢ, maxHeightᵢ] 表示第 idᵢ 棟的高度不能超過 maxHeightᵢ。每棟最多出現一次,且第 1 棟不會出現在 restrictions 中。求最高那棟建築的最大可能高度。
English: You build n buildings in a line, labeled 1..n. Rules: each height is a non-negative integer, building 1 must be 0, and adjacent buildings differ in height by at most 1. The array restrictions[i] = [idᵢ, maxHeightᵢ] caps building idᵢ at maxHeightᵢ. Each id appears at most once, and building 1 is never restricted. Return the maximum possible height of the tallest building.
Constraints / 限制:
- 2 <= n <= 10⁹
- 0 <= restrictions.length <= min(n - 1, 10⁵)
- 2 <= idᵢ <= n, ids are unique
- 0 <= maxHeightᵢ <= 10⁹
Worked example / 範例: n = 5, restrictions = [[2,1],[4,1]] → 2. One valid build is [0,1,2,1,2]; the tallest is 2.
名詞解釋 / Glossary
- 相鄰約束 / adjacency constraint:相鄰建築高度差 ≤ 1,意味著高度像樓梯一樣每步最多 +1 或 -1。Heights change by at most 1 per step, like a staircase.
- 可達上界 / reachable upper bound:在所有規則下,某棟建築實際能達到的最高高度。它可能比題目給的
maxHeight還低,因為會被左右鄰居「往下拉」。The true ceiling of a building, which may be lower than its givenmaxHeightbecause neighbors drag it down. - 兩次掃描 / two passes:先從左到右、再從右到左各掃一遍,用來把每個約束的高度「收緊」成真正的可達上界。Left-to-right then right-to-left sweeps that tighten each cap into its real reachable bound.
- 排序 / sorting:把約束按建築編號
id從小到大排列,方便相鄰處理。Ordering restrictions by id so neighbors are adjacent in the array. - 峰值公式 / peak formula:兩個固定點之間能堆出的最高點
(hₗ + hᵣ + d) / 2(整數除法,向下取整),d是兩點距離。The highest point achievable between two fixed buildings. - 整數溢位 / integer overflow:數字超過 32 位元
int能表示的範圍(約 21 億)就會出錯,本題的和可能達到 ~2×10⁹,需用long。Sums here can exceed 32-bit range, so use 64-bitlong.
思路
中文: 最直接的想法是逐棟枚舉高度,但 n 高達 10⁹,不可能一棟棟算,所以必須只關注那些「有約束的建築」。關鍵觀察:第 1 棟固定為 0,這其實也是一個約束 [1, 0],我們把它加進去。沒有約束的建築(包括最後一棟 n)夾在兩個約束之間,它的高度會被左右兩邊「自然地」決定,不需要單獨枚舉。
每個約束給的 maxHeight 只是「名義上限」,真正能達到的高度還受鄰居牽制。例如某棟標 maxHeight=100,但它左邊有一棟高度為 0、距離只有 3,那麼因為每步最多 +1,它最多只能到 3。所以我們做兩次掃描:先從左到右,用 h[i] = min(h[i], h[i-1] + 距離) 把每個約束收緊;再從右到左做一樣的事。兩遍之後,每個約束的高度就是它真正的可達上界,而且左右一致。
最後,答案的最高點一定落在「某兩個相鄰約束之間」。固定左端高 hₗ、右端高 hᵣ、距離 d,中間從左爬升、再下降到右,最高點滿足 hₗ + x = hᵣ + (d - x),解得峰值 (hₗ + hᵣ + d) / 2(向下取整,因為高度是整數)。對所有相鄰約束對取最大值就是答案。別忘了把最後一棟 n 也當成一個「無上限」約束加入(若它本身沒被約束),這樣它才會被計入。
English: The naive idea is to assign a height to every building, but n can be 10⁹, so we cannot touch buildings one by one — we only care about the restricted ones. The key insight: building 1 is fixed at 0, which is itself a restriction [1, 0], so we add it. Any unrestricted building (including the last one, n) sits between two restricted ones and its height is decided automatically by its neighbors; no need to enumerate it.
Each given maxHeight is only a nominal cap — the real reachable height is also limited by neighbors. A building capped at 100 cannot exceed 3 if a building of height 0 sits just 3 steps away (each step adds at most 1). So we run two passes: left-to-right, tightening with h[i] = min(h[i], h[i-1] + distance), then right-to-left doing the same. After both, every restriction holds its true reachable bound, consistent from both sides.
Finally, the global peak always lies between two adjacent restrictions. With left height hₗ, right height hᵣ, distance d, climbing up from the left and down to the right, the top satisfies hₗ + x = hᵣ + (d - x), giving peak (hₗ + hᵣ + d) / 2 (floored, since heights are integers). Take the max over all adjacent pairs. Remember to add building n as an "unbounded" restriction (if it isn't already restricted) so it gets counted.
逐步走查 / Walkthrough
Example: n = 5, restrictions = [[2,1],[4,1]].
Setup / 準備: Add [1,0] (building 1). Building 5 is not restricted, so add [5, ∞]. After sorting by id, the pairs are:
| index | id | h (initial) |
|---|---|---|
| 0 | 1 | 0 |
| 1 | 2 | 1 |
| 2 | 4 | 1 |
| 3 | 5 | ∞ |
Left pass / 左掃 h[i] = min(h[i], h[i-1] + (id[i]-id[i-1])):
| i | compute | new h |
|---|---|---|
| 1 | min(1, 0+(2-1)=1) | 1 |
| 2 | min(1, 1+(4-2)=3) | 1 |
| 3 | min(∞, 1+(5-4)=2) | 2 |
Right pass / 右掃 h[i] = min(h[i], h[i+1] + (id[i+1]-id[i])):
| i | compute | new h |
|---|---|---|
| 2 | min(1, 2+(5-4)=3) | 1 |
| 1 | min(1, 1+(4-2)=3) | 1 |
| 0 | min(0, 1+(2-1)=2) | 0 |
Final heights: (1,0) (2,1) (4,1) (5,2).
Peaks / 峰值 (hₗ + hᵣ + d) / 2:
| pair | compute | peak |
|---|---|---|
| 1→2 | (0+1+1)/2 | 1 |
| 2→4 | (1+1+2)/2 | 2 |
| 4→5 | (1+2+1)/2 | 2 |
Maximum peak = 2. ✅ Matches the expected output.
Solution — C
#include <stdlib.h>
// 演算法 / Algorithm:
// 1. 把 [1,0] 和(若需要)[n,∞] 加入約束,按 id 排序。
// Add [1,0] and (if needed) [n,∞], then sort restrictions by id.
// 2. 左右兩次掃描,把每個約束收緊成真正的可達上界。
// Two passes (left, right) tighten each cap to its real reachable bound.
// 3. 對每對相鄰約束用峰值公式 (hl+hr+d)/2 取最大。
// For each adjacent pair, the peak is (hl+hr+d)/2; take the max.
// 每個約束存成一個結構:id 與高度都用 long 避免溢位。
// One restriction: id and height stored as long to avoid 32-bit overflow.
typedef struct { long id, h; } Pair;
// qsort 用的比較函式:依 id 由小到大排序。
// Comparator for qsort: order by id ascending.
static int cmp(const void *a, const void *b) {
Pair *pa = (Pair *)a, *pb = (Pair *)b; // 把 void* 轉回 Pair* / cast back to Pair*
if (pa->id < pb->id) return -1; // a 在前 / a comes first
if (pa->id > pb->id) return 1; // b 在前 / b comes first
return 0; // 相等(不會發生,id 唯一)/ equal (won't happen, ids unique)
}
long lmin(long a, long b) { return a < b ? a : b; } // 取兩個 long 的較小值 / min of two longs
int maxBuilding(int n, int** restrictions, int restrictionsSize, int* restrictionsColSize) {
// 最多需要 原約束數 + 2 個位置(多出 [1,0] 與 [n,∞])。
// Need at most restrictionsSize + 2 slots (extra [1,0] and [n,∞]).
Pair *r = (Pair *)malloc(sizeof(Pair) * (restrictionsSize + 2));
int m = 0; // m = 目前已放入的數量 / current count
r[m].id = 1; r[m].h = 0; m++; // 第 1 棟固定為 0 / building 1 fixed at height 0
int hasN = 0; // 標記 n 是否已被約束 / does n already appear?
for (int i = 0; i < restrictionsSize; i++) { // 複製所有給定約束 / copy all given restrictions
r[m].id = restrictions[i][0]; // restrictions[i][0] 是建築編號 / the building id
r[m].h = restrictions[i][1]; // restrictions[i][1] 是高度上限 / the height cap
if (r[m].id == n) hasN = 1; // 若剛好是最後一棟,記下 / note if it's building n
m++;
}
// 若最後一棟沒被約束,加入一個「幾乎無限」的上限,讓掃描自然算出它的高度。
// If building n isn't restricted, add a near-infinite cap so the passes compute it.
if (!hasN) { r[m].id = n; r[m].h = 2000000000L; m++; }
// 依 id 排序,使相鄰約束在陣列中也相鄰。
// Sort by id so neighbors are adjacent in the array.
qsort(r, m, sizeof(Pair), cmp);
// 左掃:高度不能比「左鄰高度 + 兩者距離」還高(每步最多 +1)。
// Left pass: a height can't exceed left-neighbor's height + their distance.
for (int i = 1; i < m; i++)
r[i].h = lmin(r[i].h, r[i-1].h + (r[i].id - r[i-1].id));
// 右掃:同理,從右鄰往回收緊。
// Right pass: tighten from the right neighbor likewise.
for (int i = m - 2; i >= 0; i--)
r[i].h = lmin(r[i].h, r[i+1].h + (r[i+1].id - r[i].id));
// 對每對相鄰約束算峰值,取全域最大。
// For each adjacent pair compute the peak; keep the global max.
long best = 0;
for (int i = 0; i + 1 < m; i++) {
long d = r[i+1].id - r[i].id; // 兩棟之間的距離 / distance between the two
long peak = (r[i].h + r[i+1].h + d) / 2; // 峰值公式,整數除法向下取整 / peak, floored
if (peak > best) best = peak; // 更新答案 / update answer
}
free(r); // 釋放配置的記憶體,避免洩漏 / free heap memory
return (int)best; // 結果在 int 範圍內,安全轉回 / fits in int
}
Solution — C++
#include <vector>
#include <array>
#include <algorithm>
using namespace std;
// 演算法同 C 版 / Same algorithm as the C version:
// 加入 [1,0] 與 [n,∞] → 排序 → 左右兩掃收緊上界 → 相鄰對取峰值最大。
// Add [1,0] and [n,∞] → sort → two passes tighten bounds → max peak over adjacent pairs.
class Solution {
public:
int maxBuilding(int n, vector<vector<int>>& restrictions) {
// vector<array<long,2>>:每個元素是 {id, height},用 long 防止溢位。
// vector<array<long,2>>: each element is {id, height}; long avoids overflow.
vector<array<long, 2>> r;
r.reserve(restrictions.size() + 2); // 預留空間,避免多次擴容 / reserve to avoid reallocs
r.push_back({1, 0}); // 第 1 棟固定為 0 / building 1 fixed at 0
bool hasN = false; // n 是否已被約束 / is building n restricted?
for (auto& res : restrictions) { // range-for:逐一取出每條約束 / iterate each restriction
r.push_back({res[0], res[1]}); // res[0]=id, res[1]=maxHeight
if (res[0] == n) hasN = true; // 記錄是否含最後一棟 / note building n
}
// 最後一棟若無約束,加上「近乎無限」上限讓掃描算出真正高度。
// If n unrestricted, add a near-infinite cap so passes derive its true height.
if (!hasN) r.push_back({(long)n, 2000000000L});
// 用 lambda 依 id 排序;[](){} 是匿名函式,當作比較器傳給 sort。
// Sort by id using a lambda; [](){} is an anonymous function used as comparator.
sort(r.begin(), r.end(),
[](const array<long,2>& a, const array<long,2>& b){ return a[0] < b[0]; });
int m = r.size(); // 約束總數 / total number of restrictions
// 左掃:高度不能超過 左鄰 + 距離(每步最多差 1)。
// Left pass: cap by left neighbor's height plus distance.
for (int i = 1; i < m; i++)
r[i][1] = min(r[i][1], r[i-1][1] + (r[i][0] - r[i-1][0]));
// 右掃:同理從右鄰收緊。
// Right pass: tighten from the right neighbor.
for (int i = m - 2; i >= 0; i--)
r[i][1] = min(r[i][1], r[i+1][1] + (r[i+1][0] - r[i][0]));
// 相鄰對的峰值公式 (hl+hr+d)/2,取最大。
// Peak (hl+hr+d)/2 for each adjacent pair; take the maximum.
long best = 0;
for (int i = 0; i + 1 < m; i++) {
long d = r[i+1][0] - r[i][0]; // 兩棟距離 / distance
best = max(best, (r[i][1] + r[i+1][1] + d) / 2); // 更新最大峰值 / update max peak
}
return (int)best; // 回傳結果 / return answer
}
};
複雜度 / Complexity
- Time: O(k log k) —
k是restrictions的數量(最多 10⁵)。瓶頸是排序的O(k log k);兩次掃描與峰值計算都只是O(k)。注意我們完全不依賴n,所以即使n = 10⁹也很快。The dominant cost is sorting thekrestrictions; the two passes and peak scan are linear. Crucially, runtime is independent ofn, son = 10⁹is fine. - Space: O(k) — 額外用一個陣列存放約束(加上 2 個哨兵)。We store one array of the restrictions plus 2 sentinels.
Pitfalls & Edge Cases
- 整數溢位 / Integer overflow:
h + d可能達到 ~2×10⁹,超過 32 位元int(上限約 2.1×10⁹)。若用int相加會變成負數導致錯誤答案 — 程式全程使用long。Sums likeh + distancecan exceed 32-bit range; usingintwould wrap to negatives. We uselongthroughout. - 忘記第 1 棟的約束 / Forgetting building 1:第 1 棟高度必為 0,這是隱含的約束
[1,0],不加入就會算出過高的答案。Building 1 = 0 is an implicit[1,0]; omitting it overestimates. - 忘記最後一棟 n / Forgetting building n:若
n沒被約束,它仍可能是最高點(如範例 2 一路爬升)。我們補上[n, ∞]讓它被掃描與峰值計算涵蓋。Ifnis unrestricted it can still be the tallest (Example 2 climbs all the way); we add[n, ∞]so it's covered. - 必須做兩次掃描 / Both passes are required:只做左掃會漏掉「右邊更矮的鄰居把高度往下拉」的情況;只做右掃則漏掉左邊。Doing only one direction misses the constraint coming from the other side.
- 峰值用整數除法 / Floor the peak:
(hl+hr+d)/2必須向下取整,因為高度是整數;C/C++ 對非負數的整數除法本來就向下取整,剛好正確。Heights are integers, so the peak floors — integer division of non-negatives already does this. - 空 restrictions / Empty restrictions:此時只剩
[1,0]與[n,∞]兩個哨兵,公式仍正確(答案為n-1)。With no restrictions, only the two sentinels remain and the formula still givesn-1.