配列の最大と最小

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

配列の最大と最小

このレッスンで分かること

  • 計算量はオーダーだけでなく 定数倍 も大事
  • 配列から最大値と最小値を求めるのは、計算量の感覚を養う格好の題材です
  • 初期化のテクニックがコツです

配列の最大と最小 とは

配列の最大値と最小値を 1 回のスキャンで求める。O(n) で全要素を見る典型例。本レッスンでは、配列の最大と最小 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

配列から最大値と最小値を求めるのは、計算量の感覚を養う格好の題材です。max(arr)min(arr) を別々に呼べばよさそうに見えますが、それぞれが配列を全走査するため、比較回数は約 2n になります。1 回のループで両方同時に求めれば、配列を 2 回走査する無駄が省け、コードもまとまります。ただし比較回数自体は素朴な実装だと約 2n のままです。さらにペア比較を使えば約 1.5n に減らせます。Big-O で見るとどちらも O(n) ですが、定数倍の差は実装の質に直結します。

本レッスンでは「配列の最大値と最小値を 1 回のスキャンで求め、[min, max] の配列で返す」関数を作ります。

計算量はオーダーだけでなく 定数倍 も大事。1 回のループで済むなら 2 回まわす実装より明確に良い。

戦略

初期化のテクニックがコツです。最大値と最小値の変数を arr[0] で初期化し、i = 1 から最後まで比較します。こうすることで、空でない配列なら必ず正しい結果が返ります。

diagram (will load when visible)

Python での実装例

Python

def minMax(arr): mn = arr[0] mx = arr[0] for i in range(1, len(arr)): if arr[i] < mn: mn = arr[i] if arr[i] > mx: mx = arr[i] return [mn, mx]

このコードでは、1 回のループで比較を 2 回ずつ行い、合計 2(n-1) の比較で minmax の両方を求められます。min(arr)max(arr) を別々に呼ぶと 2n 弱なので、ほぼ同等ですが、変数が散らばらず読みやすいというメリットがあります。

JavaScript での実装例

JavaScript

function minMax(arr) { let mn = arr[0]; let mx = arr[0]; for (let i = 1; i < arr.length; i++) { if (arr[i] < mn) mn = arr[i]; if (arr[i] > mx) mx = arr[i]; } return [mn, mx]; }

組み込みの Math.min(...arr)Math.max(...arr) を使えば 1 行ですが、ここでは原理を理解するため自前で書きます。

計算量

比較回数は 2(n-1)、メモリ使用は変数 2 つ分のみで定数。よって 時間計算量 O(n)空間計算量 O(1) です。これは「配列を 1 回スキャンする」典型的なパターンで、計算量の見積もりの基準として覚えておきましょう。

配列を 1 回見るアルゴリズムは O(n) 。ループの中でループするなら O(n^2) 。これは計算量見積もりの基本ルール。

よくある間違い

1 つ目は 初期値を 0 にしてしまう こと。mn = 0mx = 0 で始めると、すべての要素が正のとき mn = 0 のままになってしまいます。arr[0] で初期化するのが安全です。

2 つ目は 空配列を考慮しない こと。arr が空なら arr[0]IndexError (Python) や undefined (JS) を踏みます。本問では空配列は来ない前提で良いですが、本番コードでは早期 return で対応します。

3 つ目は 戻り値の順番を間違える こと。[mn, mx] の順で返すと仕様で決まっているなら、[mx, mn] で返すとテストが落ちます。順序は仕様書をよく読んで合わせましょう。

やってみよう

  • [3, 1, 4, 1, 5, 9, 2, 6][1, 9] が返ることを確認する。
  • 単一要素の配列 [42] を渡すと [42, 42] が返る。
  • 負数を含む [-5, -10, -3][-10, -3] が返ることを確認する。

「1 回のスキャンで O(n)」のパターンは、合計・平均・最大連続部分列など、多くの問題で再利用される強力な道具。

Go での実装

Go

func minMax(arr []int) []int { mn := arr[0] mx := arr[0] for i := 1; i < len(arr); i++ { if arr[i] < mn { mn = arr[i] } if arr[i] > mx { mx = arr[i] } } return []int{mn, mx} }

比較回数を更に減らせるか

じつは「2 要素ずつペアにして比較する」テクニックで、比較回数を 1.5n に減らせます。隣り合う 2 つを先に比較し、小さい方を mn 候補、大きい方を mx 候補とすれば、各ペアあたり比較 3 回 (ペア内 + 2 つの候補) で済みます。素朴な方法は 2n 回、ペア法は 1.5n 回。オーダーは同じ O(n) ですが、定数倍を改善するアルゴリズム設計の好例です。

「定数倍を 25 % 縮める」工夫は、ホットパス (頻繁に呼ばれる関数) では効いてくる。普通のコードでは可読性を優先するのが正解。

再帰的アプローチ (分割統治)

配列を半分に分けて、それぞれの最小・最大を求めて統合するという分割統治のアプローチも考えられます。これは計算量は同じ O(n) ですが、再帰の練習として面白い課題です。第6章で再帰を扱うときに、この問題を再訪してみると、線形スキャンと再帰の違いがクリアに見えるでしょう。

実務での応用

ダッシュボードで「直近 24 時間の最大値と最小値を表示」したい、というよくある要件にこの関数がそのまま使えます。データが大量で O(n) でも遅いなら、データを 時間ウィンドウ で区切り、各ウィンドウの min/max を事前計算しておく セグメント木スライディング最小値 のような発展テクニックが必要になります。

よくある質問

Q. このトピックを覚える意味は何ですか?

A. 組み込みの min / max に頼るだけでなく、自前で書いて「1スキャンで何回比較が起きるか」を把握しておくと、ホットパスの計算量を肌感覚で見積もれるようになります。実務での問題解決スピードが格段に上がります。

Q. 実務でこの知識を使う場面は?

A. API の Content-Length 計算、ファイルアップロードのサイズ制限、画像処理での色深度計算など、低レベルな数値を扱う場面で頻出します。OS・ネットワーク・暗号の理解にも繋がるため、CS の共通言語と思って習得しておくと一生使える知識です。

Q. 他の章とどう繋がりますか?

A. 後続の章で扱うデータ構造(スタック・キュー)、アルゴリズム(探索・ソート)の基礎になります。「データを 32 bit でどう表現するか」「メモリにどう載せるか」が分かると、後続トピックの実装が腹落ちしやすくなります。

次のレッスン

次は 累積和で範囲合計 に進みましょう。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 最大・最小 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 最大・最小 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. 1 回のループで最小値と最大値の両方を求めること
  2. 組み込みの min() / max() / Math.min / Math.max は使わない
  3. 戻り値は [min, max] の順の長さ 2 の配列

入出力例

test-cases.txt

minMax([3,1,4,1,5,9,2,6])[1,9] minMax([42])[42,42] minMax([7,7,7])[7,7] minMax([-5,-10,-3])[-10,-3] minMax([1,2,3,4,5])[1,5] minMax([5,4,3,2,1])[1,5]

ヒント

main.py
main.py
学習モード

メモ

配列の最大と最小

⌘S で保存