以下のようなビット配列があります
{1 0 1 0 0 1 0 1}
上記の配列のビット数は8です
[1,5]
から範囲を取得する場合、[1,5]
範囲のビット数は[0 1 0 0 1]
です。
この範囲を反転すると、反転後は[ 1 0 1 1 0]
になります
したがって、[1,5]
の範囲を反転した後の1の総数は[1 1 0 1 1 0 0 1] = 5
になります
[1,6]
から範囲を取得する場合、[1,6]範囲のビット数は[0 1 0 0 1 0]
です。
この範囲を反転すると、反転後は[ 1 0 1 1 0 1]
になります
[1,5]範囲を反転した後の1の総数は[1 1 0 1 1 0 1 1] = 6
です
したがって、答えは[1,6]
の範囲であり、反転した後、配列内に6つの1を取得できます
この問題を解決できる優れたアルゴリズムはありますか?この問題は、組み合わせることができるサブ問題に分解できるため、私は動的プログラミングについてのみ考えています。
@Nabbsコメントに触発されて、線形時間でこれを解決する簡単な方法があります:問題を最大セグメント合計に減らすことによって。
すべて0を1に、すべて1を-1に変換します。その場合、問題は、変換後に配列の合計を最小化することと同じです。 (最小合計には、元の問題のほとんどの1に対応する、変換された配列のほとんどの-1が含まれます)。
合計は次のように計算できます
sum(after flipping) = sum(non-flipped) - sum(flipped part before flipping)
反転した部分の合計が反転するためです。ここで、反転されていない部分を次のように表現するとします。
sum(non-flipped) = sum(original array) - sum(flipped part before flipping)
最小化する必要があることがわかりました
sum(after flipping) = sum(original array) - 2 sum(flipped part before flipping)
最初の部分は定数なので、反転した部分の合計を最大化する必要があります。これはまさに最大セグメント合計問題が行うことです。
この問題を線形時間で解決する方法について長い派生物を書いた しばらく前に なので、今はコードのみを共有する。以下では、境界も保存するようにコードを更新しました。ブラウザーでのテストが非常に簡単であり、変数のタイプx
およびy
を明示的にする必要がないため、私は言語としてjavascriptを選択しました。
var A = Array(1, 0, 1, 0, 0, 1, 0, 1);
var sum = 0;
// count the 1s in the original array and
// do the 0 -> 1 and 1 -> -1 conversion
for(var i = 0; i < A.length; i++) {
sum += A[i];
A[i] = A[i] == 0 ? 1 : -1;
}
// find the maximum subarray
var x = { value: 0, left: 0, right: 0 };
var y = { value: 0, left: 0 };
for (var n = 0; n < A.length; n++) {
// update y
if (y.value + A[n] >= 0) {
y.value += A[n];
} else {
y.left = n+1;
y.value = 0;
}
// update x
if (x.value < y.value) {
x.left = y.left;
x.right = n;
x.value = y.value;
}
}
// convert the result back
alert("result = " + (sum + x.value)
+ " in range [" + x.left + ", " + x.right + "]");
これはブラウザで簡単に確認できます。たとえばChromeでは、F12キーを押し、[コンソール]をクリックして、このコードを貼り付けます。出力するはずです
result = 6 in range [1, 4]
次のコードは自明なアルゴリズムを使用し、O(n²)で実行されます。
#include <iostream>
#include <bitset>
#include <utility>
typedef std::pair<unsigned, unsigned> range_t;
template <std::size_t N>
range_t max_flip(const std::bitset<N>& bs){
int overall_score = 0;
range_t result = range_t{0,0};
for(std::size_t i = 0; i < N; ++i){
int score = bs[i] ? -1 : 1;
auto max = i;
for(std::size_t j = i + 1; j < N; ++j){
auto new_score = score + (bs[j] ? -1 : 1);
if(new_score > score){
score = new_score;
max = j;
}
}
if(score > overall_score){
overall_score = score;
result = {i,max};
}
}
return result;
}
int main(){
std::bitset<8> bitfield(std::string("10100101"));
range_t range = max_flip(bitfield);
std::cout << range.first << " .. " << range.second << std::endl;
}
O(n)での試行2.0
配列の先頭から始めます。アレイをステップスルーします。 0に達するまで。最初の0に達したら、countを0に設定し、開始位置を覚えて、カウントしながらステップを続けます:+1
0および-1
for 1.カウントが負になった場合は、カウントをリセットし、最後に到達するまで続行します。別のゼロを見つけたら、カウントを0に設定し、前のアルゴリズムを繰り返します。最後に、開始位置と終了位置がある場合は、その範囲を反転します。
void Flip( int* arr , int len )
{
int s = -1 , e = -1 , c ;
for( int i = 0 ; i < len ; i++ )
{
if( arr[i] == 0 )
{
c = 0 ;
s = i ;
e = i ;
for( int j = i ; j < len ; j++ , i++ )
{
if( arr[i] == 0 )
c++ ;
else
c-- ;
if( c < 0 )
{
s = -1 ;
e = -1 ;
break ;
}
if( arr[i] == 0 )
e = i ;
}
}
}
if( s > -1 )
for( int i = s ; i <= e ; i++ )
arr[i] ^= 1 ;
for( int i = 0 ; i < len ; i++ )
printf("%d " , arr[i] ) ;
}
int main(void)
{
int a[13] = {1,0,1,1,0,0,1,0,1,1,0,1,0} ;
Flip( a , 13 ) ;
return 0;
}
完全にはテストされていませんが、バグ(エッジケース)がある可能性がありますが、原理的には機能します。
これは再帰的なアプローチです: https://ideone.com/Su2Mmb
public static void main(String[] args) {
int [] input = {1, 0, 0, 1, 0, 0, 1,1,1,1, 0,1};
System.out.println(findMaxNumberOfOnes(input,0, input.length-1));
}
private static int findMaxNumberOfOnes(int[] input, int i, int j) {
if (i==j)
return 1;
int option1 = input[i] + findMaxNumberOfOnes(input, i+1, j);
int option2 = count(input , i , j, true);
int option3 = count(input, i, j, false);
int option4 =findMaxNumberOfOnes(input, i, j-1) +input[j];
return Math.max(option1, Math.max(option2,Math.max(option3,option4)));
}
private static int count(int[] input, int i, int j, boolean flipped) {
int a = flipped?0:1;
int count = 0;
while (i<=j){
count += (input[i++]==a)?1:0;
}
return count;
}
私も@thisが述べたのと同じ方法で考えました。しかし、彼の解決策にはバグがあります。バグを修正した後のコード(以下の説明を参照):
vector<int> Solution::flip(string arr) {
int s = -1 , e = -1 , c , len = arr.size(), S = -1, E = -1, Max = 0;
for( int i = 0 ; i < len ; i++ )
{
if( arr[i] == '0' )
{
c = 0 ;
s = i ;
e = i ;
for( int j = i ; j < len ; j++, i++ )
{
if( arr[j] == '0' )
c++ ;
else
c-- ;
//cout << c << " ";
if( c < 0 )
{
s = -1 ;
e = -1 ;
break ;
}
if( arr[j] == '0' )
e = i ;
if(c > Max){
S = s;
E = e;
Max = c;
}
}
}
}
vector<int> ans;
if( S > -1 ){
ans.Push_back(S);
ans.Push_back(E);
return ans;
}
else
return ans;
}
説明:配列の先頭から開始します。アレイをステップスルーします。 0に到達するまで。最初の0に到達したら、countを0に設定し、開始位置を覚えてカウントしながらステップを続けます。0の場合は+1、1の場合は-1です。Max
は、max(#zeros in all set/[s
、e
])。 c
がMax
より大きくなると、現在のセット[s
、e
]に最大数の「0」ビットが含まれます。したがって、Max, S, E,
を更新します。カウントが負になった場合、セット[s
、e
]の「1」の数が「0」の数より大きいため、カウントをリセットしますc
、ローカルスタートs
、ローカルエンドe
。そして最後に到達するまで続けます。別のゼロを見つけたら、カウントを0
に設定し、前のアルゴリズムを繰り返します。 S
、E
の最後の値は、ビットが反転される範囲のインデックスです。そのような範囲が存在しない場合(すべてのビットが「1」)、S = -1, E = - 1
。
このソリューションは、@ Nabbのコメントにも触発されています。 0を1に、1を-1に置き換えた新しいアレイを作成しました。次に、最大サブアレイ合計範囲問題の概念を使用して解決しました。コードは次のとおりです。
vector<int> Solution::flip(string A) {
vector<int> vec;
vector<int> res;
for(int i=0;i<A.length();i++){
if(A[i]=='1')
vec.Push_back(-1);
else
vec.Push_back(1);
}
int l=0,r=0,s=0;
int sum=0;
int sum_prev=INT_MIN;
for(int i=0;i<vec.size();i++){
sum+=vec[i];
if(sum_prev<sum){
sum_prev=sum;
l=s;
r=i;
}
if(sum<0){
sum=0;
s=i+1;
}
}
//cout<<"l: "<<l<<" r: "<<r<<endl;
if((l>=0 && r>0)||((l==0 && r==0) && A[0]=='0')){
res.Push_back(l+1);
res.Push_back(r+1);
}
return res;
}
void maxones(int n)
{
int table[n+1][n+1], i, j, totalones = 0, max = INT_MIN, start_pos = 0, end_pos =0;
if(n == 0)
{
printf("Max no of ones from 0 to %d \n",sizeof(int) * 8 -1);
return;
}
/* size of (int) * 8 bits, calculate total no of ones in every bit */
for(i=0; i<sizeof(n) * 8; i++)
totalones += n & (1 >> i);
/* Base conditions to be filled */
for(i=0; i<n; i++)
table[i][i] = (n & (1 >> i)) ? totalones - 1 : totalones + 1;
for(i=0; i<n; i++ )
for(j=i+1; j<n; j++)
{
table[i][j] = table[i][j-1] + ( n & (1 >> j)) ? 0 : 1;
if (max < table[i][j])
{
max = table[i][j];
start_pos = i;
end_pos = j;
}
}
printf("Max no of Ones on fliping bits from pos %d to pos %d \n",start_pos, end_pos);
}
int main()
{
int n;
printf("Enter number %d \n", &n);
maxones(n);
return 0;
}
この問題は、線形計画と線形空間で動的計画法を使用して解決できます。左に配列を作成できます。ここで、left [i]はサブ配列0からiまでの1の数です(両端を含む)。したがって、2つのインデックスiとjの場合:
case 1: i==j, result is array size sz-1 (if no 0 in array) or sz+1 (if there is at least one 0 in array)
case 2: i less than j, result is:
left[i-1] (# of 1 on subarray 0 ~ i-1) +
(j-i+1-(left[j]-left[i-1])) (# of 0 on subarray i ~ j) +
left[sz-1]-left[j] (# of 1 on subarray j+1 ~ sz-1)
this equals to: (j-2*left[j])-(i-2*left[i-1])+left[sz-1]+1
したがって、ケース2によると、jごとに保存する別の配列tempが必要ですmin{i-2*left[i-1] where i<j}
したがって、配列を走査して、各インデックスjで、上記のケース2を(一定時間で)計算し、大きい場合は最終結果を更新します。
C++での私のコード:
int func(vector<int>& arr){
int res = 0;
int sz = arr.size();
vector<int> left(sz, 0);
for(int i=0; i<sz; i++){
left[i] = (arr[i]==1?1:0)+(i==0?0:left[i-1]);
}
bool all_1 = true;
for(int i=0; i<sz; i++){
if(arr[i] == 0) all_1=false;
}
if(all_1) return sz-1;
res = left[sz-1]+1;
vector<int> temp(sz, INT_MAX);
for(int i=1; i<sz; i++)
temp[i] = min(temp[i-1], i-2*left[i-1]);
for(int j=1; j<sz; j++){
int val = j+1-left[j]+(left[sz-1]-left[j]);
val = max(val, j-2*left[j]-temp[j]+left[sz-1]+1);
res = max(res, val);
}
return res;
}