問題
整数のnums と、k をnums を
- 同じ
値も それぞれ 1 つと 数えます。 [9, 9, 7]で 2 番目に大きい値は 9 です numsの長さは 1 以上 100000 以下、 値は どれも -10000 以上 10000 以下です kは 1 以上、numsの長さ以下です - 全部を
並べ替える O(n log n) ではなく、 ヒープを 使って O(n log k) で 求めてください
入力と出力の例
例 1
大きい順に9, 9, 7, 4, 2, 1 で、
入力
nums = [7, 2, 9, 4, 9, 1]
k = 2出力
9例 2
大きい順に 8, 6, 5, 3 で、
入力
nums = [5, 3, 8, 6]
k = 3出力
5例 3
値が 1 つだけです。
入力
nums = [-4]
k = 1出力
-4面接での聞かれ方
面接では、
- 「配列の
中で k 番目に 大きい要素を 返してください。 並べ替えずに できますか?」 - ヒープの
書き方を 出すと、 「なぜ 大きい順ではなく 小さい 順の ヒープなのですか?」と 理由を 聞かれます - 書き終えた
あとに 「値が 流れてくる (全部が 先に そろわない ) としたら?」 「平均 O(n) の 書き方は 知っていますか?」と 広げて 聞かれます
同じ値を[9, 9, 7] の 2 番目は 9 か 7 か
アルゴリズムのチートシートの「スタックとキュー」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは
段 2
「今までに
段 3
「いちばん
段 4
ヒープのheapq がcontainer/heap に、
解答
Python
Python
from heapq import heappush
from heapq import heapreplace
def kth_largest(nums, k):
heap = []
for x in nums:
if len(heap) < k:
heappush(heap, x)
elif x > heap[0]:
heapreplace(heap, x)
return heap[0]JavaScript
JavaScript
function kthLargest(nums, k) {
const h = [];
for (const x of nums) {
if (h.length < k) {
h.push(x);
up(h, h.length - 1);
} else if (x > h[0]) {
h[0] = x;
down(h, 0);
}
}
return h[0];
}
function up(h, i) {
while (i > 0) {
const p = (i - 1) >> 1;
if (h[p] <= h[i]) {
return;
}
[h[p], h[i]] = [h[i], h[p]];
i = p;
}
}
function down(h, i) {
const n = h.length;
while (true) {
let s = i;
const l = 2 * i + 1;
const r = l + 1;
if (l < n && h[l] < h[s]) {
s = l;
}
if (r < n && h[r] < h[s]) {
s = r;
}
if (s === i) {
return;
}
[h[s], h[i]] = [h[i], h[s]];
i = s;
}
}Go
Go
import "container/heap"
type minHeap []int
func (h minHeap) Len() int {
return len(h)
}
func (h minHeap) Less(
i, j int,
) bool {
return h[i] < h[j]
}
func (h minHeap) Swap(
i, j int,
) {
h[i], h[j] = h[j], h[i]
}
func (h *minHeap) Push(x any) {
*h = append(*h, x.(int))
}
func (h *minHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func kthLargest(
nums []int, k int,
) int {
h := &minHeap{}
for _, x := range nums {
if h.Len() < k {
heap.Push(h, x)
} else if x > (*h)[0] {
(*h)[0] = x
heap.Fix(h, 0)
}
}
return (*h)[0]
}Python の heapreplace と Go の heap.Fix、down は、
計算量
- 時間は O(n log k) です。
n 個の 値それぞれに ついて、 大きさ k 以下の ヒープを 直すのが O(log k) です - 使う
記憶は O(k) です。 ヒープに 持つのは k 個までです
よく ある間違い
- 大きい順の
ヒープに 全部入れる。 全部を 入れてから k 回取り出すと O(n + k log n) で、 記憶も O(n) 使います。 間違いでは ありませんが、 小さい 順の ヒープに k 個だけ 持つ 書き方と 比べて 理由を 言えるようにします - 同じ値を 1 つに
まとめる。 集合に 入れて 重複を 消してから 数えると、 例 1 は 7 を 返してしまいます。 この 問題は 同じ 値も それぞれ数えます - 入れ替えの
条件を >=にする。 根と同じ 値で 入れ替えても 結果は 変わりませんが、 無駄に ヒープを 直します。 >で十分です - JavaScript で
配列を 毎回並べ替える。 足すたびに sortすると、1 回で O(k log k) かかり、 ヒープに した 意味が なくなります
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「並べ替えれば O(n log n) ですが、
欲しいのは 上位 k 個の 中の いちばん 小さい 値だけです。 そこで、 大きい方の k 個だけを 小さい 順の ヒープに 持ち、 根より 大きい値が 来たら 根と 入れ替えます。 最後に 根を 返します。 ヒープの 大きさは k までなので、 時間は O(n log k)、 記憶は O(k) です。」
書き終えたら、
「例 1 で k は 2 です。
7 と 2 を 入れて、 根は 2 です。 9 は 2 より 大きいので 入れ替え、 ヒープは 7 と 9 に なります。 4 は 根の 7 より 小さいので 捨てます。 次の 9 は 7 より 大きいので 入れ替え、 9 と 9 に なります。 1 は 捨てて、 根の 9 が 答えです。」
