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

C++で配列のすべての一意な部分配列の合計値の総和を求める方法

この記事では、n個の整数からなる配列 arr[] が与えられたときに、一意な(重複しない)部分配列の合計値をすべて集め、その総和を求める方法をC++で解説します。ここでいう「部分配列の合計」とは、ある部分配列に含まれる全要素を足し合わせた値のことです。

問題の確認

まず、具体例を使って問題を理解しましょう。

入力: arr[] = {1, 2, 4}
出力: 23

解説:

配列のすべての部分配列は次のとおりです:
(1), (2), (4), (1, 2), (2, 4), (1, 2, 4)
部分配列の合計 = 1 + 2 + 4 + (1+2) + (2+4) + (1+2+4) = 23

解法1: ソートを利用するアプローチ

基本的な考え方は、すべての部分配列の合計値を一旦ベクトルに格納し、ソートすることで重複する値を見つけ出すというものです。その後、一意な値だけを合計に加算します。

アルゴリズムの手順

  1. ステップ1: すべての部分配列の合計を求め、vectorに格納します。
  2. ステップ2: vectorをソートします。
  3. ステップ3: 隣接する要素を比較し、重複する値をすべて0に置き換えます(一意な値のみ残す)。
  4. ステップ4: 合計を計算して出力します。

実装例

以下のプログラムは、この解法の動作を示したものです。

#include <bits/stdc++.h>
using namespace std;
long int findSumOfSubArraySum(int arr[], int n){
    int i, j;
    long int sumArrayTill[n + 1] = { 0 };
    for (i = 0; i < n; i++)
        sumArrayTill[i + 1] = sumArrayTill[i] + arr[i];
    vector<long int> subArraySum;
    for (i = 1; i <= n; i++)
        for (j = i; j <= n; j++)
            subArraySum.push_back(sumArrayTill[j] - sumArrayTill[i - 1]);
    sort(subArraySum.begin(), subArraySum.end());
    for (i = 0; i < subArraySum.size() - 1; i++){
        if (subArraySum[i] == subArraySum[i + 1]) {
            j = i + 1;
            while (subArraySum[j] == subArraySum[i] && j < subArraySum.size()){
                subArraySum[j] = 0; j++;
            }
            subArraySum[i] = 0;
        }
    }
    long sum = 0;
    for (i = 0; i < subArraySum.size(); i++)
        sum += subArraySum[i];
    return sum;
}
int main(){
   int arr[] = { 1, 2, 4, 7, 9 };
   int n = sizeof(arr) / sizeof(arr[0]);
   cout<<"The sum of all unique subarray sum is "<<findSumOfSubArraySum(arr, n);
   return 0;
}

出力

The sum of all unique subarray sum is 144

この方法では、部分配列の列挙にO(n²)、ソートにO(n² log n)の計算量が必要となるため、全体の時間計算量はO(n² log n)となります。

解法2: ハッシュテーブルを利用するアプローチ

より効率的な方法として、ハッシュテーブル(unordered_map)を使う方法があります。各部分配列の合計値をキーとしてハッシュマップに記録し、出現回数をカウントしていきます。最後に、出現回数がちょうど1回の(つまり一意な)部分配列の合計値だけを足し合わせます。

実装例

以下のプログラムは、この解法の動作を示したものです。

#include <bits/stdc++.h>
using namespace std;
long int findSumOfSubArraySum(int arr[], int n){
    int sumSubArraySum = 0;
    unordered_map<int, int> sumSubArray;
    for (int i = 0; i < n; i++) {
        int sum = 0;
        for (int j = i; j < n; j++) {
            sum += arr[j];
            sumSubArray[sum]++;
        }
    }
    for (auto itr : sumSubArray)
        if (itr.second == 1)
            sumSubArraySum += itr.first;
        return sumSubArraySum;
}
int main(){
   int arr[] = { 1, 2, 4, 7, 5 };
   int n = sizeof(arr) / sizeof(arr[0]);
   cout<<"The sum of all unique subarray sum is "<<findSumOfSubArraySum(arr, n);
   return 0;
}

出力

The sum of all unique subarray sum is 124

ハッシュテーブルを利用することでソート処理が不要になり、平均時間計算量はO(n²)に改善されます。

まとめ

配列のすべての一意な部分配列の合計値の総和を求める問題では、ソートによる重複除去と、ハッシュマップによる出現回数のカウントという2つのアプローチが有効です。特にハッシュマップを使う方法は、ソートを省略できるため、大規模な配列に対してより効率的に動作します。用途やデータサイズに応じて、適切な手法を選択しましょう。

  1. C++で二分木内のすべての左葉の合計を求める方法【再帰・DFS・BFSで解説】

    問題概要 この問題では、二分木が与えられ、その木に含まれるすべての「左葉(左の子である葉ノード)」の値の合計を求めることが課題となります。 具体例を使って問題を確認しましょう。 入力: 出力:11 説明− 木の左葉ノードは:2, 9 合計 = 2 + 9 = 11 ここで「左葉」とは、親ノードの左の子であり、かつ子を一切持たないノードを指します。上図の例では、ノード2とノード9がこの条件を満たすため、その合計値11が答えになります。 解決アプローチ 1:再帰 最もシンプルな解決策は、木をルートから葉へ向かって走査する方法です。走査の過程で、注目しているノードの左の子が葉ノードであ

  2. C++で完全二分木の全ノードの合計を効率的に求める方法

    問題の概要 正整数 L が与えられ、これは完全二分木(パーフェクト・バイナリツリー)のレベル数を表しているとします。この木の葉ノードには、1 から n までの番号が順に割り当てられています(n は葉ノードの総数)。また、各親ノードの値は、その 2 つの子ノードの値の合計となります。 今回の課題は、この完全二分木に含まれるすべてのノードの値の合計を出力するプログラムを作成することです。 例として、次のような木を考えてみましょう。 この木の場合、すべてのノードの合計は 30 になります。 解法のアプローチ この問題を注意深く観察すると、求めるべきは全ノードの値の総和です。葉ノードには 1 から