web-dev-qa-db-fra.com

Interview Google: Trouvez toutes les sous-séquences contiguës dans un tableau d'entiers donné, dont la somme tombe dans la plage donnée. Peut-on faire mieux que O (n ^ 2)?

Étant donné un tableau d'entiers et une plage (bas, haut), trouvez tous sous-séquence contiguë dans le tableau dont la somme est comprise dans la plage.

Existe-t-il une solution meilleure que O (n ^ 2)?

J'ai beaucoup essayé, mais je n'ai pas trouvé de solution plus efficace que O (n ^ 2). Aidez-moi à trouver une meilleure solution ou confirmez que c'est le mieux que nous puissions faire. 

C’est ce que j’ai pour l’instant, je suppose que la plage doit être définie comme [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;
}
21
user1071840

O(n) solution temporelle:

Vous pouvez étendre l'idée de "deux pointeurs" pour la version "exacte" du problème. Nous allons maintenir les variables a et b telles que tous les intervalles du formulaire xs[i,a), xs[i,a+1), ..., xs[i,b-1) aient une somme comprise dans la plage recherchée [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])

Il s’agit en fait de O(n^2) en raison de sum, mais nous pouvons facilement résoudre ce problème en calculant d’abord le préfixe sums ps tel que ps[i] = sum(xs[:i]). Alors sum(xs[i:j]) est tout simplement ps[j]-ps[i].

Voici un exemple d'exécution du code ci-dessus sur [2, 5, 1, 1, 2, 2, 3, 4, 8, 2] avec [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]

Cela s'exécute dans le temps O(n + t), où t est la taille de la sortie. Comme certains l'ont remarqué, la sortie peut atteindre t = n^2, à savoir si toutes les sous-séquences contiguës correspondent.

Si nous autorisons l'écriture de la sortie dans un format compressé (les paires de sortie a,b dont toutes les sous-séquences sont contiguës), nous pouvons obtenir un algorithme pur de O(n)-time.

16
Thomas Ahle

À partir de ce problème : trouve toutes les sous-séquences contiguës dont la somme est x. Nous avons besoin de quelque chose de similaire.

Pour chaque indice i, nous pouvons calculer la somme du segment de 0 à i, qui est x. Donc, le problème est que nous devons maintenant trouver de 0 à i - 1, combien de segments ont une somme comprise entre (x-bas) et (x-haut), et cela devrait être plus rapide que O (n). Il existe donc plusieurs structures de données qui vous aident à le faire dans O (logn), qui sont Fenwick tree et Interval tree .

Nous devons donc:

  • En parcourant tous les index de 0 à n (n est la taille du tableau).

  • À index ith, calculez, en partant de 0 à index, la somme x, interrogez l’arbre pour obtenir le nombre total d’occurrences de nombres compris dans la plage (x - haut, x - bas).

  • Ajoutez x à l'arbre.

Donc, la complexité temporelle sera O (n log n)

8
Pham Trung

Vous devez utiliser une programmation dynamique simple et une recherche binaire. Pour trouver le compte:

    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

Pour trouver les résultats plutôt que le nombre, vous avez simplement besoin d'une autre table de hachage pour stocker le mappage à partir de l'original (non trié) f [i] -> liste des index.

À votre santé.

5
Daniel

O(NlogN) avec des structures de données simples est suffisant.

Pour les sous-séquences contiguës, je pense que cela signifie pour les sous-tableaux.

Nous maintenons une liste de somme de préfixes, prefix[i] = sum for the first i elements. Comment vérifier s'il existe une plage de rhum entre [low, high]? Nous pouvons utiliser recherche binaire. Alors,

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])

La seule chose dont nous devons nous soucier est que nous devons également insérer de nouvelles valeurs. Ainsi, tout tableau ou toute liste liée est ET NON d'accord. Nous pouvons utiliser un TreeSet, ou implémenter vos propres arbres AVL, la recherche binaire et l’insertion se font en O (logN).

0
Harry

Si tous les nombres entiers sont non négatifs, vous pouvez le faire en O(max(size-of-input,size-of-output)) time. C'est optimal.

Voici l'algorithme en 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");
}

À l'exception de la dernière boucle marquée "sortie d'impression", chaque opération est exécutée O(N) fois; la dernière boucle est exécutée une fois pour chaque intervalle imprimé. Si nous devons seulement compter les intervalles et ne pas les imprimer, tout l'algorithme devient O(N).

Si les nombres négatifs sont autorisés, alors O(N^2) est difficile à battre (peut-être impossible).

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

Voici comment vous pouvez obtenir O(nlogn) s'il n'y a que des nombres positifs: -

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

Complexité temporelle: -

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