プログラミング学習中のみなさん、こんにちは!
今回は、日本情報オリンピック(JOI)の難易度4レベルまでの過去問を題材に、初心者でもスッキリ理解できる「プログラミング的思考法」を解説していきます!
JOIの問題を解くとき、こんな風に考えていませんか?
- 「とりあえず結果を保存する配列を作らなきゃ…」
- 「条件ごとにグループ分けして配列に分けて…」
実はそれ、コードを無駄に複雑にしている原因かもしれません!
今日は生徒のKくんと一緒に、「無駄な変数を作らず、シンプルに最短コードで解くコツ」を学んでいきましょう!
【第1問】文字の置き換え問題
【問題】
文字列のJをOに、OをIに、IをJに変換した後の文字列を出力してください。
Kくん:
「まずは文字列を1文字ずつ受け取って、配列(リスト)に格納してから変換していけばいいのかな?」
講師:
「おっ、処理の流れはバッチリ!でもね、“出力するためだけにわざわざ新しいリストを作る必要はない”んだよ。」
Kくん:
「えっ、リストを作らなくていいの?」
講師:
「 Pythonの文字列は for 文でそのまま1文字ずつ取り出せるんだ。取り出した文字をその場で判定して、改行なしで出力しちゃえばOK!Python 3.10以降なら match-case を使うとスッキリ書けるよ。」
💡 講師の模範コード(Python)
N = int(input())
S = input()
for char in S:
match char:
case "J":
print("O", end="")
case "O":
print("I", end="")
case "I":
print("J", end="")
print() # 最後に改行
Kくん:
「配列を作らない分、コードも短くてメモリの無駄もないね!」
【第2問】2つの数列を交互に並べる問題
【問題】
長さ $N$ の整数列 $A = (A_1, A_2, \dots, A_N)$ と $B = (B_1, B_2, \dots, B_N)$ が与えられます。
$A$ と $B$ を交互に並べた長さ $2N$ の列 $C = (A_1, B_1, A_2, B_2, \dots, A_N, B_N)$ を出力してください。
Kくん:
「これは新しい配列 $C$ を作って、そこに $A_1, B_1, A_2, B_2\dots$ の順番で追加していけば完璧だよね!」
講師:
「ここでも思考のシンプル化が使えるよ!問題文に『列 $C$ を出力せよ』とあるけど、後から列 $C$ を再利用したり編集したりする指示はあるかな?」
Kくん:
「ううん、出力するだけだね。」
講師:
「だったら、配列 $C$ を作る時間もメモリも勿体ない! 繰り返し処理の中で、$A_i$ と $B_i$ をそのまま交互にプリントしちゃえばいいんだよ。」
💡 講師の模範コード(Python)
N = int(input())
A = list(map(int, input().split()))
B = list(map(int, input().split()))
for i in range(N):
print(A[i])
print(B[i])
Kくん:
「うわ、たったこれだけで終わっちゃった…!『出力するためだけの変数・配列は作らない』のがコツなんだね。」
【第3問】クラス分けとボーダーライン問題
【問題】
$N$ 人の生徒の点数が与えられます。ある点数 $x$ を基準にして、 $x$ 点以上を進学クラス、$x$ 点未満を普通クラスに分けます。
- 両方のクラスに1人以上の生徒が属する
- 進学クラスと普通クラスの人数差が最小になる
- 条件を満たす分け方が複数ある場合、進学クラスの人数が最小になる分け方を選ぶ
このとき、進学クラスの最低点(=基準となる $x$)を求めてください。
Kくん:
「う〜ん、進学クラス用の配列と普通クラス用の配列を準備して、点数を比較しながら分けていけばいいのかな…?」
講師:
「今回求められている最終出力は何だっけ?」
Kくん:
「『進学クラスの最低点』だから、数字1個だけだ!」
講師:
「そう!数字1個を答えるだけだから、生徒を実際に別々の配列に振り分ける必要はないんだよ。」
Kくん:
「じゃあどうやって考えるの?」
講師:
「ポイントは次の3ステップ!
- 点数を降順(高い順)にソートする
- 人数が半分(二等分)になるあたりを基準に考える
- 同じ点数の生徒が境界線にまたがらないよう注意しながら、進学クラスの人数が少なくなるギリギリの点数を見つける」
💡 講師の模範コード(Python)
N = int(input())
A = list(map(int, input().split()))
# 点数を高い順(降順)に並べ替える
A.sort(reverse=True)
# 人数が均等になるインデックス(中央付近)から探す
half = N // 2
# 同じ点数の人が境界にいる場合は、進学クラス側を減らす方向(インデックスを前)へ調整
while half > 1 and A[half - 1] == A[half]:
half -= 1
# 進学クラスの最低点を出力
print(A[half - 1])
Kくん:
「ソートしてから位置を計算するだけで求まるんだ!配列を2つ作ってループを回すより、ずっと見やすくてスマートだね。」
まとめ:競プロ・JOI攻略の3大原則
今回学んだ「コードをシンプルにする考え方」を振り返ってみましょう!
- 原則1:無駄な変数は作らない(最終出力に必要なものだけ保持する)
- 原則2:なるべくシンプルに考える(「配列に保存」ではなく「その場で処理」できないか疑う)
- 原則3:なるべく短いコードで済ます(コードが短ければバグも減る!)
JOIなどの競技プログラミングでは、解法を思いついたあとに「これ、もっと簡単に書けないかな?」と一度立ち止まって考える癖をつけるのが上達の近道です。
みなさんもぜひ、無駄のないスッキリとしたコードを目指してみてくださいね!
