如何在O(n)时间内找到最小正连续子序列?

时间:2015-07-26 19:12:17

标签: algorithm dynamic-programming

我们有this算法用于在O(n)时间内找到给定序列中的最大正子序列。任何人都可以提出类似的算法来找到最小的正连续子序列。

例如   如果给定的序列是1,2,3,4,5,答案应该是1。   [5,-4,3,5,4] - > 1是元素[5,-4]的最小正和。

3 个答案:

答案 0 :(得分:4)

不可能有这样的算法。该问题的下限是O(n log n)。我会通过减少它element distinctness problem来证明它(实际上是对它的非负变量)。

假设我们有一个针对此问题的O(n)算法(最小的非负数子阵列)。

我们想知道一个数组(例如A = [1,2,-3,4,2])是否只有不同的元素。为了解决这个问题,我可以构造一个具有连续元素之间差异的数组(例如A' = [1,-5,7,-2])并运行我们的O(n)算法。当且仅当最小非负子数组大于0时,原始数组才具有不同的元素。

如果我们对你的问题有一个O(n)算法,我们就会有一个O(n)算法来解决元素清晰度问题,我们知道这在图灵机上是不可能的。

答案 1 :(得分:2)

我们可以有一个O(n log n)算法如下:

假设我们有一个数组prefix,哪个索引i存储数组A的总和从0到i,所以子数组的总和(i ,j)是prefix[j] - prefix[i - 1]

因此,为了找到以索引j结尾的最小正子数组,我们需要找到最小元素prefix[x],其小于prefix[j]x < j。如果我们使用二叉搜索树,我们可以在O(log n)时间内找到该元素。

伪代码:

int[]prefix = new int[A.length];
prefix[0] = A[0];
for(int i = 1; i < A.length; i++)
    prefix[i] = A[i] + prefix[i - 1];
int result = MAX_VALUE;
BinarySearchTree tree;
for(int i = 0; i < A.length; i++){
    if(A[i] > 0)
       result = min(result, A[i];
    int v = tree.getMaximumElementLessThan(prefix[i]);
    result = min(result, prefix[i] - v);
    tree.add(prefix[i]);
}

答案 2 :(得分:1)

我相信有O(n)算法,见下文。

注意:它有一个比例因子,可能会降低它在实际应用中的吸引力:它取决于要处理的(输入)值,请参阅代码中的备注。

private int GetMinimumPositiveContiguousSubsequenc(List<Int32> values)
    {
      // Note: this method has no precautions against integer over/underflow, which may occur
      // if large (abs) values are present in the input-list.

      // There must be at least 1 item.
      if (values == null || values.Count == 0)
        throw new ArgumentException("There must be at least one item provided to this method.");

      // 1. Scan once to:
      //    a) Get the mimumum positive element;
      //    b) Get the value of the MAX contiguous sequence
      //    c) Get the value of the MIN contiguous sequence - allowing negative values: the mirror of the MAX contiguous sequence.
      //    d) Pinpoint the (index of the) first negative value.

      int minPositive = 0;

      int maxSequence = 0;
      int currentMaxSequence = 0;

      int minSequence = 0;
      int currentMinSequence = 0;

      int indxFirstNegative = -1;

      for (int k = 0; k < values.Count; k++)
      {
        int value = values[k];

        if (value > 0)
          if (minPositive == 0 || value < minPositive)
            minPositive = value;
          else if (indxFirstNegative == -1 && value < 0)
            indxFirstNegative = k;

        currentMaxSequence += value;
        if (currentMaxSequence <= 0)
          currentMaxSequence = 0;
        else if (currentMaxSequence > maxSequence)
          maxSequence = currentMaxSequence;

        currentMinSequence += value;
        if (currentMinSequence >= 0)
          currentMinSequence = 0;
        else if (currentMinSequence < minSequence)
          minSequence = currentMinSequence;
      }

      // 2. We're done if (a) there are no negatives, or (b) the minPositive (single) value is 1 (or 0...).
      if (minSequence == 0 || minPositive <= 1)
        return minPositive;

      // 3. Real work to do.
      // The strategy is as follows, iterating over the input values:
      // a) Keep track of the cumulative value of ALL items - the sequence that starts with the very first item.
      // b) Register each such cumulative value as "existing" in a bool array 'initialSequence' as we go along.
      //    We know already the max/min contiguous sequence values, so we can properly size that array in advance.
      //    Since negative sequence values occur we'll have an offset to match the index in that bool array
      //    with the corresponding value of the initial sequence.
      // c) For each next input value to process scan the "initialSequence" bool array to see whether relevant entries are TRUE.
      //    We don't need to go over the complete array, as we're only interested in entries that would produce a subsequence with
      //    a value that is positive and also smaller than best-so-far.
      //    (As we go along, the range to check will normally shrink as we get better and better results.
      //     Also: initially the range is already limited by the single-minimum-positive value that we have found.)

      // Performance-wise this approach (which is O(n)) is suitable IFF the number of input values is large (or at least: not small) relative to
      // the spread between maxSequence and minSeqence: the latter two define the size of the array in which we will do (partial) linear traversals.
      // If this condition is not met it may be more efficient to replace the bool array by a (binary) search tree.
      // (which will result in O(n logn) performance).
      // Since we know the relevant parameters at this point, we may below have the two strategies both implemented and decide run-time
      // which to choose.
      // The current implementation has only the fixed bool array approach.

      // Initialize a variable to keep track of the best result 'so far'; it will also be the return value.
      int minPositiveSequence = minPositive;

      // The bool array to keep track of which (total) cumulative values (always with the sequence starting at element #0) have occurred so far,
      // and the 'offset' - see remark 3b above.
      int offset = -minSequence;
      bool[] initialSequence = new bool[maxSequence + offset + 1];

      int valueCumulative = 0;

      for (int k = 0; k < indxFirstNegative; k++)
      {
        int value = values[k];
        valueCumulative += value;
        initialSequence[offset + valueCumulative] = true;
      }

      for (int k = indxFirstNegative; k < values.Count; k++)
      {
        int value = values[k];

        valueCumulative += value;
        initialSequence[offset + valueCumulative] = true;

        // Check whether the difference with any previous "cumulative" may improve the optimum-so-far.

        // the index that, if the entry is TRUE, would yield the best possible result.
        int indexHigh = valueCumulative + offset - 1;
        // the last (lowest) index that, if the entry is TRUE, would still yield an improvement over what we have so far.
        int indexLow = Math.Max(0, valueCumulative + offset - minPositiveSequence + 1);

        for (int indx = indexHigh; indx >= indexLow; indx--)
        {
          if (initialSequence[indx])
          {
            minPositiveSequence = valueCumulative - indx + offset;

            if (minPositiveSequence == 1)
              return minPositiveSequence;

            break;
          }
        }
      }

      return minPositiveSequence;
    }
  }