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

C++でバイナリ配列内の0のみ・1のみからなる部分配列を数える方法

0と1のみで構成される配列 arr[] が与えられたとき、各部分配列が「0のみ」または「1のみ」を含むような部分配列の総数を求めるのが本記事の目的です。

例えば、配列が [1,0,0] の場合、部分配列は以下のようになります。

  • 0のみを含む部分配列:[0]、[0]、[0,0]
  • 1のみを含む部分配列:[1]

具体例で理解しよう

入力: arr[] = { 0, 0, 1, 1, 1, 0 }

出力:

0のみを含む部分配列の数: 4
1のみを含む部分配列の数: 6

解説: 各部分配列は以下の通りです。

0のみの場合: [0], [0], [0], [0,0] の計4つ ( arr[0], arr[1], arr[5], arr[0-1] )
1のみの場合: [1], [1], [1], [1,1], [1,1], [1,1,1] の計6つ ( arr[2], arr[3], arr[4],
arr[2-3], arr[3-4], arr[2-4] )

入力: arr[] = { 1, 0, 1, 0 }

出力:

0のみを含む部分配列の数: 2
1のみを含む部分配列の数: 2

解説:

0のみの場合: [0], [0] の計2つ ( arr[1], arr[3] )
1のみの場合: [1], [1] の計2つ ( arr[0], arr[2] )

アルゴリズムの考え方

このプログラムでは、0のみを含む部分配列と1のみを含む部分配列をそれぞれ独立に数えるため、配列を2回走査します。連続する0の個数と連続する1の個数を記録するカウンター count_0 および count_1 を用意します。

ここで重要なポイントは、「長さ n の同一要素の連続列」から作れる部分配列の総数は n×(n+1)/2 という組み合わせの公式で求められることです。例えば、連続する1が3個ある場合、[1]、[1]、[1]、[1,1]、[1,1]、[1,1,1] の6つ = 3×4/2 となります。

この値を配列の末尾に達するまで合計していき、total_0(および total_1)として累積します。

手順の詳細

  • 数値の配列 arr[] を用意します。
  • 関数 sub_zero_one(int arr[], int size) は、配列を受け取り、0のみを含む部分配列の数と1のみを含む部分配列の数を返します。
  • 部分配列の累積カウントとして、初期値 temp_0 と temp_1 を用意します。
  • 連続する0と1の一時的な個数として count_0 と count_1 を用意します。
  • forループを使い、i=0 から i<size まで配列を走査します。
  • 現在の要素が0であれば count_0 をインクリメントします。
  • そうでなければ、それまでの count_0 個の0から作れる部分配列の数 temp_one_0 = count_0 × (count_0 + 1) / 2 を計算します。
  • この値をこれまでの temp_0 に加算し、count_0 をリセットします。
  • 同様の手順を、変数 count_1、temp_one_1、temp_1 を使って1についても実行します。
  • 両方の走査が終わった時点で、temp_0 と temp_1 にはそれぞれ0のみ・1のみを含む部分配列の総数が格納されています。

C++実装例

#include <bits/stdc++.h>
using namespace std;
void sub_zero_one(int arr[], int size){
    int count_1 = 0;
    int count_0 = 0;
    int temp_1 = 0;
    int temp_0 = 0;
    // 1のみを含む部分配列をカウント
    for (int i = 0; i < size; i++){
        if (arr[i] == 1){
            count_1++;
        }
        else{
            int temp_one_1 = (count_1) * (count_1 + 1) / 2;
            temp_1 = temp_1 + temp_one_1;
            count_1 = 0;
        }
    }
    // 0のみを含む部分配列をカウント
    for (int i = 0; i < size; i++){
        if (arr[i] == 0)
            { count_0++; }
        else{
            int temp_one_0 = (count_0) * (count_0 + 1) / 2;
            temp_0 = temp_0 + temp_one_0;
            count_0 = 0;
        }
    }
    // 配列末尾で連続列が残っている場合の処理
    if (count_1){
        int temp_one_1 = (count_1) * (count_1 + 1) / 2;
        temp_1 = temp_1 + temp_one_1;
    }
    if (count_0){
        int temp_one_0 = (count_0) * (count_0 + 1) / 2;
        temp_0 = temp_0 + temp_one_0;
    }
    cout<<"Subarrays with only 0's are : "<<temp_0;
    cout<<"\nSubarrays with only 1's are : "<<temp_1;
}
int main(){
    int arr[] = { 0, 0, 0, 1, 1, 0, 1};
    int size = sizeof(arr) / sizeof(arr[0]);
    sub_zero_one(arr, size);
    return 0;
}

実行結果

上記のコードを実行すると、以下の出力が得られます。

Subarrays with only 0's are : 7
Subarrays with only 1's are : 4

まとめ

本記事では、0と1のみで構成されるバイナリ配列において、「0のみ」または「1のみ」を含む部分配列の数を効率的に数える方法を解説しました。連続する同じ要素の区間ごとに n×(n+1)/2 の公式を適用することで、配列を2回走査するだけで答えを求められます。時間計算量は O(N)、空間計算量は O(1) となり、非常に効率的なアプローチです。

  1. C++で二分木の完全ノードを数える方法(反復法と再帰法)

    本記事では、二分木に含まれる「完全ノード(フルノード)」の数を、反復法と再帰法の2つのアプローチで求める方法を解説します。完全ノードとは、左と右の子を両方持ち、どちらの子もNULLでないノードのことです。つまり、ちょうど2つの子を持つノードのみが完全ノードとして扱われます。 二分木はデータの格納に用いられる特殊なデータ構造です。「各ノードが最大2つの子までしか持てない」という制約があり、ソート済み配列並みの高速な検索性能と、連結リスト並みの高速な挿入・削除性能を兼ね備えているのが特徴です。なお、1つ以上の子を持つ非葉ノードは「親ノード」とも呼ばれます。 二分木の基本構造は以下の通りです。

  2. C++で二分木の半ノード(ハーフノード)を数える方法【反復・再帰の両アプローチ】

    本記事では、二分木(バイナリツリー)に含まれる「半ノード(ハーフノード)」の数を、反復処理と再帰処理の2つのアプローチで求める方法を解説します。半ノードとは?半ノードとは、子を1つだけ持ち、もう片方の子がNULL(空)になっているノードのことです。なお、子をまったく持たない葉ノードは半ノードには含まれない点に注意してください。二分木はデータの格納に使われる特殊なデータ構造です。各ノードが最大2つの子を持つという制約があり、ソート済み配列並みの高速な検索と、連結リスト並みの高速な挿入・削除の両方を実現できるというメリットがあります。二分木の基本的な構造は以下の通りです。具体例入力:出力: カウン