【问题标题】:Space-efficient algorithm for finding the largest balanced subarray?寻找最大平衡子阵列的节省空间的算法?
【发布时间】:2011-09-11 20:33:04
【问题描述】:

给定一个由 0 和 1 组成的数组,找到最大子数组,使得 0 和 1 的数量相等。 这需要在 O(n) 时间和 O(1) 空间内完成。

我有一个算法,它在 O(n) 时间和 O(n) 空间内完成。它使用前缀和数组并利用这样一个事实,即如果 0 和 1 的数量相同,则 sumOfSubarray = lengthOfSubarray/2

#include<iostream>
#define M 15

using namespace std;

void getSum(int arr[],int prefixsum[],int size) {
    int i;
    prefixsum[0]=arr[0]=0;
    prefixsum[1]=arr[1];
    for (i=2;i<=size;i++) {
        prefixsum[i]=prefixsum[i-1]+arr[i];
    }
}

void find(int a[],int &start,int &end) {
    while(start < end) {
        int mid = (start +end )/2;
        if((end-start+1) == 2 * (a[end] - a[start-1]))
                break;
        if((end-start+1) > 2 * (a[end] - a[start-1])) {
            if(a[start]==0 && a[end]==1)
                    start++; else
                    end--;
        } else {
            if(a[start]==1 && a[end]==0)
                    start++; else
                    end--;
        }
    }
}

int main() {
    int size,arr[M],ps[M],start=1,end,width;
    ;
    cin>>size;
    arr[0]=0;
    end=size;
    for (int i=1;i<=size;i++)
            cin>>arr[i];
    getSum(arr,ps,size);
    find(ps,start,end);
    if(start!=end)
            cout<<(start-1)<<" "<<(end-1)<<endl; else cout<<"No soln\n";
    return 0;
}

【问题讨论】:

  • 我想知道如果您想象零已被替换为负一,这样做是否更容易。这样子数组的总和为零。
  • 我们可以修改原始数组吗?
  • 这是获得 O(1) 的一个选项。但是有更好的方法吗?
  • 既然这是一个家庭作业,我们可以假设有一个 O(1) 的存储解决方案吗?
  • 您发布的算法似乎不正确。输入 4 作为大小,输入 1, 1, 1, 0 作为数组数据,你会发现答案是没有解决方案。原因在于if(a[start]==0 &amp;&amp; a[end]==1)if(a[start]==1 &amp;&amp; a[end]==0) 行。 a 这里引用了前缀 sum 数组;我相信您打算让它参考原始数据。确实,条件a[start]==1 &amp;&amp; a[end]==0 是不可能的;因为start &lt; end 并且数组中没有元素是负数,所以直到start 的前缀总和不能大于直到end

标签: arrays algorithm binary-data


【解决方案1】:

现在我的算法是 O(n) 时间和 O(Dn) 空间,其中 Dn 是列表中的总不平衡。

此解决方案不会修改列表。

令 D 为列表中 1 和 0 的差。

首先,让我们线性地遍历列表并计算 D,看看它是如何工作的:

我将以这个列表为例:l=1100111100001110

Element   D
null      0
1         1
1         2   <-
0         1
0         0
1         1
1         2
1         3
1         4
0         3
0         2
0         1
0         0
1         1
1         2
1         3
0         2   <-

找到最长的平衡子数组相当于在 D 中找到 2 个相等的元素,它们距离更远。 (在本例中用箭头标记的 2 个 2s。)

最长的平衡子数组在元素+1的第一次出现和元素的最后一次出现之间。 (第一个箭头 +1 和最后一个箭头:00111100001110)

备注:

最长的子数组总是在 D 的 2 个元素之间 在 [0,Dn] 之间,其中 Dn 是 D 的最后一个元素。(Dn = 2 在 前面的例子)Dn 是 1 和 0 之间的总不平衡 列表。 (如果 Dn 为负数,则为 [Dn,0])

在这个例子中,这意味着我不需要“看” 3s 或 4s

证明:

令 Dn > 0 。

如果有一个由 P (P > Dn) 分隔的子数组。由于 0

出于同样的原因,P 不能小于 0

Dn 的证明是一样的

现在让我们处理 D,D 不是随机的,两个连续元素之间的差值始终为 1 或 -1。 Ans 在 D 和初始列表之间存在一个简单的双射。因此我有 2 个解决这个问题的方法:

  • 第一个是跟踪每个的第一次和最后一次出现 D 中介于 0 和 Dn 之间的元素(参见备注)。
  • 第二个是将列表转换为D,然后在D上工作。

