APG4b Java版 第2章: 実践的な書き方と計算量
2.00. 第2章について
第1章の内容をマスターすることで、理論上すべての計算処理を行うプログラムを書けるようになりました。
第2章では、複雑な計算をどのように記述するか、また Java に固有の便利な書き方を説明します。さらに、実行時間の見積もりに使う「計算量」の概念を学びます。第2章をマスターすると、実際にほぼすべての計算処理プログラムを実用的に書けるようになります。
AtCoder での練習について:
- AtCoder Beginner Contest (ABC) ... 毎週土曜夜に開催
- AtCoder Daily Training (ADT) ... 過去問を使ったバーチャルコンテスト
- AtCoder 強化プラン ... コンテストの復習や別の学習法
2.01. いろいろな for 文
キーポイント
- インデックス付き for:
for (int i = 0; i < N; i++) - 拡張 for 文 (foreach):
for (型 変数 : コレクション) - 複数リストを同時にループ: インデックスを使う
breakでループを抜ける、continueで次のイテレーションへ
基本の for 文パターン
range(stop) の代わり:
for (int i = 0; i < 5; i++) {
System.out.println(i);
}実行結果:
0
1
2
3
4range(start, stop) の代わり:
for (int i = 2; i < 5; i++) {
System.out.println(i);
}実行結果:
2
3
4range(start, stop, step) の代わり:
for (int i = 4; i < 10; i += 2) {
System.out.println(i);
}実行結果:
4
6
8逆順:
for (int i = 5; i >= 0; i--) {
System.out.println(i);
}実行結果:
5
4
3
2
1
0配列・ArrayList に対する for 文
拡張 for 文 (Python の for x in L: に相当):
int[] a = {3, 1, 4, 1, 5};
for (int x : a) {
System.out.println(x);
}文字列の各文字を処理:
String s = "AtCoder";
for (char c : s.toCharArray()) {
System.out.println(c);
}2次元配列のアンパック (Python の for a, b in [[5, 10], [6, 20]]:):
int[][] pairs = {{5, 10}, {6, 20}};
for (int[] pair : pairs) {
int a = pair[0], b = pair[1];
System.out.println(a + " " + b);
}インデックスと値を同時に処理 (Python の enumerate())
Java には enumerate() がないので、インデックスを手動で管理します:
int[] a = {10, 20, 30};
for (int i = 0; i < a.length; i++) {
System.out.println(i + " " + a[i]);
}実行結果:
0 10
1 20
2 30逆順の for (Python の reversed())
int[] a = {3, 1, 4, 1, 5};
for (int i = a.length - 1; i >= 0; i--) {
System.out.println(a[i]);
}複数配列の同時ループ (Python の zip())
Java には zip() がないので、インデックスを使います:
int[] A = {1, 2, 3};
int[] B = {10, 20, 30};
for (int i = 0; i < A.length; i++) {
System.out.println(A[i] + " " + B[i]);
}実行結果:
1 10
2 20
3 30break と continue
// break: ループを抜ける
for (int i = 0; i < 5; i++) {
if (i == 3) break;
System.out.println(i);
}
// 出力: 0, 1, 2
// continue: 次のイテレーションへスキップ
for (int i = 0; i < 5; i++) {
if (i == 3) continue;
System.out.println(i);
}
// 出力: 0, 1, 2, 4ラベル付き break (Python には for-else があるが Java では別の方法)
Java ではラベル付き break で外側のループを抜けられます:
outer:
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i + j == 5) {
System.out.println("抜ける: " + i + " " + j);
break outer; // 外側のループも抜ける
}
}
}
System.out.println("終了");練習問題
EX14. 隣り合う同じ値を探す
2.02. 多重ループ
キーポイント
- ループの中にさらにループを書くことを「多重ループ」という
- 内側のループで
breakすると内側のループのみ抜ける - 複数段階のループを抜けるには「ラベル付き break」または「フラグ変数」を使う
- Python と同様の書き方が使える
基本: 二重ループ
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.println(i + " " + j);
}
}
}
}実行結果:
0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2実践例: 2つの数列に共通要素があるか判定
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] A = new int[3];
int[] B = new int[3];
for (int i = 0; i < 3; i++) A[i] = sc.nextInt();
for (int i = 0; i < 3; i++) B[i] = sc.nextInt();
boolean answer = false;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (A[i] == B[j]) {
answer = true;
}
}
}
if (answer) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}
}break の動作: 内側のループのみ抜ける
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) break; // 内側の j ループのみ抜ける
System.out.println(i + " " + j);
}
}実行結果:
0 0
1 0
2 0複数段階のループから抜ける方法1: フラグ変数
boolean finished = false;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.println(i + " " + j);
if (i == 1 && j == 1) {
finished = true;
}
if (finished) break;
}
if (finished) break;
}複数段階のループから抜ける方法2: ラベル付き break (Java の利点)
Python にはない Java の機能です。ラベルを使うとすっきり書けます:
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
System.out.println(i + " " + j);
if (i == 1 && j == 1) {
break outer; // outer ラベルのループを抜ける
}
}
}複数段階のループから抜ける方法3: メソッド化して return
public class Main {
static void func() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) return; // メソッドを終了
System.out.println(i + " " + j);
}
}
}
public static void main(String[] args) {
func();
}
}練習問題
EX15. 果物屋さんでお買い物
2.03. ループによるリスト生成 (内包表記の代替)
キーポイント
- Java には Python のリスト内包表記がない
- 代わりに
forループでArrayListにadd()するか、配列を使う - Java 8 以降の Stream API を使うと内包表記に近い書き方ができる
- ただし競技プログラミングでは従来の
forループが読みやすく速い
Python の内包表記と Java の対応
Python: [i * i for i in range(5)]
Java (forループ版):
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i < 5; i++) {
result.add(i * i);
}
System.out.println(result); // [0, 1, 4, 9, 16]
}
}Java (配列版):
int[] result = new int[5];
for (int i = 0; i < 5; i++) {
result[i] = i * i;
}
// 出力: 0 1 4 9 16条件付きフィルタリング (Python の [x for x in a if x % 2 == 0])
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5, 6, 7, 8};
ArrayList<Integer> evens = new ArrayList<>();
for (int x : a) {
if (x % 2 == 0) {
evens.add(x);
}
}
System.out.println(evens); // [2, 4, 6, 8]
}
}組み込み関数との組み合わせ
Python の sum([i*i for i in range(N)]):
int N = 5;
int sum = 0;
for (int i = 0; i < N; i++) {
sum += i * i;
}
System.out.println(sum); // 30最大値・最小値:
int[] a = {3, 1, 4, 1, 5, 9, 2, 6};
int maxVal = a[0];
int minVal = a[0];
for (int x : a) {
maxVal = Math.max(maxVal, x);
minVal = Math.min(minVal, x);
}
System.out.println("max=" + maxVal + " min=" + minVal);Stream API を使った内包表記風の書き方 (参考)
競技プログラミングでは使う場面が限られますが、Stream API で簡潔に書けます:
import java.util.Arrays;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
// [i*i for i in range(5)]
int[] result = IntStream.range(0, 5).map(i -> i * i).toArray();
System.out.println(Arrays.toString(result)); // [0, 1, 4, 9, 16]
// sum([i*i for i in range(5)])
int sum = IntStream.range(0, 5).map(i -> i * i).sum();
System.out.println(sum); // 30
// [x for x in a if x % 2 == 0]
int[] a = {1, 2, 3, 4, 5, 6};
int[] evens = Arrays.stream(a).filter(x -> x % 2 == 0).toArray();
System.out.println(Arrays.toString(evens)); // [2, 4, 6]
}
}入力のリスト読み込み
Python の list(map(int, input().split())):
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = 5;
int[] a = new int[N];
for (int i = 0; i < N; i++) {
a[i] = sc.nextInt();
}
}
}練習問題
EX16. 英単語テストの練習
2.04. 多次元配列
キーポイント
- 2次元配列は
int[][] a = new int[N][M]で宣言 a[i][j]で i 行 j 列の要素にアクセスa.lengthで行数、a[0].lengthで列数を取得ArrayList<ArrayList<Integer>>で可変長の2次元リストも作れる
2次元配列の基本
public class Main {
public static void main(String[] args) {
int[][] a = {{1, 3, 5}, {2, 4, 6}};
System.out.println(a[0][0]); // 1
System.out.println(a[1][1]); // 4
System.out.println(a[0][2]); // 5 (最後の列)
System.out.println(a.length); // 2 (行数)
System.out.println(a[0].length); // 3 (列数)
}
}二重ループと2次元配列
int[][] a = {{1, 3, 5}, {2, 4, 6}};
for (int i = 0; i < a.length; i++) {
System.out.print("リストの第 " + i + " 成分は [");
for (int j = 0; j < a[i].length; j++) {
if (j > 0) System.out.print(", ");
System.out.print(a[i][j]);
}
System.out.println("]");
}入力から2次元配列を作成
方法1 (ループで代入):
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
int[][] a = new int[N][M];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
a[i][j] = sc.nextInt();
}
}
}
}すべて 0 の N×M 配列
int N = 2, M = 3;
int[][] a = new int[N][M]; // 自動的に 0 で初期化される可変長の2次元リスト
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
int N = 4;
ArrayList<ArrayList<Integer>> a = new ArrayList<>();
for (int i = 0; i < N; i++) {
a.add(new ArrayList<>());
}
a.get(0).add(10);
a.get(0).add(20);
a.get(2).add(30);
a.get(3).add(40);
a.get(3).add(50);
a.get(3).add(60);
// 結果: [[10, 20], [], [30], [40, 50, 60]]
for (ArrayList<Integer> row : a) {
System.out.println(row);
}
}
}注意: 2次元配列の初期化
Java の int[][] はデフォルトで 0 で初期化されます。Python の [[0]*M]*N のような罠はありません:
int N = 3, M = 2;
int[][] a = new int[N][M]; // 各行が独立した配列 (安全)
a[0][0] = 1;
System.out.println(a[1][0]); // 0 (他の行は影響を受けない)3次元配列
int N = 2, M = 2, D = 2;
int[][][] lst3d = new int[N][M][D];
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
for (int k = 0; k < D; k++) {
lst3d[i][j][k] = i * 100 + j * 10 + k;
}
}
}練習問題
EX17. ゲーム大会
2.05. 再帰メソッド
キーポイント
- メソッドが自分自身を呼び出すことを「再帰呼び出し」という
- 再帰メソッドには「ベースケース」と「再帰呼び出し」の2つの部分がある
- Java のデフォルトのスタックサイズは JVM に依存 (約 500〜1000 回程度が安全)
- 深い再帰には
StackOverflowErrorが発生する
再帰メソッドの基本
public class Main {
static long sumTriangle(int n) {
if (n == 0) return 0; // ベースケース
long s = sumTriangle(n - 1); // 再帰呼び出し
return s + n; // 答えの計算
}
public static void main(String[] args) {
System.out.println(sumTriangle(10)); // 55
}
}再帰メソッドの3つの部分
- ベースケース: 再帰呼び出しなしに直接答えを求める最小ケース
- 再帰呼び出し: より小さなケースを呼び出す
- 答えの計算: 再帰結果を利用して最終答えを計算
階乗
static long factorial(int n) {
if (n == 0 || n == 1) return 1; // ベースケース
return n * factorial(n - 1); // 再帰
}フィボナッチ数
static long fib(int n) {
if (n == 0 || n == 1) return 1; // ベースケース
return fib(n - 1) + fib(n - 2); // 再帰
}各桁の和
static int digitSum(int n) {
if (n <= 9) return n; // ベースケース
int ones = n % 10;
int other = n / 10;
return digitSum(other) + ones; // 再帰
}例題: 報告書の伝達時間
import java.util.*;
public class Main {
static ArrayList<ArrayList<Integer>> children;
static int completeTime(int x) {
if (children.get(x).isEmpty()) return 0;
int maxTime = 0;
for (int y : children.get(x)) {
maxTime = Math.max(maxTime, completeTime(y) + 1);
}
return maxTime;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] p = new int[N];
p[0] = -1;
for (int i = 1; i < N; i++) p[i] = sc.nextInt();
children = new ArrayList<>();
for (int i = 0; i < N; i++) children.add(new ArrayList<>());
for (int i = 1; i < N; i++) children.get(p[i]).add(i);
System.out.println(completeTime(0));
}
}入力: 6 / 0 0 1 1 4出力: 3
再帰メソッドの注意点
スタックオーバーフロー:
// 深い再帰は StackOverflowError を引き起こす
static void infiniteRecursion() {
infiniteRecursion(); // 無限再帰 → StackOverflowError
}競技プログラミングでは再帰が深くなる場合、反復 (ループ) に書き換えるか、スタック (ArrayDeque) を使って手動で管理することを検討します。
クイックソートの例
import java.util.*;
public class Main {
static ArrayList<Integer> quickSort(ArrayList<Integer> a) {
if (a.isEmpty()) return new ArrayList<>();
int p = a.remove(a.size() - 1);
ArrayList<Integer> lo = new ArrayList<>();
ArrayList<Integer> hi = new ArrayList<>();
for (int x : a) {
if (x < p) lo.add(x);
else hi.add(x);
}
ArrayList<Integer> result = quickSort(lo);
result.add(p);
result.addAll(quickSort(hi));
return result;
}
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6));
System.out.println(quickSort(list)); // [1, 1, 2, 3, 4, 5, 6, 9]
}
}練習問題
EX18. 報告書の枚数
2.06. 計算量
キーポイント
- アルゴリズム: 処理の計算手順
- 計算量: 入力に対して必要な計算時間・メモリがどう変化するかの指標
- 時間計算量: 計算ステップ数の変化
- 空間計算量: メモリ使用量の変化
- オーダー記法 O(...): 計算量を大まかに表す記法
- Java は Python より定数倍が速い (同じ O(N) でも 10〜100 倍速)
アルゴリズム
「ある処理を行うときに、どのような計算を行っていくかという計算手順」をアルゴリズムといいます。
例: 1から100までの総和
- アルゴリズムA: 順に足す (99回の計算)
- アルゴリズムB: 公式を使う (3回の計算)
N * (N + 1) / 2
オーダー記法の求め方
- 係数を省略する (定数については 1 とする)
- N を大きくしたとき一番影響が大きい項を取り出し、O(項) と書く
例:
N + 100→ O(N)10N + N³→ O(N³)3 + 5→ O(1)2ᴺ + N²→ O(2ᴺ)
計算量の例
O(1) の例:
Scanner sc = new Scanner(System.in);
long N = sc.nextLong();
long s = N * (N + 1) / 2;
System.out.println(s);O(N) の例:
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[] a = new int[N];
for (int i = 0; i < N; i++) a[i] = sc.nextInt();
int cnt = 0;
for (int x : a) {
if (x % 2 == 0) cnt++;
}
System.out.println(cnt);O(N²) の例:
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int[][] table = new int[N][N];
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
table[i][j] = (i + 1) * (j + 1);
}
}
for (int[] row : table) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < row.length; j++) {
if (j > 0) sb.append(" ");
sb.append(row[j]);
}
System.out.println(sb);
}O(log N) の例:
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int cnt = 0;
while (N % 2 == 0) {
cnt++;
N /= 2;
}
System.out.println(cnt);計算量の大小関係
O(1) < O(log N) < O(N) < O(N log N) < O(N²) < O(2ᴺ)入力 N と実行時間の目安
| N | O(1) | O(log N) | O(N) | O(N log N) | O(N²) | O(2ᴺ) |
|---|---|---|---|---|---|---|
| 1 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 |
| 10 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 一瞬 |
| 1,000 | 一瞬 | 一瞬 | 一瞬 | 一瞬 | 0.01秒 | 地球爆発 |
| 10⁶ | 一瞬 | 一瞬 | 0.01秒 | 0.2秒 | 3時間 | 地球爆発 |
| 10⁸ | 一瞬 | 一瞬 | 1秒 | 30秒 | 3年 | 地球爆発 |
| 10¹⁶ | 一瞬 | 一瞬 | 3年 | 170年 | 地球爆発 | 地球爆発 |
AtCoder の問題では実行時間制約が 2秒〜5秒であることが多いです。Java では 1秒あたり約 10⁸ 回の計算ができます (Python の約 10〜50 倍速)。
Java の高速化のヒント
出力を高速化する (StringBuilder を使う):
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
for (int i = 0; i < 100000; i++) {
pw.println(i);
}
pw.flush();
}
}入力を高速化する (BufferedReader を使う):
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine().trim());
StringTokenizer st = new StringTokenizer(br.readLine());
int[] a = new int[N];
for (int i = 0; i < N; i++) {
a[i] = Integer.parseInt(st.nextToken());
}
}
}よく使う操作の計算量
| 操作 | クラス | 計算量 |
|---|---|---|
a.length | 配列 | O(1) |
list.size() | ArrayList | O(1) |
list.get(i) | ArrayList | O(1) |
list.add(x) | ArrayList | O(1) |
list.contains(x) | ArrayList | O(N) |
Arrays.sort(a) | Arrays | O(N log N) |
Collections.sort(list) | Collections | O(N log N) |
Arrays.binarySearch(a, v) | Arrays | O(log N) |
練習問題
EX19. 計算量の見積もり