C言語 配列の練習問題

配列を使う問題を、初級から上級まで13問そろえました。1次元配列の基本から、2次元配列(行列)、並べ替え(ソート)、二分探索まで扱います。ブラウザでコードを書いて「採点する」を押すと、本物の gcc でいくつかの入力を試し、出力が正しいかを判定します。

この練習問題の使い方
  1. 問題文と実行例を読み、下の欄にコードを書く
  2. 「▶ 実行」で自分で入力を与えて動かしてみる
  3. 「✔ 採点する」で、用意したいくつかの入力に対して出力が正しいかを判定
  4. 行き詰まったら「ヒント」、解けたら「模範解答と解説」で確認
採点のルール
  • 出力は実行例と同じ形で表示する。「数を入力してください」のような入力を促すメッセージは表示しない
  • 行末の空白と、最後の空行の有無は区別しない
  • 書いたコードはこのブラウザに自動保存されます

※ 実行と採点では、コードを外部の無料コンパイルサービス Wandbox に送信して gcc で動かします。

全13問(初級 4・中級 5・上級 4)

配列の練習問題(13問)

A01 初級

問1. 逆順に表示

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数を入力し、入力と逆の順に空白区切りで1行に表示せよ。
実行例(下線が入力)
5 3 1 4 1 5 5 1 4 1 3
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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;
}
解説:要素数 N の配列の添字は 0 〜 N−1 です。i = n から始めると a[n] という配列の外を読んでしまいます(範囲外アクセスは未定義動作で、エラーにならずにでたらめな値が出ることもあります)。終了条件も i >= 0 にしないと a[0] が表示されません。
A02 初級

問2. 合計と平均

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数(それぞれ −1000 以上 1000 以下)を入力し、合計と平均を表示せよ。平均は小数第2位まで表示する。
実行例(下線が入力)
5 1 2 3 4 5 合計: 15 平均: 3.00
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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) は割ったあとで変換するので意味がありません。
A03 初級

問3. 最大値と最小値の位置

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数を入力し、最大値と最小値を、それぞれ何番目(先頭を 1 番目とする)にあるかと一緒に表示せよ。同じ値が複数あるときは最も前のものの位置を表示する。
実行例(下線が入力)
5 3 9 2 9 1 最大値: 9 (2番目) 最小値: 1 (5番目)
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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 にすると、全部が負の数のときに正しく求まりません。初期値は 先頭の要素にするのが確実です。また比較を >= にすると、同じ値のときにも更新されて最も後ろの位置になってしまいます。
A04 初級

問4. 隣り合う要素の差

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数 a1, a2, …, aN を入力し、隣り合う要素の差 a2−a1, a3−a2, …, aN−aN−1 を空白区切りで1行に表示せよ。N が 1 のときは空の行を表示する。
実行例(下線が入力)
5 3 7 4 4 10 4 -3 0 6
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
差は N−1 個です。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回も回らず、空の行だけが出ます。
A05 中級

問5. 値の探索

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数、3行目に探す値 X を入力する。X が配列の中にあれば、最初に現れる位置(先頭を 1 番目とする)と、X の個数を表示せよ。1つもなければ「見つかりません」と表示する。
実行例(下線が入力)
6 4 2 7 2 9 2 2 最初の位置: 2 個数: 3
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
位置を入れる変数を「まだ見つかっていない」ことを表す −1 で初期化しておきます。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;
}
解説:最初の1個だけを探すなら見つけた時点で break できますが、この問題は個数も数えるので最後まで調べます。その代わり、位置は最初の1回だけ記録するようにします(毎回 pos = i とすると最後の位置になります)。「見つからない」ことを、添字としてありえない値 −1 で表すのは定番のやり方です。
A06 中級

問6. 平均以上の値

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数(それぞれ −1000 以上 1000 以下)を入力する。1行目に平均を小数第2位まで表示し、2行目に平均以上の値を入力された順に空白区切りで表示せよ。
実行例(下線が入力)
6 50 60 70 80 90 100 平均: 75.00 80 90 100
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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 に変換されてから比べられます。
A07 中級

問7. 点数の度数分布

1行目に人数 N(1 ≤ N ≤ 100)、2行目に N 人の点数(0 以上 100 以下の整数)を入力し、10点ごとの度数分布を * の個数で表示せよ。区間は 0-9, 10-19, …, 80-89, 90-100 の10個(100点は 90-100 に含める)で、該当者がいない区間も「20-29:」のように表示する。
実行例(下線が入力)
10 0 9 10 55 59 60 89 90 99 100 0-9: ** 10-19: * 20-29: 30-39: 40-49: 50-59: ** 60-69: * 70-79: 80-89: * 90-100: ***
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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;
}
解説:「値から添字を計算して、その要素を 1 増やす」のが度数分布(ヒストグラム)の基本形です。100点の扱いを忘れると cnt[10] という範囲外に書き込んでしまいます。数える配列は = {0} で全要素を 0 に初期化しておくこと(関数の中の配列は、初期化しないと中身が不定です)。
A08 中級