第一个解决方案

目前我找不到比第一个更好的方法:

首先计算 Dn (in O(n)) 。 Dn=2

第二个不是创建 D,而是创建一个字典,其中键是 D 的值(在 [0 和 Dn] 之间),每个键的值是一对 (a,b),其中 a 是第一次出现的键和 b 最后一个。

Element   D DICTIONNARY
null      0 {0:(0,0)}
1         1 {0:(0,0) 1:(1,1)}
1         2 {0:(0,0) 1:(1,1) 2:(2,2)}
0         1 {0:(0,0) 1:(1,3) 2:(2,2)}
0         0 {0:(0,4) 1:(1,3) 2:(2,2)}
1         1 {0:(0,4) 1:(1,5) 2:(2,2)}
1         2 {0:(0,4) 1:(1,5) 2:(2,6)}
1         3 { 0:(0,4) 1:(1,5) 2:(2,6)}
1         4 {0:(0,4) 1:(1,5) 2:(2,6)}  
0         3{0:(0,4) 1:(1,5) 2:(2,6) }
0         2 {0:(0,4) 1:(1,5) 2:(2,9) }
0         1 {0:(0,4) 1:(1,10) 2:(2,9) } 
0         0 {0:(0,11) 1:(1,10) 2:(2,9) } 
1         1 {0:(0,11) 1:(1,12) 2:(2,9) } 
1         2 {0:(0,11) 1:(1,12) 2:(2,13)}
1         3 {0:(0,11) 1:(1,12) 2:(2,13)} 
0         2 {0:(0,11) 1:(1,12) 2:(2,15)} 

您选择了差异最大的元素:2:(2,15) 并且是 l[3:15]=00111100001110(其中 l=1100111100001110)。

时间复杂度:

2 次,第一次计算 Dn,第二次构建 字典。 在字典中找到最大值。

总计为 O(n)

空间复杂度:

D 中的当前元素:O(1) 字典 O(Dn)

因为这句话,字典里没有取3和4

复杂度是O(n)时间和O(Dn)空间(平均情况下Dn

我想这种方法可能有比字典更好的方法。

欢迎提出任何建议。

希望对你有帮助


第二个解决方案(只是一个想法而不是真正的解决方案)

第二种方法是将您的列表转换为 D。(因为从 D 回到列表很容易,所以没问题)。 (O(n) 时间和 O(1) 空间,因为我将列表转换到位,即使它可能不是“有效” O(1))

然后,您需要从 D 中找到距离较远的 2 个相等的元素。

看起来像是在链表中找到最长的循环,对Richard Brent algorithm的修改可能会返回最长的循环,但我不知道该怎么做,它需要 O(n) 时间和 O(1 ) 空间。

找到最长的周期后,返回第一个列表并打印。

这个算法需要 O(n) 时间和 O(1) 空间复杂度。

【讨论】:

  • 如果您需要转换列表,那么它不是 O(1) 空间。
  • @Tomas T. 为什么会这样?如果我修改它(这就是我正在做的)?无论如何,我的第二个解决方案只是一个想法,因为我还没有找到链表中最长循环的算法。目前只有第一个有效,在 O(n) 时间和 O(M) 空间中,其中 M
  • @Tomas T. 老实说,我怀疑这在 O(1) 空间中也是可能的。
  • 我认为修改输入是不允许的,即转换输入列表不是O(1)空间。
  • 我认为修改输入是允许的,但只是在输入的空间内。当你用 n 次 log n 替换一个 1 位条目(或者在这里取任何你想要的常量,8 位字符,这无关紧要)(什么是最大可能的数字......,正确的 n)时,所需的空间是 O (n log n)。
【解决方案2】:

不同的方法,但仍然是 O(n) 时间和内存。从尼尔的建议开始,把0当作-1。

符号:A[0, …, N-1] - 你的数组大小 N, f(0)=0, f(x)=A[x-1]+f(x-1) - 一个函数

如果您绘制f,您会看到,您要寻找的是f(m)=f(n), m=n-2k 的点,其中k 为自然。更准确地说,只有x 这样A[x]!=A[x+1](以及数组中的最后一个元素),您必须检查f(x) 是否已经发生。不幸的是,现在我看不到将数组 B[-N+1…N-1] 用于存储此类信息的改进。

完成我的想法:最初是B[x]=-1,当p = min k: f(k)=x时是B[x]=p。算法是(仔细检查,因为我很累):

fx = 0
B = new array[-N+1, …, N-1]
maxlen = 0
B[0]=0
for i=1…N-1 :
    fx = fx + A[i-1]
    if B[fx]==-1 :
        B[fx]=i
    else if ((i==N-1) or (A[i-1]!=A[i])) and (maxlen < i-B[fx]):
        We found that A[B[fx], …, i] is best than what we found so far
        maxlen = i-B[fx]

编辑:两个床头想法(=躺在床上时想出来的:P):

1) 您可以按子数组的长度对结果进行二进制搜索,这将给出 O(n log n) 时间和 O(1) 内存算法。让我们使用函数g(x)=x - x mod 2(因为总和为 0 的子数组的长度总是偶数)。首先检查整个数组的总和是否为 0。如果是 - 我们完成了,否则继续。我们现在假设 0 作为起点(我们知道有这样长度的子数组和“求和为零的属性”)和 g(N-1) 作为终点(我们知道没有这样的子数组)。让我们做吧

    a = 0
    b = g(N-1)
    while a<b : 
        c = g((a+b)/2)
        check if there is such subarray in O(n) time
        if yes:
            a = c
        if no:
            b = c
    return the result: a (length of maximum subarray)

