C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で合計がkより大きい最長部分配列を求める方法

このチュートリアルでは、C++を使って「合計(総和)が k より大きい最長の部分配列」を見つけるプログラムを作成します。単純な全探索では O(n²) の計算量が必要ですが、累積和二分探索を組み合わせることで、O(n log n) まで効率化できます。負の数を含む配列にも対応できるのが特徴です。

解法のアルゴリズム

以下の手順で問題を解いていきます。

  1. 配列を初期化します。
  2. 配列を先頭から走査し、各インデックスにおける累積和とそのインデックスをペアとして vector に格納します。
  3. 格納した累積和を、値とインデックスを基準にソートします。
  4. 最小インデックスを保持するための配列を初期化します。
  5. n 回繰り返すループを記述し、ソート済みの累積和配列を走査しながら、それまでに出現した最小のインデックスで値を更新していきます。
  6. 合計を 0 に初期化し直します。
  7. 再び 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) の計算量で求める方法を紹介しました。負の数が含まれる配列でも正しく動作する点がポイントです。チュートリアルに関するご質問があれば、コメント欄でお気軽にお知らせください。

  1. 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}

  2. C++のプレフィックス和(累積和)を活用してO(n)で最大部分配列和を求める方法

    問題概要 正の整数と負の整数が混在する配列が与えられたとき、その配列の中で合計値が最大となる部分配列(連続した要素の並び)の合計を求める問題です。 例 入力配列が {-12, -5, 4, -1, -7, 1, 8, -3} の場合、合計が最大になる部分配列は {1, 8} となるため、出力は 9 になります。 アルゴリズム この問題は、プレフィックス和(累積和)を利用することで O(n) の時間計算量で効率的に解くことができます。考え方の核心は、「ある位置 i で終わる部分配列の合計の最大値」は「prefix_sum[i] から、それ以前に現れた最小の累積和を引いた値」で表せるという点です