最小ヒープで 点を 含む 最も 短い 区間を答える
電波の
海外で定番の問題リスト 4 本には入っていません
問題
まっすぐな[左端, 右端] のranges で右端 - 左端 + 1 です。points もpoints と
- 地点を
含む範囲が 1 つも 無ければ、 その 地点の 答えは -1です rangesもpointsも並べ替えられていません。 pointsには同じ値が 何度も 出る ことがあります - どの
組も、 左端は 右端以下です rangesとpointsの長さは どちらも 1 以上 100000 以下、 値は どれも 1 以上 10000000 以下です
入力と出力の例
例 1
地点 4 には [1, 6] (長さ 6) と [3, 4] (長さ 2) が[8, 8] (長さ 1) が[1, 6] だけで 6、
例 2
同じ
例 3
地点 20 は [10, 20] の[10, 20] が[15, 30] の 16 だけが
面接での聞かれ方
面接では、
- 「区間の
並びと 問い 合わせの 並びが あります。 問い 合わせごとに、 それを 含む 最も 短い 区間の 長さを 答えてください」 - 「1 つずつ
全部の 区間を 調べても よいですか」と 聞くと、 「どちらも 10 万個あります」と 返ってくるのが ふつうです - 書き終えた
あとに 「問い 合わせが 1 つずつ 届き、 先を 読めないなら どうしますか」と、 続きを 聞かれる こともあります
問い
ヒント
まだ開いていません
付箋は 3 枚。下の段ほど答えに近づきます
ヒント 2
ヒント 3答えに近い
アルゴリズムのチートシートの「配列と文字列」に、
関わる学び
配列と
まず 考える手順
段 1
素直に
段 2
地点を
段 3
範囲を(長さ, 右端) の-1 です。
解答
Python
Python
from heapq import heappush
from heapq import heappop
def smallest_interval(ranges, points):
items = sorted(ranges)
order = sorted(
range(len(points)),
key=lambda k: points[k],
)
out = [-1] * len(points)
heap = []
i = 0
for k in order:
x = points[k]
while (
i < len(items)
and items[i][0] <= x
):
lo, hi = items[i]
heappush(
heap, (hi - lo + 1, hi)
)
i += 1
while heap and heap[0][1] < x:
heappop(heap)
if heap:
out[k] = heap[0][0]
return outJavaScript
JavaScript
function smallestInterval(
ranges, points
) {
const items = [...ranges];
items.sort(
(p, q) => p[0] - q[0],
);
const order = points.map(
(_, k) => k,
);
order.sort(
(a, b) => points[a] - points[b],
);
const out = points.map(() => -1);
const h = [];
let i = 0;
for (const k of order) {
const x = points[k];
while (
i < items.length &&
items[i][0] <= x
) {
const [lo, hi] = items[i];
push(h, [hi - lo + 1, hi]);
i++;
}
while (
h.length > 0 && h[0][1] < x
) {
pop(h);
}
if (h.length > 0) {
out[k] = h[0][0];
}
}
return out;
}
function less(a, b) {
if (a[0] !== b[0]) {
return a[0] < b[0];
}
return a[1] < b[1];
}
function push(h, x) {
h.push(x);
let i = h.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (!less(h[i], h[p])) {
return;
}
[h[p], h[i]] = [h[i], h[p]];
i = p;
}
}
function pop(h) {
const top = h[0];
const last = h.pop();
const n = h.length;
if (n === 0) {
return top;
}
h[0] = last;
const lt = (a, b) =>
less(h[a], h[b]);
let i = 0;
while (true) {
let s = i;
const l = 2 * i + 1;
const r = l + 1;
if (l < n && lt(l, s)) {
s = l;
}
if (r < n && lt(r, s)) {
s = r;
}
if (s === i) {
return top;
}
[h[s], h[i]] = [h[i], h[s]];
i = s;
}
}Go
Go
import (
"container/heap"
"slices"
)
type span struct {
size, hi int
}
type spans []span
func (h spans) Len() int {
return len(h)
}
func (h spans) Less(
i, j int,
) bool {
if h[i].size != h[j].size {
return h[i].size < h[j].size
}
return h[i].hi < h[j].hi
}
func (h spans) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *spans) Push(x any) {
*h = append(*h, x.(span))
}
func (h *spans) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func smallestInterval(
ranges [][]int, points []int,
) []int {
items := slices.Clone(ranges)
slices.SortFunc(items, func(
p, q []int,
) int {
return p[0] - q[0]
})
n := len(points)
order := make([]int, n)
for k := range order {
order[k] = k
}
slices.SortFunc(order, func(
a, b int,
) int {
return points[a] - points[b]
})
out := make([]int, n)
h := &spans{}
i := 0
for _, k := range order {
x := points[k]
for i < len(items) &&
items[i][0] <= x {
lo, hi := items[i][0],
items[i][1]
heap.Push(h,
span{hi - lo + 1, hi})
i++
}
for h.Len() > 0 &&
(*h)[0].hi < x {
heap.Pop(h)
}
out[k] = -1
if h.Len() > 0 {
out[k] = (*h)[0].size
}
}
return out
}3 言語とも、
計算量
- 時間は O(n log n + q log q) です。
範囲と 地点の 並べ替えに O(n log n) と O(q log q)、 どの 範囲も ヒープに 1 回入って 多くとも 1 回出るので、 ヒープの 出し入れは 合わせて O(n log n) です - 使う
記憶は O(n + q) です。 ヒープと 範囲の 写しが 最悪 n 個、 地点の 番号の 並びと 答えが q 個を持ちます - 地点を
値の 順に 並べ替えるのは、 地点を 先に 全部受け取れるからです。 1 つずつ届く 場合は、 区間木のような 別の 道具が要ります
よく ある間違い
- 地点を
並べ替えて、 答えも 並べ替えた順で返す。 例 1 で 地点を 2、 4、 8、 10 の 順に 答えると [6, 2, 1, -1]になります。 元の 番号を 覚えて おき、 その 場所に 答えを書きます - 右端が
地点ちょうどの 範囲を捨てる。 根を 捨てる 条件を hi <= xと書くと、 例 3 の 地点 20 で [10, 20]を捨て、 答えが 16 に なります。 範囲は 右端を 含むので、 捨てるのは 右端が 地点より 小さい ときだけです - 長さを
右端 - 左端で数える。 両端を含むので 1 を 足します。 足し忘れると、 例 1 の [8, 8]の長さが 0 になります - 届かなくなった
範囲を 地点ごとに ヒープから 全部探して消す。 ヒープの 中を 全部 なめると、 1 つの 地点で O(n) かかり、 段 1 と 変わらなくなります。 根だけを 見て、 根が 届かない ときだけ捨てれば足ります
解答を動かした版 Python 3.14.7Node.js 24.22.1Go 1.24.2確認日 2026年11月8日
面接での 話し方の例
書き始める
「地点ごとに
全部の 範囲を 調べると O(nq) で 間に 合いません。 地点を 左から 順に 答えると、 届き始めた 範囲は それ以降も ずっと 届き 始めたまま、 右端を 過ぎた 範囲は それ以降ずっと 届かないので、 範囲は 左端の 順、 地点は 値の 順に 並べます。 届き始めた 範囲を 長さの 短い順の 最小ヒープに 入れ、 根の 右端が 地点より 小さければ 捨てて、 残った 根の 長さが 答えです。 地点は 元の 番号を 覚えて おき、 その 場所に 書きます。 時間は O(n log n + q log q) です。」
書き終えたら、
「例 1 では、
地点を 2、 4、 8、 10 の 順に 見ます。 地点 2 では [1, 6]を入れ、 根の 長さ 6 が 答えです。 地点 4 では [3, 4]を入れ、 根は 長さ 2 で 右端 4 は 4 以上 なので 2 です。 地点 8 では [5, 9]と[8, 8]を入れます。 根の [8, 8]は長さ 1 で 右端 8 なので 1 です。 地点 10 では、 根の [8, 8]、[3, 4]、[5, 9]、[1, 6]が順に 右端が 10 より 小さいので 全部捨て、 ヒープが 空なので -1 です。 元の 順に 戻すと [2, 1, 6, -1]です。」