检查具有某个给定长度 L 的“总和为零属性”的子数组很简单:

    a = 0
    b = L
    fa = fb = 0
    for i=0…L-1:
        fb = fb + A[i]
    while (fa != fb) and (b<N) :
        fa = fa + A[a]
        fb = fb + A[b]
        a = a + 1
        b = b + 1
    if b==N:
        not found
    found, starts at a and stops at b

2) …你能修改输入数组吗?如果是并且如果 O(1) 内存完全意味着您使用没有额外的空间(除了恒定数量的元素),那么只需将您的前缀表值存储在您的输入数组中。不再使用空间(除了一些变量):D

再一次,仔细检查我的算法,因为我已经很累了,可能会一次次出错。

【讨论】:

    【解决方案3】:

    和 Neil 一样,我发现考虑字母 {±1} 而不是 {0, 1} 很有用。 假设不失一般性,+1 至少与 -1 一样多。 以下算法使用 O(sqrt(n log n)) 位并运行在时间 O(n) 中,由于“A.F.”

    注意:此解决方案不会作弊,假设输入是可修改的和/或有浪费的位。截至本次编辑,此解决方案是唯一发布的 O(n) 时间和 o(n) 空间的解决方案。

    一个更简单的版本,它使用 O(n) 位,流式传输前缀和数组并标记每个值的第一次出现。然后它向后扫描,考虑 0 和 sum(arr) 之间的每个高度,该高度处的最大子数组。一些想法表明,最优值在其中(记住假设)。在 Python 中:

    sum = 0
    min_so_far = 0
    max_so_far = 0
    is_first = [True] * (1 + len(arr))
    for i, x in enumerate(arr):
        sum += x
        if sum < min_so_far:
            min_so_far = sum
        elif sum > max_so_far:
            max_so_far = sum
        else:
            is_first[1 + i] = False
    
    sum_i = 0
    i = 0
    while sum_i != sum:
        sum_i += arr[i]
        i += 1
    sum_j = sum
    j = len(arr)
    longest = j - i
    for h in xrange(sum - 1, -1, -1):
        while sum_i != h or not is_first[i]:
            i -= 1
            sum_i -= arr[i]
        while sum_j != h:
            j -= 1
            sum_j -= arr[j]
        longest = max(longest, j - i)
    

    降低空间的技巧来自于注意到我们正在按顺序扫描is_first,尽管相对于它的构造顺序相反。由于循环变量适合 O(log n) 位,我们将在每个 O(√(n log n)) 步骤之后计算循环变量的检查点,而不是 is_first。这是 O(n/√(n log n)) = O(√(n/log n)) 个检查点,总共 O(√(n log n)) 个位。通过从检查点重新启动循环,我们按需计算 is_first 的每个 O(√(n log n)) 位部分。

    (P.S.:may or may not be my fault 问题陈述要求 O(1) 空间。我真诚地道歉,如果是我拉了Fermat 并建议我有一个比我想象的更难的问题的解决方案是的。)

    【讨论】:

      【解决方案4】:

      如果您的算法确实在所有情况下都有效(请参阅我对您的问题的评论并指出一些更正),请注意前缀数组是您的常量内存目标的唯一障碍。

      检查find 函数发现这个数组可以替换为两个整数,从而消除了对输入长度的依赖并解决了您的问题。考虑以下几点:

      • 您只依赖于find 函数中前缀数组中的两个值。它们是a[start - 1]a[end]。是的,startend 发生了变化,但这值得阵列吗?
      • 查看循环的进程。最后,start 递增或end 递减仅减一
      • 考虑到前面的语句,如果你用整数替换a[start - 1] 的值,你将如何更新它的值?换句话说,对于循环中改变start 值的每个转换,您可以做些什么来相应地更新整数以反映a[start - 1] 的新值?
      • 这个过程可以用a[end]重复吗?
      • 如果事实上a[start - 1]a[end]的值可以用两个整数来反映,难道整个前缀数组就不再有用了吗?因此不能删除吗?

      由于不需要前缀数组并且移除了对输入长度的所有存储依赖,您的算法将使用恒定数量的内存来实现其目标,从而使其成为 O(n) 时间和 O(1) 空间.

      我希望您根据上述见解自己解决这个问题,因为这是家庭作业。不过,我在下面提供了一个解决方案以供参考:

      #include <iostream>
      using namespace std;
      
      void find( int *data, int &start, int &end )
      {
          // reflects the prefix sum until start - 1
          int sumStart = 0;
      
          // reflects the prefix sum until end
          int sumEnd = 0;
          for( int i = start; i <= end; i++ )
              sumEnd += data[i];
      
          while( start < end )
          {
              int length = end - start + 1;
              int sum = 2 * ( sumEnd - sumStart );
      
              if( sum == length )
                  break;
              else if( sum < length )
              {
                  // sum needs to increase; get rid of the lower endpoint
                  if( data[ start ] == 0 && data[ end ] == 1 )
                  {
                      // sumStart must be updated to reflect the new prefix sum
                      sumStart += data[ start ];
                      start++;
                  }
                  else
                  {
                      // sumEnd must be updated to reflect the new prefix sum
                      sumEnd -= data[ end ];
                      end--;
                  }
              }
              else
              {
                  // sum needs to decrease; get rid of the higher endpoint
                  if( data[ start ] == 1 && data[ end ] == 0 )
                  {
                      // sumStart must be updated to reflect the new prefix sum
                      sumStart += data[ start ];
                      start++;
                  }
                  else
                  {
                      // sumEnd must be updated to reflect the new prefix sum
                      sumEnd -= data[ end ];
                      end--;
                  }
              }
          }
      }
      
      int main() {
          int length;
          cin >> length;
      
          // get the data
          int data[length];
          for( int i = 0; i < length; i++ )
              cin >> data[i];
      
          // solve and print the solution
          int start = 0, end = length - 1;
          find( data, start, end );
      
          if( start == end )
              puts( "No soln" );
          else
              printf( "%d %d\n", start, end );
      
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        这个算法是O(n)时间和O(1)空间。它可能会修改源数组,但会恢复所有信息。所以它不适用于 const 数组。如果这个谜题有多个解,这个算法会选择最接近数组开头的解。或者它可能会被修改以提供所有解决方案。

        算法

        变量:

        • p1 - 子数组开始
        • p2 - 子数组结束
        • d - 子数组中 1 和 0 的差异

          1. 计算d,如果d==0,则停止。如果d&lt;0,则反转数组,找到平衡子数组后将其反转。
          2. d &gt; 0提前p2:如果数组元素为1,则只减少p2d。否则p2 应该传递11*0 形式的子数组,其中* 是一些平衡子数组。为了使回溯成为可能,11*0? 更改为0?*00(其中? 是子数组旁边的值)。然后d 递减。
          3. 存储p1p2
          4. 回溯p2:如果数组元素为1,则增加p2。否则我们找到元素,在步骤 2 中更改。还原更改并传递 11*0 形式的子数组。
          5. 前进p1:如果数组元素为1,则增加p1。否则p1 应该传递0*11 形式的子数组。
          6. 存储 p1p2,如果 p2 - p1 得到改进。
          7. 如果p2 位于数组的末尾,请停止。否则继续第 4 步。

        它是如何工作的

        算法遍历输入数组中平衡子数组的所有可能位置。对于每个子阵列位置,p1p2 尽可能远离彼此,提供本地最长的子阵列。在所有这些子数组中选择具有最大长度的子数组。

        为了确定p1 的下一个最佳位置,它被推进到第一个位置,其中 1 和 0 之间的平衡改变了一个。 (第 5 步)。

        为了确定p2 的下一个最佳位置,它会前进到最后一个位置,其中 1 和 0 之间的平衡改变了一个。为了使它成为可能,步骤 2 检测所有这些位置(从数组的末端开始)并以这样的方式修改数组,以便可以使用线性搜索遍历这些位置。 (第 4 步)。

        在执行步骤 2 时,可能会满足两个可能的条件。简单一:找到值'1'时;指针p2 只是前进到下一个值,不需要特殊处理。但是当找到值'0'时,平衡的方向是错误的,需要通过几个位,直到找到正确的平衡。所有这些位都对算法不感兴趣,停止p2 将给出一个太短的平衡子阵列,或者一个不平衡的子阵列。因此,p2 应该传递11*0 形式的子数组(从右到左,* 表示任何平衡子数组)。没有机会朝另一个方向走同样的路。但是可以临时使用模式11*0 中的一些位来允许回溯。如果我们将第一个“1”更改为“0”,将第二个“1”更改为最右边“0”旁边的值,并清除最右边“0”旁边的值:11*0? -&gt; 0?*00,那么我们就有可能(首先)注意返回的模式,因为它以“0”开头,然后(第二)找到p2 的下一个好位置。

        C++ 代码:

        #include <cstddef>
        #include <bitset>
        
        static const size_t N = 270;
        
        void findLargestBalanced(std::bitset<N>& a, size_t& p1s, size_t& p2s)
        {
            // Step 1
            size_t p1 = 0;
            size_t p2 = N;
            int d = 2 * a.count() - N;
            bool flip = false;
        
            if (d == 0) {
                p1s = 0;
                p2s = N;
                return;
            }
        
            if (d < 0) {
                flip = true;
                d = -d;
                a.flip();
            }
        
            // Step 2
            bool next = true;
            while (d > 0) {
                if (p2 < N) {
                    next = a[p2];
                }
        
                --d;
                --p2;
        
                if (a[p2] == false) {
                    if (p2+1 < N) {
                        a[p2+1] = false;
                    }
        
                    int dd = 2;
                    while (dd > 0) {
                        dd += (a[--p2]? -1: 1);
                    }
        
                    a[p2+1] = next;
                    a[p2] = false;
                }
            }
        
            // Step 3
            p2s = p2;
            p1s = p1;
        
            do {
                // Step 4
                if (a[p2] == false) {
                    a[p2++] = true;
                    bool nextToRestore = a[p2];
                    a[p2++] = true;
        
                    int dd = 2;
                    while (dd > 0 && p2 < N) {
                        dd += (a[p2++]? 1: -1);
                    }
        
                    if (dd == 0) {
                        a[--p2] = nextToRestore;
                    }
                }
                else {
                    ++p2;
                }
        
                // Step 5
                if (a[p1++] == false) {
                    int dd = 2;
                    while (dd > 0) {
                        dd += (a[p1++]? -1: 1);
                    }
                }
        
                // Step 6
                if (p2 - p1 > p2s - p1s) {
                    p2s = p2;
                    p1s = p1;
                }
            } while (p2 < N);
        
            if (flip) {
                a.flip();
            }
        }
        

        【讨论】:

        • 是我做错了什么,还是您的代码在以下示例中失败了? 1100111100001110 1000110100011 应该分别产生 (2, 15) (3, 12)。另外,我可能重读了你的描述十几次,但我无法理解。会在没有这么多算法细节的情况下描述想法,而只是一个主要概念吗?尤其是第2步是混乱的。据我所知,您更改原始数组以创建模式 11*0 或 00*0,它们表示介于 (*) 之间的数组的某些特定属性?看起来不是个好主意。
        • @wf34:很可能您没有考虑到此代码 sn-p 中使用的位集使用从右到左的索引。它还将结果报告为典型的 C++ 半开区间。 just a main concept? - 这篇文章的一部分,名为“它是如何工作的”,正是为了解释一个主要概念。 step 2 is messy - 很抱歉,但如果描述中有任何不清楚的地方,您可以阅读代码。
        【解决方案6】:

        对数组中的所有元素求和,然后 diff = (array.length - sum) 将是 0 和 1 的个数之差。

        1. 如果 diff 等于 array.length/2,则最大子数组 = array。
        2. 如果 diff 小于 array.length/2 则 1 多于 0。
        3. 如果 diff 大于 array.length/2 则 0 多于 1。

        对于情况 2 和 3,初始化两个指针,开始和结束指向数组的开始和结束。如果我们有更多的 1,则根据 array[start] = 1 或 array[end] = 1 向内移动指针(start++ 或 end--),并相应地更新 sum。在每一步检查 sum = (end - start) / 2。如果此条件为真,则 start 和 end 表示最大子数组的边界。

        在这里,我们最终对数组进行了两次遍历,一次计算总和,一次向内移动指针。我们使用的是常量空间,因为我们只需要存储总和和两个索引值。

        如果有人想敲出一些伪代码,欢迎你:)

        【讨论】:

        • 您能否更准确地解释一下如何移动指针?我看不出你如何决定移动哪个指针。也许你可以举一个数组的例子(001000111011000001000 或其他东西)。实际上,你做了一个贪心算法,我不相信它在一般情况下有效。
        • @kgadek 你应该努力减少差异,如果情况(2)然后测试任一侧是否为1,(如果两者都是,选择一侧,说左边)并增加那一侧,如果两边都不是1,则选择一侧(比如左侧)并增加该侧;案例 (3) 的相反行为
        • 这比 Oceanic 和 Mark 建议的要复杂。你可以很容易地找出它不起作用的例子。
        • @Mark : 和 00011111111100 我如何选择右或左?
        • -1。 1)您的解决方案不起作用 2)在您的第一段中,diff 的公式显然是错误的,应该是 diff = 2*sum - length。
        【解决方案7】:

        这是一个看起来像缩放 O(n) 的 actionscript 解决方案。虽然它可能更像 O(n log n)。它肯定只使用 O(1) 内存。

        警告我还没有检查它的完整性。我可能会遗漏一些案例。

        protected function findLongest(array:Array, start:int = 0, end:int = -1):int {
            if (end < start) {
                end = array.length-1;
            }
        
            var startDiff:int = 0;
            var endDiff:int = 0;
            var diff:int = 0;
            var length:int = end-start;
            for (var i:int = 0; i <= length; i++) {
                if (array[i+start] == '1') {
                    startDiff++;
                } else {
                    startDiff--;
                }
        
                if (array[end-i] == '1') {
                    endDiff++;
                } else {
                    endDiff--;
                }
        
                //We can stop when there's no chance of equalizing anymore.
                if (Math.abs(startDiff) > length - i) {
                    diff = endDiff;
                    start = end - i;
                    break;
                } else if (Math.abs(endDiff) > length - i) {
                    diff = startDiff;
                    end = i+start;
                    break;
                }
            }
        
            var bit:String = diff > 0 ? '1': '0';
            var diffAdjustment:int = diff > 0 ? -1: 1;
        
            //Strip off the bad vars off the ends.
            while (diff != 0 && array[start] == bit) {
                start++;
                diff += diffAdjustment;
            }
        
            while(diff != 0 && array[end] == bit) {
                end--;
                diff += diffAdjustment;
            }
        
            //If we have equalized end. Otherwise recurse within the sub-array.
            if (diff == 0)
                return end-start+1;
            else
                return findLongest(array, start, end);      
        
        }
        

        【讨论】:

        • 递归需要栈——如果栈不是 O(1) 大小,那么你就没有 O(1) 空间解决方案。
        【解决方案8】:

        我认为不可能存在具有 O(1) 的算法,以下列方式存在。假设您在每一位上迭代一次。这需要一个需要 O(log n) 空间的计数器。可能有人会争辩说 n 本身是问题实例的一部分,那么您将输入长度为长度为 k 的二进制字符串:k + 2-log k。无论您如何查看它们,您都需要一个额外的变量,如果您需要对该数组的索引,这已经使其非 O(1)。

        通常你没有这个问题,因为你有一个大小为n的问题,输入n个大小为log k的数字,加起来为nlog k。这里长度 log k 的变量只是 O(1)。但是这里我们的 log k 只是 1。所以我们只能引入一个具有恒定长度的帮助变量(我的意思是真正恒定的,无论 n 有多大,它都必须受到限制)。

        这里的一个问题是问题的描述是可见的。在计算机理论中,您必须非常小心您的编码。例如。如果切换到一元编码,则可以使 NP 问题多项式(因为输入大小比 n 元 (n>1) 编码呈指数级大。

        对于 n 的输入只有 2-log n 的大小,必须小心。当你在 O(n) 的情况下说话时——这实际上是一种 O(2^n) 的算法(这不是我们需要讨论的重点——因为人们可以争论 n 本身是描述的一部分还是不是)。

        【讨论】:

        • 虽然我理解你在说什么,但如果你假设机器是跨二分的(就像大多数机器一样),那么你可以假设每个机器字都可以保存 Omega(log n) 位。在这种情况下,您可以使用 O(1) 机器词来控制问题的大小。
        • @templatetypedef:我不确定你想说什么。正确的是,您可以使用允许您在 O(1) 中制定问题的 RAM 模型。但这没什么。当你现在想要 O(1) 空间时,这几乎总是被填满。我想说每个已经提出的算法都有能力,当你输入一个恒定大小的问题时,可以在恒定(即 O(1))空间中计算答案。顺便提一句。如果我考虑一下,输入适合 O(1) 机器词也不太正确 - 例如n = 256 意味着 8 位字,但您不能在一个 8 位字中存储 256 个元素(位)。
        【解决方案9】:

        我有这个算法在 O(n) 时间和 O(1) 空间中运行。

        它使用了简单的“先收缩后扩展”的技巧。代码中的注释。

        public static void longestSubArrayWithSameZerosAndOnes() {
            // You are given an array of 1's and 0's only.
            // Find the longest subarray which contains equal number of 1's and 0's
            int[] A = new int[] {1, 0, 1, 1, 1, 0, 0,0,1};
            int num0 = 0, num1 = 0;
        
            // First, calculate how many 0s and 1s in the array
            for(int i = 0; i < A.length; i++) {
                if(A[i] == 0) {
                    num0++;
                }
                else {
                    num1++;
                }
            }
            if(num0 == 0 || num1 == 0) {
                System.out.println("The length of the sub-array is 0");
                return;
            }
        
            // Second, check the array to find a continuous "block" that has
            // the same number of 0s and 1s, starting from the HEAD and the
            // TAIL of the array, and moving the 2 "pointer" (HEAD and TAIL)
            // towards the CENTER of the array
            int start = 0, end = A.length - 1;
            while(num0 != num1 && start < end) {
                if(num1 > num0) {
                    if(A[start] == 1) {
                        num1--; start++;
                    }
                    else if(A[end] == 1) {
                        num1--; end--;
                    }
                    else {
                        num0--; start++;
                        num0--; end--;
                    }
                }
                else if(num1 < num0) {
                    if(A[start] == 0) {
                        num0--; start++;
                    }
                    else if(A[end] == 0) {
                        num0--; end--;
                    }
                    else {
                        num1--; start++;
                        num1--; end--;
                    }
                }
            }
            if(num0 == 0 || num1 == 0) {
                start = end;
                end++;
            }
        
            // Third, expand the continuous "block" just found at step #2 by
            // moving "HEAD" to head of the array and "TAIL" to the end of
            // the array, while still keeping the "block" balanced(containing
            // the same number of 0s and 1s
            while(0 < start && end < A.length - 1) {
                if(A[start - 1] == 0 && A[end + 1] == 0 || A[start - 1] == 1 && A[end + 1] == 1) {
                    break;
                }
                start--;
                end++;
            }
            System.out.println("The length of the sub-array is " + (end - start + 1) + ", starting from #" + start + " to #" + end);
        

        }

        【讨论】:

          【解决方案10】:

          线性时间,恒定空间。如果我遗漏了任何错误,请告诉我。
          在python3中测试。

          def longestBalancedSubarray(A):
              lo,hi = 0,len(A)-1
              ones = sum(A);zeros = len(A) - ones
              while lo < hi:
                  if ones == zeros: break
                  else:
                      if ones > zeros:
                          if A[lo] == 1: lo+=1; ones-=1
                          elif A[hi] == 1: hi+=1; ones-=1
                          else: lo+=1; zeros -=1
                      else:
                          if A[lo] == 0: lo+=1; zeros-=1
                          elif A[hi] == 0: hi+=1; zeros-=1
                          else: lo+=1; ones -=1
              return(A[lo:hi+1])
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-09-27
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多