配列を使う問題を、初級から上級まで13問そろえました。1次元配列の基本から、2次元配列(行列)、並べ替え(ソート)、二分探索まで扱います。ブラウザでコードを書いて「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。
※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
for (i = 0; i < n; i++) で scanf("%d", &a[i]); を繰り返して全部読み込みます。表示は i = n - 1 から始めて i-- で 0 まで戻ります。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } for (int i = n - 1; i >= 0; i--) { printf("%d ", a[i]); } printf("\n"); return 0; }
i = n から始めると a[n] という配列の外を読んでしまいます(範囲外アクセスは未定義動作で、エラーにならずにでたらめな値が出ることもあります)。終了条件も i >= 0 にしないと a[0] が表示されません。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
sum を 0 で初期化し、sum += a[i]; を N 回繰り返します。平均は (double)sum / n と片方を double にして割ります。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int sum = 0; for (int i = 0; i < n; i++) { sum += a[i]; } printf("合計: %d\n", sum); printf("平均: %.2f\n", (double)sum / n); return 0; }
sum / n のままだと整数どうしの割り算で小数部分が切り捨てられます(5 / 3 は 1)。(double)sum / n と書けば、割り算の前に sum が double に変換されるので 1.666… が得られます。(double)(sum / n) は割ったあとで変換するので意味がありません。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
imax = 0 から始めて、a[i] > a[imax] なら imax = i と更新します。表示するときは imax + 1 番目です。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int imax = 0, imin = 0; for (int i = 1; i < n; i++) { if (a[i] > a[imax]) imax = i; if (a[i] < a[imin]) imin = i; } printf("最大値: %d (%d番目)\n", a[imax], imax + 1); printf("最小値: %d (%d番目)\n", a[imin], imin + 1); return 0; }
0 にすると、全部が負の数のときに正しく求まりません。初期値は 先頭の要素にするのが確実です。また比較を >= にすると、同じ値のときにも更新されて最も後ろの位置になってしまいます。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
i を 0 から n - 2 まで回し、a[i + 1] - a[i] を表示します。ループの条件は i < n - 1 です。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } for (int i = 0; i < n - 1; i++) { printf("%d ", a[i + 1] - a[i]); } printf("\n"); return 0; }
a[i + 1] を使うときは、i + 1 が配列の範囲内かを考えます。条件を i < n にすると、最後に a[n](範囲外)を読んでしまいます。N=1 のときはループが1回も回らず、空の行だけが出ます。見つかりません」と表示する。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
a[i] == x のとき、位置がまだ −1 なら i を記録し、個数は毎回 1 増やします。#include <stdio.h> int main(void) { int n, x; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } scanf("%d", &x); int pos = -1, count = 0; for (int i = 0; i < n; i++) { if (a[i] == x) { if (pos == -1) pos = i; count++; } } if (count == 0) { printf("見つかりません\n"); } else { printf("最初の位置: %d\n", pos + 1); printf("個数: %d\n", count); } return 0; }
break できますが、この問題は個数も数えるので最後まで調べます。その代わり、位置は最初の1回だけ記録するようにします(毎回 pos = i とすると最後の位置になります)。「見つからない」ことを、添字としてありえない値 −1 で表すのは定番のやり方です。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
a[i] >= avg のものを表示します。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int sum = 0; for (int i = 0; i < n; i++) { sum += a[i]; } double avg = (double)sum / n; printf("平均: %.2f\n", avg); for (int i = 0; i < n; i++) { if (a[i] >= avg) { printf("%d ", a[i]); } } printf("\n"); return 0; }
int avg = sum / n; で求めると、入力 1 2 2 で平均が 1 になり、1 まで表示されてしまいます。a[i] >= avg の比較では int が double に変換されてから比べられます。* の個数で表示せよ。区間は 0-9, 10-19, …, 80-89, 90-100 の10個(100点は 90-100 に含める)で、該当者がいない区間も「20-29:」のように表示する。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
int cnt[10] = {0}; を用意します。点数 s の区間番号は s / 10 ですが、100点だと 10 になるので、そのときだけ 9 にします。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int cnt[10] = {0}; for (int i = 0; i < n; i++) { int k = a[i] / 10; if (k == 10) k = 9; cnt[k]++; } for (int k = 0; k < 10; k++) { if (k == 9) { printf("90-100: "); } else { printf("%d-%d: ", k * 10, k * 10 + 9); } for (int j = 0; j < cnt[k]; j++) { printf("*"); } printf("\n"); } return 0; }
cnt[10] という範囲外に書き込んでしまいます。数える配列は = {0} で全要素を 0 に初期化しておくこと(関数の中の配列は、初期化しないと中身が不定です)。2番目に大きい値はありません」と表示する。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
max を求め、2回目で「max より小さい値の中の最大」を求めます。2回目は「まだ1つも見つかっていない」ことを表す変数(フラグ)を使います。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int max = a[0]; for (int i = 1; i < n; i++) { if (a[i] > max) max = a[i]; } int found = 0, second = 0; for (int i = 0; i < n; i++) { if (a[i] < max && (!found || a[i] > second)) { second = a[i]; found = 1; } } if (found) { printf("2番目に大きい値: %d\n", second); } else { printf("2番目に大きい値はありません\n"); } return 0; }
second の初期値を 0 などの決め打ちにすると、全部が負の数のときに失敗します。そこで found で「最初に見つけた値はそのまま採用」とします。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
a[i][j] は「i 行目・j 列目」です。行の合計は i を外側にして j を回し、列の合計は j を外側にして i を回します。合計用の変数は外側のループに入るたびに 0 に戻します。#include <stdio.h> int main(void) { int r, c; int a[10][10]; scanf("%d %d", &r, &c); for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { scanf("%d", &a[i][j]); } } printf("行の合計:"); for (int i = 0; i < r; i++) { int sum = 0; for (int j = 0; j < c; j++) { sum += a[i][j]; } printf(" %d", sum); } printf("\n"); printf("列の合計:"); for (int j = 0; j < c; j++) { int sum = 0; for (int i = 0; i < r; i++) { sum += a[i][j]; } printf(" %d", sum); } printf("\n"); return 0; }
sum = 0 を2重ループの外に書くと、前の行の合計が持ち越されて累計になってしまいます。列の合計で a[j][i] のように添字を逆にするのもよくある間違いで、R と C が違うと範囲外を読みます。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
j を 0 から n - 2 - i まで動かして a[j] > a[j + 1] なら交換します。交換には作業用の変数 tmp を使います。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } int swaps = 0; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - 1 - i; j++) { if (a[j] > a[j + 1]) { int tmp = a[j]; a[j] = a[j + 1]; a[j + 1] = tmp; swaps++; } } } for (int i = 0; i < n; i++) { printf("%d ", a[i]); } printf("\n交換回数: %d\n", swaps); return 0; }
a[j] = a[j + 1]; a[j + 1] = a[j]; と書くと、1行目で元の a[j] が消えるので両方同じ値になります。必ず tmp に退避します。外側が1周するたびに最大値が右端に確定するので、内側の範囲を n - 1 - i まで縮められます。なお交換回数は「左にあって自分より大きい値」の組の数(転倒数)と一致します。パスk: 」に続けて空白区切りで表示せよ。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
i = k - 1 の位置を決める処理です。imin = i から始めて j = i + 1 〜 n - 1 を調べ、a[j] < a[imin] なら imin = j。最後に a[i] と a[imin] を交換して配列を表示します。#include <stdio.h> int main(void) { int n; int a[100]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } for (int i = 0; i < n - 1; i++) { int imin = i; for (int j = i + 1; j < n; j++) { if (a[j] < a[imin]) imin = j; } int tmp = a[i]; a[i] = a[imin]; a[imin] = tmp; printf("パス%d:", i + 1); for (int j = 0; j < n; j++) { printf(" %d", a[j]); } printf("\n"); } return 0; }
<= にすると最小値が複数あるとき最も右が選ばれ、入力 3 1 3 1 で途中経過が変わります。「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
i(C の行)と j(C の列)を回し、その中で k を 0 〜 m−1 と回して c[i][j] += a[i][k] * b[k][j]; を足し込みます。#include <stdio.h> int main(void) { int n, m, p; int a[10][10], b[10][10], c[10][10]; scanf("%d %d %d", &n, &m, &p); for (int i = 0; i < n; i++) { for (int k = 0; k < m; k++) { scanf("%d", &a[i][k]); } } for (int k = 0; k < m; k++) { for (int j = 0; j < p; j++) { scanf("%d", &b[k][j]); } } for (int i = 0; i < n; i++) { for (int j = 0; j < p; j++) { c[i][j] = 0; for (int k = 0; k < m; k++) { c[i][j] += a[i][k] * b[k][j]; } } } for (int i = 0; i < n; i++) { for (int j = 0; j < p; j++) { printf("%d ", c[i][j]); } printf("\n"); } return 0; }
c[i][j] を0 に初期化せずに += すると、不定の値から足し始めてしまいます。ループの上限は、i が n、j が p、k が m です。どれかを取り違えると、正方行列のテストでは通っても、n, m, p が違うときに範囲外を読みます。なし」と表示せよ。二分探索を使うこと。「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
lo〜hi(最初は 0〜N−1)で表し、lo <= hi の間、真ん中 mid と比べて範囲を半分にします。a[mid] が小さければ lo = mid + 1、大きければ hi = mid - 1 です。#include <stdio.h> int main(void) { int n, q; int a[1000]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } scanf("%d", &q); for (int t = 0; t < q; t++) { int x; scanf("%d", &x); int lo = 0, hi = n - 1, pos = -1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (a[mid] == x) { pos = mid; break; } else if (a[mid] < x) { lo = mid + 1; } else { hi = mid - 1; } } if (pos >= 0) { printf("%d\n", pos + 1); } else { printf("なし\n"); } } return 0; }
lo = mid のように ±1 を付け忘れると、範囲が縮まらずに無限ループになることがあります。条件も lo < hi にすると、要素が1つ残ったときに調べずに終わってしまいます。配列が並んでいることが前提なので、並んでいなければ先にソートが必要です。