C++で同じ配列内に平均値が存在するペアを数える方法
問題の概要
各要素が[-1000, 1000]の範囲に収まる整数型配列が与えられます。求めるのは、「2つの要素の平均値が同じ配列内にも存在する」ようなペアの総数です。
例えば、arr[] = [1, 2, 3, 4] の場合を考えてみましょう。1と3の平均は2、2と4の平均は3であり、2も3も配列内に存在するため、該当するペアは (1, 3) と (2, 4) の2つとなり、答えは 2 になります。
入出力例
入力: arr[] = { -1, 2, 5, -3, 8, 10 }
出力: 同じ配列内に平均値が存在するペアの数: 2
説明: 平均値がarr[]内に存在するペアは (-1, 5)(平均は2)と (2, 8)(平均は5)の2つです。
入力: arr[] = { 1, 3, 2, 5, 10, 6 }
出力: 同じ配列内に平均値が存在するペアの数: 3
説明: 該当するペアは (1, 3)(平均は2)、(1, 5)(平均は3)、(2, 10)(平均は6)の3つです。
アルゴリズムの考え方
このプログラムでは頻度配列(度数分布)を活用します。負の要素も扱う必要があるため、頻度配列のサイズは「範囲の2倍+1」である2001とします。
- 負の数の頻度はインデックス0〜1000に格納
- 正の数の頻度はインデックス1000〜2000に格納
具体的には、元の値にN(=1000)を加算した位置をインデックスとして使用することで、負の値も正の値も同じ配列で管理できます。
非ゼロの頻度に対する処理
- 同一値同士のペア: countに freq[i] × (freq[i] - 1) / 2 を加算します。同じ数同士の平均はその数自身になるためです。例えば配列に2が5個ある場合、ペアの総数は 5 × (5 - 1) / 2 = 10 となります。
- 1つ飛ばしの走査: freq[i]が非ゼロの場合、隣接する数ではなく1つ飛ばしで頻度を調べます。連続する2数の平均は小数になり、整数配列内には存在しないためです。
- 平均の存在確認: freq[j]が非ゼロで、かつ freq[(i + j) / 2](2数の平均に対応する頻度)も非ゼロであれば、countに freq[i] × freq[j] を加算します。各数は相手側のすべての数とペアになり得るためです。
手順の詳細
- 整数型配列 arr[] を受け取ります。
- 関数 average_pair(arr, size) は、ペアの平均値が配列arr[]内にも存在するようなペアの数を返します。
- 初期カウントを0とし、N = 1000 で初期化します。
- 頻度配列の長さを size_2 = 2 × N + 1(範囲[-1000, 1000]用)として計算します。
- 頻度配列をすべて0で初期化します。
- 負の要素の頻度はインデックス0〜1000に、正の要素はそれ以降に格納されるよう、インデックスにNを加算して頻度配列を作成します。
- 各要素 arr[i] について、arr_freq[arr[i] + N]++ で頻度を更新します。
- i = 0 から i < size_2 まで頻度配列を走査します。
- 非ゼロの頻度ごとに、条件1に従って freq[i] × (freq[i] - 1) / 2 をcountに加算します。
- arr_freq[i]が非ゼロなので、1つ飛ばしで他の頻度を走査します。
- temp_2 を arr_freq[(i + j) / 2] として計算します。
- temp_2が非ゼロかつ arr_freq[j]が非ゼロなら条件3を満たすので、countに積 arr_freq[i] × arr_freq[j] を加算します。
- すべての反復が終わると、countに該当ペアの総数が格納されます。
- 結果としてcountを返します。
C++での実装例
#include <bits/stdc++.h>
using namespace std;
int average_pair(int arr[], int size_1){
int count = 0;
int N = 1000;
int size_2 = (2 * N) + 1;
int arr_freq[size_2] = { 0 };
for (int i = 0; i < size_1; i++){
int temp = arr[i];
arr_freq[temp + N]++;
}
for (int i = 0; i < size_2; i++){
if (arr_freq[i] > 0){
int check = (arr_freq[i]) * (arr_freq[i] - 1);
count += check / 2;
for (int j = i + 2; j < 2001; j += 2){
int temp_2 = arr_freq[(i + j) / 2];
if (arr_freq[j] > 0 && temp_2 > 0){
count += (arr_freq[i] * arr_freq[j]);
}
}
}
}
return count;
}
int main(){
int arr[] = { 2, 3, 1, 8, 9, 10 };
int size = sizeof(arr) / sizeof(arr[0]);
cout<<"Count of pairs with average present in the same array are: "<<average_pair(arr, size);
return 0;
}
出力結果
上記のコードを実行すると、次の出力が得られます。
Count of pairs with average present in the same array are: 2
計算量について
頻度配列の構築には配列の要素数nに対してO(n)、その後の走査は頻度配列のサイズM(=2001)に対して各インデックスから最大約M/2回の比較を行うため、全体の時間計算量はO(M²)、空間計算量はO(M)となります。要素の値域が限定されている場合、要素数が多くても安定した性能を発揮できるのがこの手法の大きな利点です。
-
【C++】左右の偶数・奇数の個数が等しくなる配列インデックスを見つけるプログラム
問題の概要「両側で偶数(または奇数)の個数が同じになる配列インデックス」とは、ある要素の左側と右側に含まれる偶数の個数、または奇数の個数が互いに等しくなるような位置のことです。つまり、「左側の個数=右側の個数」を満たすインデックスを見つける問題です。まず、この概念に関連する基本用語を確認しておきましょう。基本用語の定義配列(Array):同じデータ型の要素を格納するためのコンテナ(データ構造)です。配列インデックス(Array Index):配列内の要素の位置を示す番号です。インデックスは必ず0から始まります。偶数:2で割り切れる整数のことです。奇数:2で割り切れない整数のことです。すべての整
-
C++で左右の偶数・奇数の出現回数が一致する配列インデックスを見つける方法
問題の概要 ここで取り上げるのは次のような問題です。n個の要素を持つ配列が与えられたとき、「あるインデックスの左側にある偶数の出現回数と右側にある偶数の出現回数が等しい」、または「左側にある奇数の出現回数と右側にある奇数の出現回数が等しい」という条件を満たすインデックスを1つ見つけます。該当するインデックスが存在しない場合は -1 を返します。 例として、配列が {4, 3, 2, 1, 2, 4} の場合を考えてみましょう。このとき答えは 2 になります。インデックス2の要素は「2」であり、その左側には奇数が1つ(3)、右側にも奇数が1つ(1)しか存在しないためです。 解決のアプローチ こ