【问题标题】:Find number of bits to be flipped to get maximum 1's in array查找要翻转的位数以获得数组中的最大 1
【发布时间】:2014-01-26 10:10:23
【问题描述】:

我们有一个如下所示的位数组

{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

有没有很好的算法可以解决这个问题。我只考虑动态规划,因为这个问题可以分解成可以组合的子问题。

【问题讨论】:

  • 简单的 O(n²) 算法,只需检查所有范围(n 个起点 -> 最大 n 个端点)。
  • 我不认为它对downvote有效,因为一旦通过数组和@Zeta你提到O(n2)就可以完成这个算法,这不是最佳方式。
  • 我不明白这个问题。在您的示例中,答案不应该是 4 吗?如果你翻转 4 个零,你会得到 8 个,这是最大值。
  • 我其实觉得这个问题很有趣。 +1。
  • 我认为如果将 1 替换为 -1 并将 0 替换为 1,这会减少到最大子数组问题。

标签: algorithm bit-manipulation dynamic-programming


【解决方案1】:

以下代码使用平凡算法并在 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³):创建所有范围 (O(n²)) 并检查每个范围(范围长度线性)。因为我们有大约。 n² 范围我们有 O(n³)。
  • 尝试在 range_t 上运行这个给出错误它应该是 range_t result = make_pair(0,0);
  • @Alien01:C++11。使用-std=C++0x 在 4.6.2 上运行良好。您想要符合 C++03 的版本吗?
  • 不,谢谢..我编辑了代码以使其与我的编译器兼容。
【解决方案2】:

在 O(n) 中尝试 2.0

从数组的开头开始。遍历数组。直到到达 0。当到达第一个 0 时,将计数设置为 0,记住起始位置并在计数时继续步进:+1 表示 0,-1 表示 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;
}

未经彻底测试,可能存在错误(边缘情况),但原则上可行。

【讨论】:

  • 既然我们正在运行两个循环,那么这是 O(n)..我想这是 O(n2)
  • @Alien01 两个嵌套循环不一定意味着 O(n^2)。您已经仔细查看了第二个循环的开始位置。我的代码很匆忙,所以代码同样丑陋。如果重构代码,则不需要秒循环。
  • 以防万一@Alien01 表示 O(2n),而不是 O(n^2)(技术上 n2 = 2n,尽管 n2 通常用于表示 n^2):在大 O 表示法,所以 O(2n) = O(n)。
  • @Dukeling 哦,对。我的算法正好是 O(n)。顺便说一句,如果您可以尝试在各种输入上对其进行测试,您将是一个很大的帮助。
【解决方案3】:

受@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)

第一部分是一个常数,所以我们确实需要最大化翻转部分的总和。这正是最大段和问题的作用。


我写了一篇关于如何在线性时间内解决这个问题的冗长推导a while ago,所以现在我只分享代码。下面我更新了代码以存储边界。我选择了 javascript 作为语言,因为它很容易在浏览器中测试,而且我不必明确变量 xy 的类型。

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]

【讨论】:

  • 我看到了你之前的解决方案,它看起来很优雅..我很难理解这些解决方案..我很难思考和实施..你能推荐一些书吗?更多解释。
  • @Alien01 如果我没记错的话,Anne Kaldewaij 的算法推导以及整个工作方式都涵盖了最大分段问题。它基本上遵循了 Dijkstra 所宣扬的工作方式(它在像这样的小问题上效果很好,但对于现实生活中的问题却毫无用处),所以也许谷歌搜索 Dijkstra 也可以提供更多的指导。
  • 如果我们考虑上面的例子 { 1 0 1 0 0 1 0 1} 那么在更改 0->1 和 1->-1 数组将更改为 { -1 1 -1 1 1 -1 1 -1} 。如果我们在这个数组上取最大段总和,那么它将是 2 ,索引范围 =[3,4]。我无法理解我们如何才能从该解决方案中获得最大数量的要翻转的元素。
  • 好的,我可以得到的最大子数组为 [3,4]= [1,1]..如果我翻转原始数组中的 3,4 个索引,那么我将得到 {1 0 1 1 1 1 0 1} 这意味着在翻转 3,4 索引后我可以在原始数组上得到 6 个 1。所以这是对的吗?
  • 如果变量混淆:y = 以 n 结尾的最大子数组,x = 迄今为止看到的最大子数组。
【解决方案4】:
            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;
            }

【讨论】:

    【解决方案5】:

    这是一种递归方法: 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;
    }
    

    【讨论】:

      【解决方案6】:

      这个问题可以使用线性时间和空间的动态规划来解决。您可以创建一个数组 left ,其中 left[i] 是子数组 0 到 i(包括)上 1 的数量。所以对于两个索引 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,我们需要另一个数组 temp 来存储每个 j min{i-2*left[i-1] where i&lt;j}

      所以我们可以遍历数组,在每个索引 j 处,我们计算上述情况二(在恒定时间内),如果它更大,则更新最终结果。

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

      【讨论】:

        【解决方案7】:

        我的想法也和@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,记住起始位置并在计数时继续步进:+1 表示 0,-1 表示 1。Max 将 max(#zeros 的值存储在全部 [s, e])。如果c 大于Max,则当前集[s, e] 包含最大数量的“0”位。因此更新Max, S, E,。如果计数变为负数,则表示集合[se]中'1'的个数大于'0'的个数,因此重置计数c,本地开始s,本地结束e。并继续,直到你到达终点。如果您发现另一个零集计数为0 并重复前面的算法。 SE 的最终值是要翻转位的范围的索引。如果不存在这样的范围(所有位都是 '1'),则 S = -1, E = - 1.

        【讨论】:

          【解决方案8】:

          这个解决方案也受到@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;
          

          }

          【讨论】:

            【解决方案9】:

            解决方案使用 Kadane 算法。

            我们必须选择最大数量为 0 和最小数量为 1 的子字符串,即 max(count(0)-count(1)) 的子字符串。这样翻转后,我们可以得到最终字符串中的最大1个数。

            遍历字符串并保持计数。遇到 0 时增加此计数,遇到 1 时减少此计数。具有此计数最大值的子字符串将是我们的答案。

            这是 alGOds 的一段视频,它很好地解释了这种方法。如果您有任何疑问,请观看。

            链接:https://youtu.be/cLVpE5q_-DE

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-11-16
              • 1970-01-01
              • 2016-05-05
              • 1970-01-01
              • 2013-11-28
              相关资源
              最近更新 更多