JavaScriptで合計が指定値と一致するバイナリ部分配列の個数を数える方法
問題
最初の引数としてバイナリ配列 arr を、2番目の引数として数値 target を受け取る JavaScript 関数を作成します。
この関数の役割は、配列 arr 内に存在する連続する部分配列のうち、要素の合計が target と一致するものの個数を数え、その結果を返すことです。
たとえば、関数への入力が次の場合を考えてみましょう。
入力
const arr = [1, 0, 1, 0, 1]; const target = 2;
出力
const output = 4;
出力の説明
条件を満たす部分配列は、次の4つです。
[1,0,1] [1,0,1,0] [0,1,0,1] [1,0,1]
解法のアプローチ:累積和とハッシュマップ
すべての部分配列を総当たりで調べる方法もありますが、その場合時間計算量は O(n²) となり、配列が長くなると非効率です。そこで累積和(プレフィックスサム)とハッシュマップを組み合わせることで、O(n) の線形時間に高速化できます。
考え方のポイントは以下の通りです。
- 変数
sumに、現在位置までの要素の累積和を保持します。 map[sum]には「これまでに同じ累積和が出現した回数」を記録します。- ある時点で累積和が
Sのとき、それより前に累積和がS − targetであった箇所が存在すれば、その箇所の直後から現在位置までの部分配列の合計は必ずtargetになります。 - したがって
count += map[sum - target]とすることで、条件を満たす部分配列の個数を効率的に加算できます。
なお、ループの先頭で map[sum] を更新してから sum を進めることで、「空の前置き(累積和 0)」も含めて正しくカウントできる仕組みになっています。
コード例
const arr = [1, 0, 1, 0, 1];
const target = 2;
const countSubarrays = (arr = [], target = 1) => {
const map = {}
let sum = 0
let count = 0
for (const num of arr) {
// 現在の累積和の出現回数を記録
map[sum] = (map[sum] || 0) + 1
sum += num
// 合計が target となる部分配列の個数を加算
count += map[sum - target] || 0
}
return count
};
console.log(countSubarrays(arr, target));出力
4
計算量について
- 時間計算量: O(n) ― 配列を一度だけ走査すればよいため非常に高速です。
- 空間計算量: O(n) ― 累積和の出現回数を保存するためのマップが必要です。
この手法はバイナリ配列だけでなく、負の数を含む整数配列に対しても同様に適用できる汎用的なテクニックなので、覚えておくとさまざまな場面で役立ちます。
-
C++で部分配列の合計が偶数になる個数を求める方法
要素数 N の配列 arr[] が与えられたとき、合計が偶数となる部分配列(連続部分配列)の個数を求める問題について解説します。 問題の例 入力 arr[] = {2, 1, 3, 4, 2, 5} 出力 28 この配列において、合計が偶数となる連続部分配列は全部で 28 個存在します。(元記事の列挙には非連続の部分配列(部分列)が混在していましたが、ここでは標準的な定義である「連続部分配列」としてカウントしています。) 解法 1:ブルートフォース法(全探索) 最も直感的な方法は、すべての部分配列の合計を計算し、偶数かどうかを判定することです。計算量は O(N^2) となります。 アルゴリ
-
C++で合計がSとなるバイナリ部分配列を数える方法
0と1のみから構成される配列Aが与えられたとき、合計がSとなる空でない(非空の)部分配列がいくつ存在するかを求める問題です。例えば、入力が [1,0,1,0,1]、S = 2 の場合、答えは 4 になります。該当する部分配列は [1,0,1]、[1,0,1,0]、[0,1,0,1]、[1,0,1] の4つです。解法のアプローチこの問題は「スライディングウィンドウ(尺取り法)」を用いた補助関数 atMost() を使うことで効率的に解けます。atMost(A, x) は「合計が x 以下となる部分配列の個数」を返す関数です。すると、合計がちょうど S となる部分配列の個数は、次の差として求められ