web-dev-qa-db-ja.com

Googleインタビュー:指定された整数の配列内で、合計が指定された範囲内にあるすべての連続したサブシーケンスを検索します。 O(n ^ 2)よりもうまくできますか?

整数の配列と範囲(低、高)を指定すると、範囲内の合計を持つ配列内のすべての連続したサブシーケンスを見つけます。

O(n ^ 2)よりも良い解決策はありますか?

私は何度も試しましたが、O(n ^ 2)よりも優れたソリューションを見つけることができませんでした。より良い解決策を見つけるのを手伝うか、これが私たちができる最善の方法であることを確認してください。

これは私が今持っているものです、私は範囲が[lo, hi]

public static int numOfCombinations(final int[] data, final int lo, final int hi, int beg, int end) {
    int count = 0, sum = data[beg];

    while (beg < data.length && end < data.length) {
       if (sum > hi) {
          break;
       } else {
          if (lo <= sum && sum <= hi) {
            System.out.println("Range found: [" + beg + ", " + end + "]");
            ++count;
          }
          ++end;
          if (end < data.length) {
             sum += data[end];
          }
       }
    }
    return count;
}

public static int numOfCombinations(final int[] data, final int lo, final int hi) {
    int count = 0;

    for (int i = 0; i < data.length; ++i) {
        count += numOfCombinations(data, lo, hi, i, i);
    }

    return count;
}
22
user1071840

O(n)時間解:

問題の「正確な」バージョンの「2ポインター」のアイデアを拡張できます。変数abを維持して、フォーム_xs[i,a), xs[i,a+1), ..., xs[i,b-1)_のすべての区間の合計が求められている範囲_[lo, hi]_になるようにします。

_a, b = 0, 0
for i in range(n):
    while a != (n+1) and sum(xs[i:a]) < lo:
        a += 1
    while b != (n+1) and sum(xs[i:b]) <= hi:
        b += 1
    for j in range(a, b):
        print(xs[i:j])
_

これは実際にはsumのためにO(n^2)ですが、最初にps[i] = sum(xs[:i])になるようにプレフィックスの合計psを計算することで簡単に修正できます。 sum(xs[i:j])は、単に_ps[j]-ps[i]_です。

以下は、上記のコードを_[2, 5, 1, 1, 2, 2, 3, 4, 8, 2]_で_[lo, hi] = [3, 6]_とともに実行する例です。

_[5]
[5, 1]
[1, 1, 2]
[1, 1, 2, 2]
[1, 2]
[1, 2, 2]
[2, 2]
[2, 3]
[3]
[4]
_

これは時間内に実行されますO(n + t)、ここでtは出力のサイズです。一部の人が気づいたように、出力は_t = n^2_と同じ大きさになる可能性があります。つまり、すべての連続したサブシーケンスが一致した場合です。

出力を圧縮形式(すべてのサブシーケンスが連続する出力ペア_a,b_)で書き込むことができる場合、純粋なO(n)時間アルゴリズムを取得できます。

16
Thomas Ahle

これから開始します 問題 :xに合計するすべての連続したサブシーケンスを見つけます。必要なのは似たようなものです。

すべてのインデックスiについて、0からiまでのセグメントの合計xを計算できます。したがって、今の問題は、(x-low)から(x-high)までの合計を持つセグメントの数を0からi-1まで見つける必要があり、O(n)よりも高速であることです。そのため、O(logn)でそれを行うのに役立ついくつかのデータ構造があります。これらは Fenwick tree および Interval tree です。

だから私たちがする必要があるのは:

  • 0からnまでのすべてのインデックスを反復処理します(nは配列のサイズです)。

  • インデックスithで、0からi番目のインデックスの合計xを計算し、ツリーにクエリを実行して、範囲内の数値(x-高、x-低)の合計オカレンスを取得します。

  • ツリーにxを追加します。

したがって、時間の複雑さはO(n log n)になります

8
Pham Trung

