web-dev-qa-db-ja.com

配列の最大1を取得するために反転するビット数を見つける

以下のようなビット配列があります

{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を取得できます

この問題を解決できる優れたアルゴリズムはありますか?この問題は、組み合わせることができるサブ問題に分解できるため、私は動的プログラミングについてのみ考えています。

19
anand

@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]
21

次のコードは自明なアルゴリズムを使用し、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;
}
2
Zeta

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;
}

完全にはテストされていませんが、バグ(エッジケース)がある可能性がありますが、原理的には機能します。

2
this

これは再帰的なアプローチです: 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;
}
0
bruhhhhh

私も@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/[se])。 cMaxより大きくなると、現在のセット[se]に最大数の「0」ビットが含まれます。したがって、Max, S, E,を更新します。カウントが負になった場合、セット[se]の「1」の数が「0」の数より大きいため、カウントをリセットしますc、ローカルスタートs、ローカルエンドe。そして最後に到達するまで続けます。別のゼロを見つけたら、カウントを0に設定し、前のアルゴリズムを繰り返します。 SEの最後の値は、ビットが反転される範囲のインデックスです。そのような範囲が存在しない場合(すべてのビットが「1」)、S = -1, E = - 1

0
Puneet Kumar

このソリューションは、@ 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;

}

0
Chandan
            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;
            }
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;
}
0
yfeng