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

C++で最大要素がkより大きい部分配列の数を求める方法

問題概要

整数要素を含む配列 arr[] と変数 k が与えられます。求めるのは、最大要素が k より大きい部分配列の個数です。

たとえば、配列が [1,2,3]、k が 1 の場合、考えられる部分配列は [1], [2], [3], [1,2], [2,3], [1,2,3] の6つです。このうち最大要素が 1 より大きいのは [2], [3], [1,2], [2,3], [1,2,3] の5つなので、答えは 5 となります。

入出力例

例1

入力: arr[] = {1,2,5,3}, k = 3

出力: 最大要素が k より大きい部分配列の数: 6

説明: すべての部分配列は [1], [2], [5], [3], [1,2], [2,5], [5,3], [1,2,5], [2,5,3], [1,2,5,3] の10個です。このうち最大要素が 3 より大きいのは、要素 5 を含む次の6つです。

[5], [2,5], [5,3], [1,2,5], [2,5,3], [1,2,5,3]

例2

入力: arr[] = {1,2,3,4,5}, k = 4

出力: 最大要素が k より大きい部分配列の数: 5

説明: 4 より大きい要素は 5 のみです。したがって、5 を含む次の5つの部分配列が該当します。

[5], [4,5], [3,4,5], [2,3,4,5], [1,2,3,4,5]

解法のアプローチ

まず、要素数 n の配列に含まれる部分配列の総数は n×(n+1)/2 であることを利用します。

次に、「すべての要素が k 以下」である部分配列の数を数えます。こうした部分配列は最大要素が k 以下のため条件を満たしません。具体的には、k より大きい要素を区切りとして配列を分割し、連続する「k 以下の要素」の区間ごとに長さ l を求め、それぞれ l×(l+1)/2 を加算していきます。この合計を X とすると、答えは n×(n+1)/2 − X で求められます。

この方法なら、全部分配列を実際に列挙することなく O(n) の計算量で答えを得られるのが大きな利点です。

アルゴリズムの手順

  • 整数配列 arr[] と変数 k を入力として受け取ります。
  • 関数 maximum_k(int arr[], int size, int k) は、配列・k・配列の長さを受け取り、最大要素が k より大きい部分配列の数を返します。
  • カウント用変数 count を 0 で初期化します。
  • while ループで、インデックス i = 0 から i < size まで配列を走査します。
  • arr[i] > k の場合は continue 文でスキップします(ここが区切りになります)。
  • それ以外の場合は、内側の while ループで連続する「k 以下の要素」の長さを数えます。
  • i < size かつ arr[i] <= k の間、i と temp(区間の長さ)をインクリメントします。
  • 内側のループを抜けたら、temp×(temp+1)/2 を計算して count に加算します。
  • 外側のループが終わった時点で、count には「すべての要素が k 以下」の部分配列の総数が入っています。
  • 最後に、全部分配列の数 size×(size+1)/2 から count を引いた値を結果として返します。

C++実装例

#include <bits/stdc++.h>
using namespace std;

int maximum_k(int arr[], int size, int k){
    int count = 0;
    int i = 0;
    while (i < size){
        // kより大きい要素はスキップ(区切りとして扱う)
        if (arr[i] > k){
            i++;
            continue;
        }
        int temp = 0;
        // 連続してk以下の要素が続く長さを数える
        while (i < size && arr[i] <= k){
            i++;
            temp++;
        }
        // 長さtempの区間から作れる部分配列の数を加算
        int temp_2 = temp * (temp + 1);
        count = count + temp_2 / 2;
    }
    // 全部分配列数から、最大要素がk以下の部分配列数を引く
    count = (size * (size + 1) / 2 - count);
    return count;
}

int main(){
    int arr[] = { 4, 1, 2, 7, 8, 3 };
    int k = 5;
    int size = sizeof(arr) / sizeof(arr[0]);
    cout << "Count of subarrays whose maximum element is greater than k are: " << maximum_k(arr, size, k);
    return 0;
}

実行結果

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

Count of subarrays whose maximum element is greater than k are: 14

この例では、配列 {4, 1, 2, 7, 8, 3} の全部分配列は 6×7/2 = 21 個あります。「すべての要素が 5 以下」の部分配列は、区間 {4,1,2} から 3×4/2 = 6 個、区間 {3} から 1 個の合計 7 個なので、21 − 7 = 14 が答えとなります。

  1. C++で配列の「直前のより大きい要素」を効率的に求める方法

    問題の概要この問題では、整数の配列が与えられます。配列の各要素について、その要素より前方(左側)に位置する要素の中で最大の値を見つけて出力します。該当する要素が存在しない場合は -1 を出力します。入出力例入力: {6, 2, 7, 1, 5, 3} 出力: -1, 6, -1, 7, 7, 7この例では、最初の要素「6」の前方には要素が存在しないため -1。2番目の要素「2」の前方にあるのは「6」だけなので 6。3番目の要素「7」の前方に「7」より大きい要素はないため -1。4番目の要素「1」の前方には「7」があるので 7。以降も同様に判定していきます。解法1: 二重ループによる単純なアプロ

  2. C++でソート済み配列から出現頻度がn/2以上の要素を検索する方法

    サイズnのソート済み配列を考えます。この配列には、出現回数がn/2以上(nは要素数)となる要素が必ず1つ存在します。例えば、配列が [3, 4, 5, 5, 5] の場合、出力は 5 となります。解法のポイントこの種の配列を注意深く観察すると、重要な性質に気づくことができます。それは、出現頻度がn/2以上の要素は、必ずインデックス n/2 の位置にも存在するという点です。なぜなら、ある要素が配列全体の半分以上を占めている場合、その要素は配列の中央位置(n/2)に必ず含まれるからです。したがって、配列がすでにソートされていることを利用すれば、線形探索やハッシュマップを使わずに、単一の要素アクセス