語法書 / AA 競程語法書 上冊 / 第六單元 / 用陣列收集與記錄資訊

6.7 用陣列收集與記錄資訊

前面的題目,陣列存的都是「題目給的輸入」。這一節換個角度:陣列也可以存你自己算出來的東西——收集結果、記錄狀態、統計次數。這是之後大量題目的地基。

收集不定長度的結果

站上的所有 x 的位置要先輸出 x 出現的次數,再由小到大列出每個出現位置——次數要印在最前面,但「有幾個」事先不知道。標準寫法:準備一個結果陣列 ans 和一個計數器 num

int ans[100005];                   // 收集所有符合條件的位置
int num = 0;                       // 目前收集到幾個

for (int i = 1; i <= n; i++) {
    int x;
    cin >> x;                      // 這題甚至不用把輸入整串存起來
    if (x == target) {
        ans[num] = i;              // 放進第 num 格……
        num++;                     // ……數量順便加一
    }
}

cout << num << '\n';
for (int i = 0; i < num; i++) cout << ans[i] << '\n';

num 一人分飾兩角:既是「目前的數量」,也是「下一個空格的編號」。中間兩行也常合寫成 ans[num++] = i;——後置 ++ 先用舊值當索引、用完再加一(4.7 教過)。

陣列是一排「活的」變數

陣列不是唯讀倉庫:每一格都能隨時讀、隨時改。例如收到指令「把第 x 格加上 v,再回報它的新值」:

a[x] += v;                         // 第 x 格的「目前值」更新了
cout << a[x] << '\n';

索引=編號、值=這個編號目前的資訊——把這個對應感建立起來,很多題目會突然變簡單。

計數陣列:統計每個數字出現幾次

值域不大時(例如分數都在 0 \sim 100),開一個「出現次數」陣列,掃到什麼就把對應的格子加一:

int cnt[105] = {};                 // cnt[v]=數字 v 目前出現的次數

for (int i = 1; i <= n; i++) {
    int x;
    cin >> x;
    cnt[x]++;                      // 又看到一次 x
}

統計完之後想怎麼用都行:哪個數字出現最多次?有沒有數字出現超過一次?哪些數字從沒出現?——全部變成 6.4 的基本遍歷(走訪的對象從輸入變成 cnt 陣列)。

計算數字個數 1 正是這個套路的實戰:先把每個數的出現次數統計進 cnt,之後每個詢問「b 出現幾次」直接回答 cnt[b]——比起每收到一個詢問就把整串數列重掃一遍,快上一大截。

範例程式碼

讀入 n0 \sim 100 的分數,輸出「出現次數最多」的分數和它的次數(同票取分數較小者):

#include <iostream>
using namespace std;

int cnt[105];                      // 全域:自動全 0

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        cnt[x]++;
    }

    int best = 0;                  // 目前出現最多次的分數
    for (int v = 1; v <= 100; v++) {
        if (cnt[v] > cnt[best]) best = v;   // 用「嚴格大於」:同票保留較小的 v
    }
    cout << best << ' ' << cnt[best] << '\n';

    return 0;
}

執行結果(輸入 760 90 60 100 90 60 77):

60 3

動手試試看:把範例改成輸出「所有出現至少兩次的分數」(由小到大、每行一個)。想想看:這一步遍歷的是 cnt 陣列(0 \sim 100),還是原始輸入?兩者有什麼差別?