最长的正和子串

时间:2015-02-06 00:11:55

标签: algorithm

我想知道如何在序列中得到最长的正和子序列:

例如我有-6 3 -4 4 -5,所以最长的正子序列是3 -4 4.实际上总和为正(3),我们不能加-6而不是-5或者它会变成负面的。

它可以很容易地在O(N ^ 2)中解决,我认为可以存在更快的东西,比如O(NlogN)

你有什么想法吗?

编辑:必须保留订单,您可以跳过子字符串中的任何数字

EDIT2:如果我使用术语" sebsequence"引起混淆,我很抱歉,因为@beaker指出我的意思是子串

3 个答案:

答案 0 :(得分:3)

O(n)空间和时间解决方案,将从代码开始(抱歉,Java ;-)并尝试稍后解释:

  public static int[] longestSubarray(int[] inp) {
    // array containing prefix sums up to a certain index i
    int[] p = new int[inp.length];
    p[0] = inp[0];
    for (int i = 1; i < inp.length; i++) {
      p[i] = p[i - 1] + inp[i];
    }

    // array Q from the description below
    int[] q = new int[inp.length];
    q[inp.length - 1] = p[inp.length - 1];
    for (int i = inp.length - 2; i >= 0; i--) {
      q[i] = Math.max(q[i + 1], p[i]);
    }

    int a = 0;
    int b = 0;
    int maxLen = 0;
    int curr;
    int[] res = new int[] {-1,-1};
    while (b < inp.length) {
      curr = a > 0 ? q[b] - p[a-1] : q[b];
      if (curr >= 0) {
        if(b-a > maxLen) {
          maxLen = b-a;
          res = new int[] {a,b};
        }
        b++;
      } else {
        a++;
      }
    }
    return res;
  }
  • 我们正在对大小为A
  • 的输入数组n进行操作
  • 让我们将数组P定义为包含前缀sum的数组,直到索引为i所以P[i] = sum(0,i)其中`i = 0,1,...,n-1& #39;
  • 请注意,如果u < vP[u] <= P[v]u永远不会成为我们的终点
  • 因为上面我们可以定义一个QQ[n-1] = P[n-1]
  • 的数组Q[i] = max(P[i], Q[i+1])
  • 现在让我们考虑M_{a,b},它会向我们显示从a开始到b或更高结束的最大总和子阵列。我们知道M_{0,b} = Q[b]M_{a,b} = Q[b] - P[a-1]
  • 根据上述信息,我们现在可以初始化a, b = 0并开始移动它们。如果M的当前值大于或等于0,那么我们知道我们将找到(或已经找到)一个sum> = 0的子阵列,然后我们只需要将b-a与之前的比较发现长度。否则,没有从a开始并且遵守我们的约束的子数组,因此我们需要增加a

答案 1 :(得分:1)

让我们做一个天真的实现,然后改进它。

我们从左向右移动计算部分和,并且对于每个位置,我们发现最左边的部分和,例如当前部分和大于此。

input a
int partialSums[len(a)]
for i in range(len(a)):
    partialSums[i] = (i == 0 ? 0 : partialSums[i - 1]) + a[i]
    if partialSums[i] > 0:
        answer = max(answer, i + 1)
    else:
        for j in range(i):
            if partialSums[i] - partialSums[j] > 0:
                answer = max(answer, i - j)
                break

这是O(n 2 )。现在找到最左边的&#34;好&#34; sum可以通过BST实际维护,其中每个节点将表示为一对(partial sum, index),并通过partial sum进行比较。此外,每个节点都应支持一个特殊字段min,该字段将是此子树中indices的最小值。

现在,我们可以使用当前的部分和作为后续三个规则的密钥来降低BST,而不是直接搜索适当的部分和(假设C是当前节点,LR分别是左右子树的根源:

  1. 维持当前的最低指数&#34; good&#34; curMin中的部分总和,最初为+∞
  2. 如果C.partial_sum是&#34;好&#34;然后使用curMin更新C.index
  3. 如果我们转到R,请使用curMin更新L.min
  4. 然后使用answer更新i - curMin,同时将当前部分金额添加到BST。

    那会给我们O(n * log n)。

答案 2 :(得分:0)

我们可以轻松获得最长子序列的O(n log n)解决方案

  • 首先,对数组进行排序,记住它们的索引。

  • 选择所有最大的数字,当它们的总和为负时停止,并且你有答案。

  • 恢复原始订单。

伪代码

  sort(data);
  int length = 0;
  long sum = 0;
  boolean[] result = new boolean[n];
  for(int i = n ; i >= 1; i--){
      if(sum + data[i] <= 0)
         break;
      sum += data[i];
      result[data[i].index] = true;
      length++;
  }
  for(int i = 1; i <= n; i++)
      if(result[i])
        print i;

因此,我将提出一个O(n log n)解决方案,用于最长的正子串,而不是等待。

  • 首先,我们创建一个数组prefix,它是数组的前缀和。

  • 其次,我们使用二元搜索来寻找具有正和的最长长度

伪代码

  int[]prefix = new int[n];
  for(int i = 1; i <= n; i++)
      prefix[i] = data[i];
      if(i - 1 >= 1)
        prefix[i] += prefix[i - 1]; 
  int min = 0;
  int max = n;
  int result = 0;
  while(min <= max){
       int mid = (min + max)/2;
       boolean ok = false;
       for(int i = 1; i <= n; i++){
           if(i > mid && pre[i] - pre[i - mid] > 0){//How we can find sum of segment with mid length, and end at index i
                ok = true;
                break;
           }
      }
      if(ok){
         result = max(result, mid)
         min = mid + 1;
      }else{
         max = mid - 1;
      }
  }

好的,所以上面的算法是错误的,正如 piotrekg2 所指出的,我们需要做的是

  • 创建一个数组prefix,它是数组的前缀和。

  • prefix数组进行排序,我们需要记住前缀数组的索引。

  • 迭代前缀数组,存储到目前为止我们遇到的最小索引,索引之间的最大差异就是答案。

注意:当我们比较prefix中的值时,如果两个索引的等值,那么较小的索引将会被认为是更大,这将避免总和为0时的情况。

伪代码:

  class Node{
      int val, index;
  }


  Node[]prefix = new Node[n];
  for(int i = 1; i <= n; i++)
      prefix[i] = new Node(data[i],i);
      if(i - 1 >= 1)
        prefix[i].val += prefix[i - 1].val; 

  sort(prefix);
  int min = prefix[1].index;
  int result = 0;
  for(int i = 2; i <= n; i ++)
      if(prefix[i].index > min) 
         result = max(prefix[i].index - min + 1, result)
      min = min(min, prefix[i].index);