単純な動的プログラミングとバイナリ検索を使用する必要があります。カウントを見つけるには:

    from bisect import bisect_left, bisect_right

    def solve(A, start, end):
        """
        O(n lg n) Binary Search
        Bound:
        f[i] - f[j] = start
        f[i] - f[j'] = end
        start < end
        f[j] > f[j']

        :param A: an integer array
        :param start: lower bound
        :param end: upper bound 
        :return:
        """
        n = len(A)
        cnt = 0
        f = [0 for _ in xrange(n+1)]

        for i in xrange(1, n+1):
            f[i] = f[i-1]+A[i-1]  # sum from left

        f.sort()
        for i in xrange(n+1):
            lo = bisect_left(f, f[i]-end, 0, i)
            hi = bisect_right(f, f[i]-start, 0, i)
            cnt += hi-lo

        return cnt

https://github.com/algorhythms/LintCode/blob/master/Subarray%20Sum%20II.py

カウントではなく結果を見つけるには、元の(ソートされていない)f [i]->インデックスのリストからのマッピングを保存する別のハッシュテーブルが必要です。

乾杯。

5
Daniel

単純なデータ構造を持つO(NlogN)で十分です。

連続するサブシーケンスの場合、サブアレイを意味すると思います。

接頭辞合計リストprefix[i] = sum for the first i elementsを維持します。 [low, high]の間に範囲ラムがあるかどうかを確認する方法は? バイナリ検索を使用できます。そう、

prefix[0] = array[0]  
for i in range(1, N) 
  prefix[i] = array[i] + prefix[i-1];
  idx1 = binarySearch(prefix, prefix[i] - low);
  if (idx1 < 0) idx1 = -1 - idx1;
  idx2 = binarySearch(prefix, prefix[i] - high);
  if (idx2 < 0) idx2 = -1 - idx2;
  // for any k between [idx1, idx2], range [k, i] is within range [low, high]
  insert(prefix, prefix[i])

注意する必要があるのは、挿入新しい値も必要です。したがって、配列またはリンクリストは[〜#〜] not [〜#〜] OKです。 TreeSetを使用するか、独自の[〜#〜] avl [〜#〜]ツリーを実装できます。バイナリ検索と挿入の両方がO(logN)になります。

0
Harry

すべての整数が負でない場合、O(max(size-of-input,size-of-output))時間で実行できます。これが最適です。

Cのアルゴリズムを次に示します。

_void interview_question (int* a, int N, int lo, int hi)
{
  int sum_bottom_low = 0, sum_bottom_high = 0,
      bottom_low = 0, bottom_high = 0,
      top = 0;
  int i;

  if (lo == 0) printf ("[0 0) ");
  while (top < N)
  {
    sum_bottom_low += a[top];
    sum_bottom_high += a[top];
    top++;
    while (sum_bottom_high >= lo && bottom_high <= top)
    {
      sum_bottom_high -= a[bottom_high++];
    }
    while (sum_bottom_low > hi && bottom_low <= bottom_high)
    {
      sum_bottom_low -= a[bottom_low++];
    }
    // print output
    for (i = bottom_low; i < bottom_high; ++i)
      printf ("[%d %d) ", i, top);
  }
  printf("\n");
}
_

「印刷出力」とマークされた最後のループを除き、各操作はO(N)回実行されます。最後のループは、印刷された間隔ごとに1回実行されます。それらを印刷しないと、アルゴリズム全体がO(N)になります。

負の数が許可されている場合、O(N^2)は打ちにくいです(不可能な場合があります)。

0
n.m.
yes in my opinion it can be in O(n)

struct subsequence
{
int first,last,sum;
}s;

function(array,low,high)
{
int till_max=0;
s.first=0;s.last=0;s.sum=0;
for(i=low;i<high;i++)
{

if(till_max+array[i]>array[i])
{
s.first=s.first;
s.last=i;
till_max+=array[i];
}
else
{
s.first=i;
s.last=i;
till_max=array[i];
}
if(till_max in range)
{
s.sum=till_max;
   printf("print values between first=%d and last=%d and sum=%d",s.first,s.last,s.sum);
}
}
}
0
nancy goel

正数のみがある場合にO(nlogn)を取得する方法は次のとおりです。

1. Evaluate cumulative sum of array
2. for i  find total sum[j] in (sum[i]+low,sum[i]+high) using binary search
3. Total = Total + count
4. do 3 to 5 for all i

時間の複雑さ:-

Cumulative sum is O(N)
Finding sums in range is O(logN) using binary search
Total Time complexity is O(NlogN)
0
Vikram Bhat