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

C++で1の個数が最大となるバイナリ行列の行番号を求める方法

この記事では、各行がソート済み(左側に0、右側に1が並ぶ)であるバイナリ行列が与えられたとき、1の個数が最も多い行の番号を求めるアルゴリズムを解説します。単純な全走査から二分探索、さらに最適化まで、段階的に改善できる3つのアプローチを紹介します。

問題の確認

まず、具体的な例で問題を理解しましょう。

入力

binMat[][] = {
    1, 1, 1, 1
    0, 0, 0, 0
    0, 0, 0, 1
    0, 0, 1, 1
}

出力

1

この例では、1行目(インデックス0)に4つの1が含まれているため、結果は「1」(1始まりの行番号)となります。

解法1:各行の1をカウントする単純な方法

最も直感的な解決策は、各行に含まれる1の総数を数え上げ、その中で最も多い行の番号を返すことです。時間計算量は O(R × C) です。

実装例

#include <iostream>
using namespace std;
#define R 4
#define C 4
int findMax1Row(bool mat[R][C]) {
    int max1Row = 0, max1Count = -1;
    int i, index;
    for (i = 0; i < R; i++) {
        int oneCount = 0;
        for(int j = 0; j < C; j++){
            if(mat[i][j])
                oneCount++;
        }
        if(oneCount > max1Count){
            max1Count = oneCount;
            max1Row = i;
        }
    }
    return (max1Row + 1);
}
int main() {
    bool mat[R][C] = {
        {0, 1, 1, 1},
        {0, 0, 1, 1},
        {0, 0, 0, 1},
        {0, 0, 0, 0}
    };
    cout<<"The number of row with maximum number of 1's is "<<findMax1Row(mat);
    return 0;
}

出力

The number of row with maximum number of 1's is 1

解法2:二分探索で最初の1の位置を見つける

各行がソートされていることを利用すると、二分探索によって「その行で最初に1が出現する位置」を効率よく見つけられます。行内の1の個数は「列数 − 最初の1のインデックス」で計算できます。この方法により、時間計算量を O(R log C) に抑えられます。

実装例

#include <iostream>
using namespace std;
#define R 4
#define C 4
int binarySearch1Row(bool arr[], int start, int end) {
    if(end >= start) {
        int mid = start + (end - start)/2;
        if ( ( mid == 0 || arr[mid-1] == 0) && arr[mid] == 1)
            return mid;
        else if (arr[mid] == 0)
            return binarySearch1Row(arr, (mid + 1), end);
        else
            return binarySearch1Row(arr, start, (mid -1));
    }
    return -1;
}
int findMax1Row(bool mat[R][C]) {
    int max1Row = 0, max1Count = -1;
    int i, index;
    for (i = 0; i < R; i++) {
        index = binarySearch1Row(mat[i], 0, C-1);
        if (index != -1 && ( C-index) > max1Count) {
            max1Count = C - index;
            max1Row = i;
        }
    }
    return (max1Row + 1);
}
int main() {
    bool mat[R][C] = {
        {0, 1, 1, 1},
        {0, 0, 1, 1},
        {0, 0, 0, 1},
        {0, 0, 0, 0}
    };
    cout<<"The number of row with maximum number of 1's is "<<findMax1Row(mat);
    return 0;
}

出力

The number of row with maximum number of 1's is 1

解法3:探索範囲を絞り込む最適化

上記のアプローチにさらに最適化を加えることができます。前の行で見つかった「最初の1のインデックス」を利用し、現在の行がそれより多くの1を含むかどうかを先に判定します。1の数が上回る可能性がある場合のみ二分探索を実行し、その際の探索範囲も「0 〜 前の行の最初の1のインデックス」までに限定します。

これにより、現在の記録よりも1の少ない行に対する無駄な計算を省き、処理のオーバーヘッドを大幅に削減できます。

実装例

#include <iostream>
using namespace std;
#define R 4
#define C 4
int binarySearch1Row(bool arr[], int start, int end) {
    if(end >= start) {
        int mid = start + (end - start)/2;
        if ( ( mid == 0 || arr[mid-1] == 0) && arr[mid] == 1)
            return mid;
        else if (arr[mid] == 0)
            return binarySearch1Row(arr, (mid + 1), end);
        else
            return binarySearch1Row(arr, start, (mid -1));
    }
    return -1;
}
int findMax1Row(bool mat[R][C]) {
    int i, index;
    int max1Row = 0;
    int max1Count = binarySearch1Row(mat[0], 0, C - 1);
    for (i = 1; i < R; i++){
        if (max1Count != -1 && mat[i][C - max1Count - 1] == 1) {
            index = binarySearch1Row (mat[i], 0, C - max1Count);
            if (index != -1 && C - index > max1Count) {
                max1Count = C - index;
                max1Row = i;
            }
        }
        else
        max1Count = binarySearch1Row(mat[i], 0, C - 1);
    }
    return (max1Row + 1);
}
int main() {
    bool mat[R][C] = {
        {0, 1, 1, 1},
        {0, 0, 0, 1},
        {0, 0, 1, 1},
        {0, 0, 0, 0}
    };
    cout<<"The number of row with maximum number of 1's is "<<findMax1Row(mat);
    return 0;
}

出力

The number of row with maximum number of 1's is 1

まとめ:各解法の計算量比較

本記事で紹介した3つのアプローチの計算量は以下のとおりです。

  • 解法1(全要素のカウント):O(R × C)
  • 解法2(二分探索):O(R log C)
  • 解法3(探索範囲の絞り込み):最悪時 O(R log C)。ただし探索範囲が狭まるため、実際には平均的により高速に動作します。

行列のサイズが大きい場合は、二分探索ベースの解法2や解法3を採用することで、パフォーマンスを大きく向上させることができます。

  1. C++で二分木の最大レベル和を求める方法

    問題概要 この問題では、正と負の値を含む二分木が与えられます。私たちのタスクは、二分木におけるレベル和の最大値を見つけることです。 問題の説明: 与えられた二分木に対して、各レベルに存在するすべてのノードの値の合計を計算し、その中で最も大きい値を返します。 具体例を使って問題を理解しましょう。 入力: 出力: 5 説明: レベル1の要素の合計:3 レベル2の要素の合計:-3 + 4 = 1 レベル3の要素の合計:5 - 1 + 6 - 5 = 5 各レベルの合計は「3」「1」「5」となるため、最大のレベル和は 5 となります。 解法アプローチ この問題を効率的に解くには、レベル順走査(幅優先

  2. C++で二分木の最大値(または最小値)を求める方法

    この記事では、二分木が与えられたときに、その中から最大値(または最小値)を持つノードを見つける方法を解説します。 問題の概要 与えられた二分木の中から、最大値および最小値を持つノードの値を求めるのが課題です。 入力例 出力例 max = 9 , min = 1 解法のアプローチ 二分木の最大値を求めるには、木全体を走査する必要があります。基本的な考え方は次のとおりです。 ルートノードから出発し、再帰的に左部分木と右部分木を走査します。 各ノードにおいて、そのノードの値・左部分木の最大値・右部分木の最大値を比較します。 最も大きい値を現在の最大値として返し、再帰的に結果を親ノードへ伝えてい