C++で合計がkより大きい最長部分配列を求める方法
このチュートリアルでは、C++を使って「合計(総和)が k より大きい最長の部分配列」を見つけるプログラムを作成します。単純な全探索では O(n²) の計算量が必要ですが、累積和と二分探索を組み合わせることで、O(n log n) まで効率化できます。負の数を含む配列にも対応できるのが特徴です。
解法のアルゴリズム
以下の手順で問題を解いていきます。
- 配列を初期化します。
- 配列を先頭から走査し、各インデックスにおける累積和とそのインデックスをペアとして vector に格納します。
- 格納した累積和を、値とインデックスを基準にソートします。
- 最小インデックスを保持するための配列を初期化します。
- n 回繰り返すループを記述し、ソート済みの累積和配列を走査しながら、それまでに出現した最小のインデックスで値を更新していきます。
- 合計を 0 に初期化し直します。
- 再び n 回繰り返すループを記述します。
- 現在の要素を合計に加算します。
- 合計が k より大きい場合、最大部分配列の長さは
i + 1となります。 - そうでない場合は、二分探索を使って過去の累積和からインデックスを探します。「sum − k − 1」以下となる最大の累積和が、求めたい要素のインデックスです。
実装例
実際のコードを見てみましょう。
#include <bits/stdc++.h>
using namespace std;
// ペアを比較する関数(値を優先し、同値の場合はインデックス順)
bool compare(const pair<int, int>& a, const pair<int, int>& b) {
if (a.first == b.first) {
return a.second < b.second;
}
return a.first < b.first;
}
// val 以下の最大の累積和を持つ位置を二分探索で取得
int findIndex(vector<pair<int, int> >& previousSums, int n, int val) {
int start = 0;
int end = n - 1;
int mid, result = -1;
while (start <= end) {
mid = (start + end) / 2;
if (previousSums[mid].first <= val) {
result = mid;
start = mid + 1;
} else {
end = mid - 1;
}
}
return result;
}
int getLargestSubArray(int arr[], int n, int k) {
int maxLength = 0;
vector<pair<int, int> > previousSums;
int sum = 0, minIndexes[n];
// 各インデックスでの累積和を記録
for (int i = 0; i < n; i++) {
sum = sum + arr[i];
previousSums.push_back({ sum, i });
}
// 累積和をソート
sort(previousSums.begin(), previousSums.end(), compare);
// 先頭からの最小インデックスを前処理で計算
minIndexes[0] = previousSums[0].second;
for (int i = 1; i < n; i++) {
minIndexes[i] = min(minIndexes[i - 1], previousSums[i].second);
}
// 主処理:各区切り位置で最長の長さを更新
sum = 0;
for (int i = 0; i < n; i++) {
sum = sum + arr[i];
if (sum > k) {
maxLength = i + 1;
} else {
int ind = findIndex(previousSums, n, sum - k - 1);
if (ind != -1 && minIndexes[ind] < i) {
maxLength = max(maxLength, i - minIndexes[ind]);
}
}
}
return maxLength;
}
int main() {
int arr[] = { 5, 3, -3, 2, 4, 7 };
int k = 5, n = 6;
cout << getLargestSubArray(arr, n, k) << endl;
return 0;
}
出力結果
上記のコードを実行すると、次の結果が得られます。
6
この例では、配列全体 { 5, 3, -3, 2, 4, 7 } の合計が 18 となり、k = 5 を超えているため、条件を満たす最長の部分配列の長さは 6(配列全体)になります。
まとめ
本チュートリアルでは、累積和と二分探索を組み合わせることで、合計が k より大きい最長部分配列を O(n log n) の計算量で求める方法を紹介しました。負の数が含まれる配列でも正しく動作する点がポイントです。チュートリアルに関するご質問があれば、コメント欄でお気軽にお知らせください。
-
C++でmを法とする最大部分配列の総和を求めるアルゴリズム
C++でmを法とする最大部分配列の総和とは この問題では、サイズnの整数型配列と整数mが与えられます。求めるのは、すべての部分配列の総和の中から、mで割った余りが最大となる値です。 問題の概要 − 各部分配列の全要素の合計をmで割った余りを計算し、その中で最大の値を求めます。 具体例で問題を確認しよう まずは例を見て、問題の内容を理解しましょう。 入力 − array = {4, 9, 2}、m = 6 出力 − 5 説明 − すべての部分配列と、それぞれを6で割った余りは以下の通りです。 {4}: 4 % 6 = 4 {9}: 9 % 6 = 3 {2}: 2 % 6 = 2 {4, 9}
-
C++のプレフィックス和(累積和)を活用してO(n)で最大部分配列和を求める方法
問題概要 正の整数と負の整数が混在する配列が与えられたとき、その配列の中で合計値が最大となる部分配列(連続した要素の並び)の合計を求める問題です。 例 入力配列が {-12, -5, 4, -1, -7, 1, 8, -3} の場合、合計が最大になる部分配列は {1, 8} となるため、出力は 9 になります。 アルゴリズム この問題は、プレフィックス和(累積和)を利用することで O(n) の時間計算量で効率的に解くことができます。考え方の核心は、「ある位置 i で終わる部分配列の合計の最大値」は「prefix_sum[i] から、それ以前に現れた最小の累積和を引いた値」で表せるという点です