問8. 2番目に大きい値

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数を入力し、異なる値の中で2番目に大きい値を表示せよ。たとえば 3 9 7 9 1 なら、最大は 9、2番目は 7 である。そのような値がない(全部同じ値、または N が 1)ときは「2番目に大きい値はありません」と表示する。
実行例(下線が入力)
5 3 9 7 9 1 2番目に大きい値: 7
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
2回に分けると分かりやすいです。1回目で最大値 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;
}
解説:並べ替えて後ろから2番目を取る方法だと、3 9 7 9 1 では 9 が答えになってしまいます(最大値が重複するケース)。second の初期値を 0 などの決め打ちにすると、全部が負の数のときに失敗します。そこで found で「最初に見つけた値はそのまま採用」とします。
A09 中級

問9. 行と列の合計(2次元配列)

1行目に行数 R と列数 C(1 ≤ R, C ≤ 10)を入力し、続く R 行に各行 C 個の整数を入力する。各行の合計を1行に、各列の合計を次の1行に、それぞれ空白区切りで表示せよ。
実行例(下線が入力)
2 3 1 2 3 4 5 6 行の合計: 6 15 列の合計: 5 7 9
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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 が違うと範囲外を読みます。
A10 上級

問10. バブルソートと交換回数

1行目に整数 N(1 ≤ N ≤ 100)、2行目に N 個の整数を入力し、バブルソートで小さい順に並べ替えて空白区切りで表示せよ。次の行に、隣り合う2つを交換した回数を表示する。交換は「左の方が大きいとき」だけ行う(等しいときは交換しない)。
実行例(下線が入力)
5 5 3 1 4 2 1 2 3 4 5 交換回数: 7
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
外側のループを N−1 回まわし、内側で 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 まで縮められます。なお交換回数は「左にあって自分より大きい値」の組の数(転倒数)と一致します。
A11 上級

問11. 選択ソートの途中経過

1行目に整数 N(2 ≤ N ≤ 20)、2行目に N 個の整数を入力し、選択ソートで小さい順に並べ替える。k 回目のパス(k = 1, 2, …, N−1)では、k 番目以降の要素から最小値を探し(最小値が複数あるときは最も左のもの)、それを k 番目の要素と交換する。各パスのあとに、そのときの配列を「パスk: 」に続けて空白区切りで表示せよ。
実行例(下線が入力)
5 5 3 1 4 2 パス1: 1 3 5 4 2 パス2: 1 2 5 4 3 パス3: 1 2 3 4 5 パス4: 1 2 3 4 5
コードを書く
「実行」で入力を与えて動かす/「採点する」で 4 個のテストを自動で試します
添字で考えると、パス k は 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;
}
解説:選択ソートは「残りの中の最小値を探して、先頭に持ってくる」の繰り返しです。最小値は値ではなく添字(imin)で覚えるのがポイントで、値だけ覚えても交換相手の位置が分かりません。比較を <= にすると最小値が複数あるとき最も右が選ばれ、入力 3 1 3 1 で途中経過が変わります。
A12 上級

問12. 行列の積

1行目に整数 n, m, p(1 ≤ n, m, p ≤ 10)を入力する。続いて n 行 m 列の行列 A を n 行で、m 行 p 列の行列 B を m 行で入力する(要素は −100 以上 100 以下の整数)。積 C = AB(n 行 p 列)を、1行に1つの行ずつ、要素を空白区切りで表示せよ。C の (i, j) 要素は、A の i 行目と B の j 列目の対応する要素どうしの積の和である。
実行例(下線が入力)
2 3 2 1 2 3 4 5 6 7 8 9 10 11 12 58 64 139 154
コードを書く
「実行」で入力を与えて動かす/「採点する」で 5 個のテストを自動で試します
3重ループになります。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 が違うときに範囲外を読みます。
A13 上級

問13. 二分探索

1行目に整数 N(1 ≤ N ≤ 1000)、2行目に小さい順に並んだ、重複のない N 個の整数を入力する。3行目に質問の数 Q(1 ≤ Q ≤ 100)、続く Q 行に探す値を1つずつ入力する。それぞれの値について、配列の何番目(先頭を 1 番目とする)にあるかを1行ずつ表示し、なければ「なし」と表示せよ。二分探索を使うこと。
実行例(下線が入力)
7 2 3 5 7 11 13 17 4 7 2 17 4 4 1 7 なし
コードを書く
「実行」で入力を与えて動かす/「採点する」で 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;
}
解説:二分探索は1回比べるごとに範囲が半分になるので、N=1000 でも最大10回ほどの比較で済みます(先頭から順に調べると最大1000回)。lo = mid のように ±1 を付け忘れると、範囲が縮まらずに無限ループになることがあります。条件も lo < hi にすると、要素が1つ残ったときに調べずに終わってしまいます。配列が並んでいることが前提なので、並んでいなければ先にソートが必要です。

復習する講座

← 練習問題の一覧へ