問題
整数のnums と、k をnums の
- 返す順は、
出てくる 回数の 多い順です。 回数が 同じ 値どうしは、 値の 小さい 順に並べます - 回数が
同じ値が k 個目を またぐ ときも、 この 順で 前から k 個を 選びます。 例 2 を 見てください numsの長さは 1 以上 100000 以下、 値は どれも -10000 以上 10000 以下です kは 1 以上、numsにある 違う 値の 数以下です - 違う
値の 数を u として、 全部を 並べ替える O(u log u) より 速い、 O(n log k) を めざしてください
入力と出力の例
例 1
4 は 3 回、
入力
nums = [4, 1, 4, 2, 2, 4, 3]
k = 2出力
[4, 2]例 2
5 と 7 は 2
入力
nums = [7, 5, 9, 7, 5]
k = 1出力
[5]例 3
値が 1 つだけです。
入力
nums = [-3]
k = 1出力
[-3]面接での聞かれ方
面接では、
- 「配列の
中で、 出現回数が 多い 上位 k 個の 要素を 返してください」 - 最初に
並べ替えの 書き方を 出すと、 「O(n log n) より 速く できますか?」と 寄せられます - 書き終えた
あとに 「回数が 同じ ときは どう 並べますか?」 「回数の 上限が n なのを 使うと、 もっと 速くなりませんか?」と 広げて 聞かれます
返す順が
アルゴリズムのチートシートの「スタックとキュー」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは
段 2
欲しいのは
段 3
最後に、(回数, -値) の- を
段 4
JavaScript にはless をcontainer/heap に、
解答
Python
Python
from collections import Counter
from heapq import heappush
from heapq import heappop
def top_k_frequent(nums, k):
count = Counter(nums)
heap = []
for v, c in count.items():
heappush(heap, (c, -v))
if len(heap) > k:
heappop(heap)
out = []
while heap:
c, v = heappop(heap)
out.append(-v)
return out[::-1]JavaScript
JavaScript
function topKFrequent(nums, k) {
const count = new Map();
for (const x of nums) {
const c = count.get(x) ?? 0;
count.set(x, c + 1);
}
const h = [];
for (const [v, c] of count) {
push(h, [c, v]);
if (h.length > k) {
pop(h);
}
}
const out = [];
while (h.length > 0) {
out.push(pop(h)[1]);
}
return out.reverse();
}
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"
type pair struct {
c, v int
}
type pairs []pair
func (h pairs) Len() int {
return len(h)
}
func (h pairs) Less(
i, j int,
) bool {
if h[i].c != h[j].c {
return h[i].c < h[j].c
}
return h[i].v > h[j].v
}
func (h pairs) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *pairs) Push(x any) {
*h = append(*h, x.(pair))
}
func (h *pairs) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func topKFrequent(
nums []int, k int,
) []int {
count := map[int]int{}
for _, x := range nums {
count[x]++
}
h := &pairs{}
for v, c := range count {
heap.Push(h, pair{c, v})
if h.Len() > k {
heap.Pop(h)
}
}
n := h.Len()
out := make([]int, n)
for i := n - 1; i >= 0; i-- {
p := heap.Pop(h).(pair)
out[i] = p.v
}
return out
}3 言語とも、
計算量
- 時間は O(n + u log k) です。
u は 違う値の 数で、 u は n 以下なので O(n log k) に 収まります。 数えるのが O(n)、 u 個の 組を それぞれ 大きさ k 程度の ヒープに 出し入れするのが O(log k) です - 使う
記憶は O(u + k) です。 回数の 辞書に u 個、 ヒープに k 個を持ちます
よく ある間違い
- 同じ
回数の 並べ方を 決めない。 回数だけで 比べると、 例 2 で 5 と 7 の どちらが 残るかが、 辞書を たどる 順で 変わります。 比べ方に 値も 入れて、 順を 1 つに決めます - ヒープの
向きを 逆にする。 回数の 多い 物が 根に 来る ヒープに k 個だけ 持つと、 捨てる 物が 「いちばん強い候補」に なり、 上位を 捨ててしまいます。 根には 捨てたい 物 (いちばん弱い 候補) を置きます - 取り出した
順のまま返す。 弱い方から 取り出しているので、 そのまま 返すと 回数の 少ない 順に なります。 逆に するか 後ろから詰めます - Python で
値の 向きを 逆に し忘れる。 (回数, 値)のまま 入れると、 回数が 同じ とき 小さい 値が 弱いことに なり、 同じ 回数の 値の うち 大きい方が 残ります。 -値で入れて、 戻すときに -を外します
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「まず
辞書で 回数を 数えます。 全部を 並べ替えると 違う 値の 数を u として O(u log u) なので、 上位 k 個だけを、 いちばん弱い 候補が 根に 来る ヒープに 持ちます。 弱いとは 回数が 少ない こと、 同じなら 値が 大きい ことです。 k 個を 超えたら 根を 捨て、 最後に 取り出して 逆に 並べます。 時間は O(n log k)、 記憶は 辞書の 分の O(u) です。」
書き終えたら、
「例 2 で k は 1 です。
回数は 7 と 5 が 2 回、 9 が 1 回です。 7 を 入れ、 次に 5 を 入れると 2 つに なるので、 弱い方を 捨てます。 回数が 同じなので 値の 大きい 7 が 弱く、 捨てられます。 9 は 入れて すぐ、 回数 1 で 弱いので 捨てられ、 5 が 残ります。」
