C++でバイナリ文字列における0と1の個数差の最大値を求める方法
この記事では、与えられたバイナリ文字列(「0」と「1」のみで構成される文字列)から部分文字列を取り出し、その中に含まれる「0」の個数と「1」の個数の差の最大値を求める方法を解説します。
問題の確認
具体例を使って、何をすべきかを見ていきましょう。
入力
str = "100100110"
出力
3
説明
位置1〜5に対応する部分文字列「00100」には「0」が4個、「1」が1個含まれているため、差は 4 − 1 = 3 となります。これが求められる最大値です。
入力
str = "00000"
出力
5
説明
文字列がすべて「0」で構成されている場合は、文字列全体を選ぶことで差は5になります。
アルゴリズムの考え方
この問題は、「0」を +1 、「1」を −1 に置き換えて数列に変換したうえで、その最大部分配列和(Kadaneのアルゴリズムと同じ発想)を求める問題に帰着できます。「0」が多い区間ほど値は大きくなり、「1」が多い区間ほど値は小さくなるためです。
以下のプログラムでは、メモ化再帰を用いてこの値を効率よく計算しています。
プログラムの処理手順
main() 関数内でバイナリ文字列を格納する string 型変数 str を作成し、文字列のサイズを格納する int 型変数 size を初期化して、両方を Max() 関数に渡します。
Max() 関数では、まず One() 関数を呼び出して、すべての要素が「1」であるかどうかを判定します。
bool 型の One() 関数を作成し、内部で int 型変数 O = 0 を用意します。
i = 0 から str.size() 未満までループし、str[i] が '1' であれば変数 O に 1 を加算します。
ループを抜けた後、O == size が成立していれば true を返します。
Max() 関数に戻り、One() が true を返した場合は答えとして -1 を返します(すべて「1」の場合は「0」の個数が常に少なくなるため)。
それ以外の場合は各区間の差の計算へ進みます。配列 int a[100] = { 0 } を初期化します。
i = 0 から size 未満までループし、a[i] = (str[i] == '0' ? 1 : -1) として文字列の各要素を数値に変換します。
ループの外でもう一つの配列 int arr[100][3] を用意し、memset(arr, -1, sizeof arr) ですべての要素を -1 で初期化した後、Length(a, str, size, 0, 0, arr) を呼び出します。
Length() 関数では、最初に i >= size かどうかを判定します。真であれば文字列を読み終えたことを意味するので 0 を返します。
次に arr[i][s] != -1 かどうかを判定します。真であれば、その状態はすでに計算済みなので、記憶しておいた arr[i][s] の値をそのまま返します(メモ化)。
s == 0 の場合は、arr[i][s] = max(a[i] + Length(a, str, size, i + 1, 1, arr), Length(a, str, size, i + 1, 0, arr)) を返します。これは「現在の位置から部分文字列を始めるか」「次の位置へスキップするか」を選択することに相当します。
それ以外の場合(部分文字列の選択中)は、arr[i][s] = max(a[i] + Length(a, str, size, i + 1, 1, arr), 0) を返し、区間の合計が負になった時点で切り捨てます。
C++実装例
#include <bits/stdc++.h>
using namespace std;
bool One(string str, int size){
int O = 0;
for (int i = 0; i < str.size(); i++)
O += (str[i] == '1');
return (O == size);
}
int Length(int a[], string str, int size,
int i, int s, int arr[][3]){
// 文字列を読み終えた場合
if (i >= size)
return 0;
// すでに計算済みの場合(メモ化)
if (arr[i][s] != -1)
return arr[i][s];
if (s == 0)
return arr[i][s] = max(a[i] +
Length(a, str, size, i + 1, 1, arr),
Length(a, str, size, i + 1, 0, arr));
else
return arr[i][s] = max(a[i] +
Length(a, str, size, i + 1, 1, arr), 0);
}
int Max(string str, int size){
// すべての要素が「1」かどうかを確認
if (One(str, size))
return -1;
// 各区間の差を計算
int a[100] = { 0 };
for (int i = 0; i < size; i++)
a[i] = (str[i] == '0' ? 1 : -1);
int arr[100][3];
memset(arr, -1, sizeof arr);
return Length(a, str, size, 0, 0, arr);
}
// main関数
int main(){
string str = "100100110";
int size = 9;
cout << Max(str, size);
return 0;
}
出力
3
計算量について
メモ化により各状態 (i, s) は一度しか計算されないため、時間計算量は文字列の長さを N として O(N)、メモテーブルと再帰スタックのため空間計算量も O(N) となります。これにより、単純な全探索(O(N²))よりも効率的に答えを求められます。
-
C++で解く二分木の「ノードと祖先の最大差」アルゴリズム
二分木のルートが与えられたとき、異なる2つのノードAとB(AはBの祖先)が存在し、V = |Aの値 − Bの値| となるような最大値Vを求める問題を考えてみましょう。例えば、次のような二分木が与えられた場合を考えます。この場合、出力は 7 となります。祖先と子孫のノード間の差は [(8 - 3), (7 - 3), (8 - 1), (10 - 13)] のようになり、その中で最大なのは (8 - 1) = 7 だからです。解法のアプローチこの問題を解くには、以下の手順に従います。まず、答えを格納する変数 ans を 0 で初期化します。solve() というメソッドを定義します。このメソッド
-
C++の比較演算子(==)とstd::string::compare()の違いを徹底解説
C++の比較演算子(==)とstd::string::compare()の違いとは? C++で文字列を比較する方法として、比較演算子 == と std::string::compare() の2つがあります。結論から言うと、この2つの違いは戻り値だけです。実際、string::operator==() は内部的に string::compare() を呼び出して実装されています。 戻り値の違い 比較演算子(==)は、2つの文字列が等しいかどうかを示す bool 型の値(true / false)を返します。一方、compare() は、2つの文字列が互いにどのような関係にあるかを示す int