問題
小さいnums1 と nums2 を
- 中央値は、
合わせた 個数が 奇数なら 真ん中の 値、 偶数なら 真ん中の 2 つの平均です - 戻り値は
数です。 平均が 小数に なる ときは 6.5のように返します。 ちょうど 整数に なる ときは、 6と返しても 6.0と返しても 同じと 見ます (例の 出力は 6と書きます) nums1とnums2の長さは それぞれ 0 以上 1000 以下で、 合わせて 1 つ以上 あります。 値は どれも -1000000 以上 1000000 以下です - 長さを m と n として、
2 つを まとめる O(m + n) ではなく、 O(log(min(m, n))) で 求めてください
入力と出力の例
例 1
合わせると [1, 2, 4, 6, 9] の 5 つで、
入力
nums1 = [1, 4, 9]
nums2 = [2, 6]出力
4例 2
合わせると [3, 5, 8, 10] の 4 つで、
入力
nums1 = [3, 8]
nums2 = [5, 10]出力
6.5例 3
片方が空です。
入力
nums1 = []
nums2 = [7]出力
7面接での聞かれ方
面接では、
- 「ソート済みの
配列が 2 つあります。 まとめた ときの 中央値を 求めてください」 - 最初に
まとめる 書き方を 出すと、 「O(m + n) は 分かりました。 対数時間に できますか?」と 一段下げるよう 求められます - 書き終えた
あとに 「片方が空なら?」 「なぜ短い方で 探すのですか?」と、 端の 場合と 理由を 確かめられます
この
アルゴリズムのチートシートの「ソートと二分探索」に、この型の要点を表とコードでまとめています。
まず 考える手順
段 1
まずは
段 2
見方をnums1 のnums2 のhalf = (m + n + 1) // 2 とhalf - i で
段 3
i がnums1 のal、ar、nums2 もbl と br とal <= br かつ bl <= ar ならal > br なら nums1 からbl > ar なら
段 4
切れ目がmax(al, bl)、min(ar, br) の
解答
Python
Python
def find_median(nums1, nums2):
a, b = nums1, nums2
if len(a) > len(b):
a, b = b, a
m, n = len(a), len(b)
half = (m + n + 1) // 2
inf = float("inf")
def at(x, k):
if k < 0:
return -inf
if k >= len(x):
return inf
return x[k]
lo, hi = 0, m
while lo <= hi:
i = (lo + hi) // 2
j = half - i
al = at(a, i - 1)
ar = at(a, i)
bl = at(b, j - 1)
br = at(b, j)
if al > br:
hi = i - 1
elif bl > ar:
lo = i + 1
else:
left = max(al, bl)
if (m + n) % 2 == 1:
return left
right = min(ar, br)
total = left + right
return total / 2JavaScript
JavaScript
function findMedian(
nums1, nums2
) {
let a = nums1;
let b = nums2;
if (a.length > b.length) {
[a, b] = [b, a];
}
const m = a.length;
const n = b.length;
const half = (m + n + 1) >> 1;
const { max, min } = Math;
const at = (x, k) => {
if (k < 0) {
return -Infinity;
}
if (k >= x.length) {
return Infinity;
}
return x[k];
};
let lo = 0;
let hi = m;
while (lo <= hi) {
const i = (lo + hi) >> 1;
const j = half - i;
const al = at(a, i - 1);
const ar = at(a, i);
const bl = at(b, j - 1);
const br = at(b, j);
if (al > br) {
hi = i - 1;
} else if (bl > ar) {
lo = i + 1;
} else {
const left = max(al, bl);
if ((m + n) % 2 === 1) {
return left;
}
const right = min(ar, br);
return (left + right) / 2;
}
}
return 0;
}Go
Go
import "math"
func findMedian(
nums1, nums2 []int,
) float64 {
a, b := nums1, nums2
if len(a) > len(b) {
a, b = b, a
}
m, n := len(a), len(b)
half := (m + n + 1) / 2
at := func(x []int, k int,
) int {
if k < 0 {
return math.MinInt
}
if k >= len(x) {
return math.MaxInt
}
return x[k]
}
lo, hi := 0, m
for lo <= hi {
i := (lo + hi) / 2
j := half - i
al := at(a, i-1)
ar := at(a, i)
bl := at(b, j-1)
br := at(b, j)
if al > br {
hi = i - 1
} else if bl > ar {
lo = i + 1
} else {
left := max(al, bl)
if (m+n)%2 == 1 {
return float64(left)
}
right := min(ar, br)
s := float64(left + right)
return s / 2
}
}
return 0
}正しいreturn 0 は、
計算量
- 時間は O(log(min(m, n))) です。
短い方の 並びの 切る 位置 0 から m までを、 毎回 半分に狭めます - 使う
記憶は O(1) です。 並びを 写さず、 位置と 値の 置き場だけを使います
よく ある間違い
- 長い方で i を探す。
長い方を 切ると j = half - iが負に なったり、 短い方の 長さを 超えたりして、 範囲の 外を 読みます。 最初に 短い方を aにそろえます halfを(m + n) // 2にする。 合わせた個数が 奇数の とき、 真ん中の 値が 右の 半分に 入り、 max(al, bl)では取れません。 (m + n + 1) // 2にして、 奇数の ときは 左の 半分を 1 つ 多くします - 端の
値の 置き換えを忘れる。 i が 0 や m の とき、 a[i - 1]やa[i]は無い所です。 Python の a[-1]は止まらずに 末尾を 返すので、 間違いに 気づきにくくなります - 整数どうしの
割り算で 小数を落とす。 Go で (left + right) / 2と整数のまま 割ると、 6.5 が 6 に なります。 先に float64に直してから割ります
解答を動かした版 Python 3.12.14Node.js 22.23.4Go 1.24.6確認日 2026年10月30日
面接での 話し方の例
書き始める
「まと
めれば O(m + n) ですが、 中央値は 合わせた 並びを 左右半分に 分ける 切れ目だけで 決まります。 左の 半分の 個数は 決まっているので、 短い方の 並びを 何個取るかの i を 決めれば、 もう 片方は 自動で 決まります。 切れ目の 両側の 値の 大小で i を 増やすか 減らすかが 分かるので、 i を 二分 探索します。 時間は O(log(min(m, n)))、 記憶は O(1) です。」
書き終えたら、
「例 1 では
短い方は [2, 6]で、左の 半分は 3 個です。 i を 1 に すると、 短い方から 2、 長い方から 1 と 4 を 取ります。 2 は 9 以下、 4 は 6 以下なので 正しい 切り方です。 合わせて 5 個で 奇数なので、 左の 最大の 4 が 答えです。」
