web-dev-qa-db-fra.com

Longueur du plus long sous-tableau de somme inférieure ou égale à k

Dans une interview, on m'a posé cette question: étant donné un tableau d'entiers positifs s, trouvez la longueur du sous-tableau le plus long de sorte que la somme de toutes ses valeurs soit inférieure ou égale à un entier positif k. Chaque entrée aura toujours au moins une solution. Le tableau n'est pas circulaire.

J'ai commencé à écrire une solution de programmation dynamique qui cherchait la longueur maximale à des valeurs de plus en plus grandes allant de 0 à k.

Voici mon code en python, il y a une erreur à l'intérieur que je n'arrive pas à trouver, ma réponse est toujours décalée de quelques chiffres:

def maxLength(s, k):
    lengths = [0 for x in range(k)]
    for i in range(1,k+1):
        for j in range(len(s)):
            if s[j] <= i and lengths[i - s[j]] + 1 > lengths[i]:
                lengths[i] = lengths[i - s[j]] + 1
        if i + 1 == len(s):
            break
    return lengths[-1]

Entrée1: s = [1,2,3], k = 4

Sortie1: 2

Entrée2: s=[3,1,2,1], k = 4

Sortie2: 3

15
nonsequiter

Vous pouvez le faire en temps linéaire (O (n)):

def max_length(s, k):
    # These two mark the start and end of the subarray that `current` used to be.
    subarray_start = 0
    subarray_end = 0

    subarray_sum = 0
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.
    for i in s:
        subarray_sum += i
        subarray_end += 1
        while subarray_sum > k: # Shrink the array from the left, until the sum is <= k.
            subarray_sum -= s[subarray_start]
            subarray_start += 1

        # After the previous while loop, subarray_sum is guaranteed to be 
        # smaller than or equal to k.
        max_len = max(max_len, subarray_end - subarray_start)

    return max_len

Il y avait une certaine confusion avec la question initiale, où je pensais que nous recherchions un sous-tableau avec une somme ** égale à (mais pas inférieure à) k * . Ma réponse initiale est ci-dessous. Vous y trouverez également des informations sur la linéarité de cette solution, alors poursuivez votre lecture si cela vous intéresse.

Réponse originale

Voici comment je le ferais:

def max_length(s, k):
    current = []
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.
    for i in s:
        current.append(i)
        while sum(current) > k: # Shrink the array from the left, until the sum is <= k.
           current = current[1:]
        if sum(current) == k:
            max_len = max(max_len, len(current))

    return max_len

Cela exploite le fait que nous recherchons un sous-tableau contigu pour obtenir une solution avec une complexité temporelle linéaire (O (n)). current correspond à notre tentative actuelle de créer un sous-tableau qui ajoute la valeur k. Nous parcourons s et ajoutons tous les éléments de s à current. Si la somme totale de current devient trop importante (supérieure à k), nous supprimons les éléments situés à gauche de current jusqu'à ce que la somme soit inférieure ou égale à k. Si, à un moment quelconque, la somme est égale à k, nous enregistrons la longueur.


Eh bien ... j'ai menti et Francisco Couzo m'a attrapé dans les commentaires. Le code ci-dessus n'est pas vraiment O(n) J'appelle len(current) et sum(current), qui prennent au plus des étapes n, ce qui rend l'algorithme exécuté dans un temps quadratique (O (n ^ 2)). Nous pouvons résoudre ce problème en gardant une trace de la taille et de la somme de current nous-mêmes.

La version ci-dessous nous rapproche de O (n), mais j'ai remarqué un problème lors de l'écriture.

def max_length(s, k):
    current = []
    len_current = 0
    sum_current = 0
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.
    for i in s:
        current.append(i)
        sum_current += i
        len_current += 1
        while sum_current > k: # Shrink the array from the left, until the sum is <= k.
            sum_current -= current[0]
            current = current[1:]
            len_current -= 1
        if sum_current == k:
            max_len = max(max_len, len_current)

    return max_len

Ce morceau de code pourrait ressembler à O (n), et s'il était écrit en Go, ce le serait. Voir ce current = current[1:]? Selon l'article TimeComplexities du wiki Python , prendre une partie d'une liste prend O (n).

Je ne pouvais pas trouver d'opération de liste qui supprime un élément dès le début, jusqu'à ce que je réalise soudain que je n'avais pas à le faire. current serait toujours un sous-tableau contigu de s, alors pourquoi ne pas en marquer le début et la fin?

Alors voici ma solution finale:

def max_length(s, k):
    # These two mark the start and end of the subarray that `current` used to be.
    subarray_start = 0
    subarray_end = 0

    subarray_sum = 0
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.
    for i in s:
        subarray_sum += i
        subarray_end += 1
        while subarray_sum > k: # Shrink the array from the left, until the sum is <= k.
            subarray_sum -= s[subarray_start]
            subarray_start += 1
        if subarray_sum == k:
            max_len = max(max_len, subarray_end - subarray_start)

    return max_len

Si vous considérez que le tableau est circulaire, ce que le premier exemple de cas dans la question semble indiquer, vous pouvez parcourir le tableau deux fois:

def max_length(s, k):
    s = s + s
    # These two mark the start and end of the subarray that `current` used to be.
    subarray_start = 0
    subarray_end = 0

    subarray_sum = 0
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.
    for i in s:
        subarray_sum += i
        subarray_end += 1
        while subarray_sum > k: # Shrink the array from the left, until the sum is <= k.
            subarray_sum -= s[subarray_start]
            subarray_start += 1
        if subarray_sum == k:
            max_len = max(max_len, subarray_end - subarray_start)

    return max_len

Vous pouvez probablement effectuer des vérifications pour sortir plus rapidement de cette deuxième passe, en fonction des valeurs que vous avez rencontrées lors de la première passe.

21
bigblind

Réponse originale

Au départ, la question était de trouver la longueur du plus long sous-réseau pouvant atteindre k.

Vous pouvez parcourir les index de la liste en prenant chaque index comme point de départ d'une fenêtre sur laquelle vous faites la somme. Vous parcourez ensuite les index de votre index de départ à la fin pour marquer la fin de la fenêtre. A chaque étape, vous prenez la somme, ou mieux, ajoutez-la à un terme de somme. Si la somme dépasse la cible, vous sortez de la boucle intérieure et continuez.

Il ressemblera à ceci:

def get_longes(a_list, k):
    longest = 0
    length = len(a_list)
    for i in xrange(length):
        s = 0
        for j in xrange(i,length):
            s+=a_list[j]
            if s < k:
                pass
            Elif s==k:
                longest = j+1-i
            else:
                break
    return longest

Cela peut être encore plus rapide car vous n'avez pas besoin de réinitialiser la taille de la fenêtre lorsque vous déplacez une étape dans la boucle externe. En fait, il vous suffit de suivre la taille de la fenêtre et de la diminuer de 1 si la boucle externe passe. De cette façon, vous pouvez même vous débarrasser de la boucle interne et écrire quelque chose dans O(n) :

def get_longest(a_list,k):
    length=len(a_list)
    l_length = 0
    longest = 0
    s = 0
    for i in xrange(length):
        while s<k:  # while the sum is smaller, we increase the window size
            if i+l_length==length: # this could also be handled with a try, except IndexError on the s+=a_list[... line
                return longest
            s+=a_list[i+l_length]
            l_length+=1
        if s == k:  # if the sum is right, keep its length if it is bigger than the last match.
            longest = max(l_length, longest)
        l_length-=1  # keep the window end at the same position (as we will move forward one step)
        s-=a_list[i]  # and subtract the element that will leave the window
    return longest

Réponse à la question mise à jour

La question mise à jour demande le sous-tableau le plus long pour lequel la somme est égale ou inférieure à k.

Pour cette question, l'approche de base est la même et en réalité, la solution devient encore plus simple, car nous n'avons plus que deux conditions à la somme, à savoir:

1) la somme est inférieure ou égale à k.

2) la somme est supérieure à k.

La solution ressemble à:

def get_longest_smaller_or_equal(a_list,k):
    length=len(a_list)
    l_length = 0
    longest = 0
    s = 0
    for i in xrange(length):
        while s<=k:  # while the sum is smaller, we increase the window size
            longest = max(l_length, longest)
            if i+l_length==length: # this could also be handled with a try, except IndexError on the s+=a_list[... line
                return longest
            s+=a_list[i+l_length]
            l_length+=1
        l_length-=1  # keep the window end at the same position (as we will move forward one step)
        s-=a_list[i]  # and subtract the element that will leave the window
    return longest
7
jojo

Je pense que cela fonctionne ... (récursivement et en retirant l'exigence contiguous de la question, car cela ne semble pas correspondre aux exemples de résultats fournis dans la question) et le PO indique que la question était:

étant donné un tableau d'entiers positifs s, trouvez la longueur du plus long sous-tableau de sorte que la somme de toutes les valeurs soit égale à un entier positif k.

def longest_sum(input_list, index, num_used, target_number):
    if target_number == 0:
        return num_used
    if index >= len(input_list):
        return 0

    # Taken
    used_1 = longest_sum(input_list, index + 1, num_used + 1, target_number - input_list[index])
    # Not taken
    used_2 = longest_sum(input_list, index + 1, num_used, target_number)
    return max(used_1, used_2)


if __== "__main__":
    print(longest_sum([2, 1, 8, 3, 4], 0, 0, 6))
    print(longest_sum([1, 2, 3], 0, 0, 4))
    print(longest_sum([3, 1, 2, 1], 0, 0, 4))
    print(longest_sum([1, 2, 7, 8, 11, 12, 14, 15], 0, 0, 10))
    print(longest_sum([1, 2, 3], 0, 0, 999))
    print(longest_sum([1, 1, 1, 1, 1, 1, 4], 0, 0, 6))

Les sorties:

3
# BorrajaX's note: 2 + 1 + 3
2
# BorrajaX's note: 3 + 1
3
# BorrajaX's note: 1 + 2 + 1
3
# BorrajaX's note: 1 + 2 + 7
0
# BorrajaX's note: No possible sum
6
# BorrajaX's note: 1 + 1 + 1 + 1 + 1 + 1

EDIT 01:

Si vous voulez chercher quelle est la liste qui vous donne la plus longue somme, vous pouvez toujours le faire comme ceci:

import copy


def longest_sum(input_list, used_list, target_number):
    if target_number == 0:
        return used_list

    if not input_list:
        return []

    # Taken
    used_list_taken = copy.copy(used_list)
    used_list_taken.append(input_list[0])
    used_1 = longest_sum(input_list[1:], used_list_taken, target_number - input_list[0])

    # Not taken
    used_list_not_taken = copy.copy(used_list)
    used_2 = longest_sum(input_list[1:], used_list_not_taken, target_number)
    if len(used_1) > len(used_2):
        return used_1
    else:
        return used_2


if __== "__main__":
    print(longest_sum([2, 1, 8, 3, 4], [], 6))
    print(longest_sum([1, 2, 3], [], 4))
    print(longest_sum([3, 1, 2, 1], [], 4))
    print(longest_sum([1, 2, 7, 8, 11, 12, 14, 15], [], 10))
    print(longest_sum([1, 2, 3], [], 999))
    print(longest_sum([1, 1, 1, 1, 1, 1, 4], [], 6))

Vous verriez:

[2, 1, 3]
[1, 3]
[1, 2, 1]
[1, 2, 7]
[]
[1, 1, 1, 1, 1, 1]

(PS 1:} _ Je ne sais vraiment pas comment faire cela sans les capacités de retour arrière rapides fournies par la récursivité ... Désolé _ {:-(

PS 2: Si ce n'est pas ce que vous vouliez (j'ai mentionné que j'avais retiré l'exigence contiguous des exigences), faites-le-moi savoir et je supprimerai cette réponse.

3
BorrajaX

Voici une solution qui fonctionne pour toutes les variables s (même un itérateur) itérable. C'est essentiellement le même algorithme que la réponse de bigblind , mais il sera plus efficace si k est grand par rapport aux valeurs de s (de sorte que les longueurs des sous-séquences pertinentes sont longues):

import itertools

def max_length(s, k):
    head, tail = itertools.tee(s)
    current_length = current_sum = 0
    max_len = -1 # returns -1 if there is no subsequence that adds up to k.

    for i in head:
        current_length += 1
        current_sum += i

        while current_sum > k:
           current_length -= 1
           current_sum -= next(tail)

        if current_sum == k:
            max_len = max(max_len, current_sum)

    return max_len

Comme nous ne conservons pas de liste avec la sous-séquence que nous examinons en itérant, cette approche basée sur les itérateurs est utile uniquement si vous avez uniquement besoin de la longueur de la sous-séquence la plus longue, et non de son contenu réel.

Si vous voulez obtenir une copie de la sous-séquence la plus longue, vous pouvez utiliser une variante différente de la réponse de bigblind, en utilisant un collections.dequeue au lieu d'une liste (pour que le saut à partir de la gauche soit rapide) et en gardant la trace de la somme en cours comme le fait mon code (vous n'avez donc pas besoin d'appeler sum plusieurs fois):

import collections

def max_subsequence(s, k):
    current = collections.dequeue()
    current_sum = 0
    max_len = -1
    max_seq = None # returns None if there is no valid subsequence.

    for i in s:
        current.append(i)
        current_sum += i

        while current_sum > k: # Shrink from the left efficiently!
           current_sum -= current.popleft()

        if current_sum == k:
            if len(current) > max_len:
                max_len = len_current
                max_seq = list(current) # save a copy of the subsequence

    return max_seq

Si le titre de votre question est trompeur et que la sous-séquence soit contiguë ne vous intéresse pas, alors je pense que votre approche de programmation dynamique actuelle peut faire ce que vous voulez. Je ne suis pas tout à fait sûr de comprendre comment vos boucles ont été conçues pour fonctionner. Je pense que c'est très naturel avec une boucle externe sur les éléments d'entrée, et une seconde boucle sur les sommes potentielles qui incluent cette valeur (qui sont des index dans la liste lengths). Je suggérerais également d'utiliser None comme valeur initiale pour toutes les longueurs autres que 0, car sinon il est difficile de faire en sorte que les conditions fonctionnent correctement sans cas particulier.

def max_length(s, k):
    lengths = [None for _ in range(k+1)]
    lengths[0] = 0

    for x in s:
        for i in range(k, x-1, -1): # count down to avoid duplication
            if lengths[i-x] is not None and (lengths[i] is None or
                                             lengths[i-x] >= lengths[i]):
                lengths[i] = lengths[i-x] + 1

    return lengths[k]
2
Blckknght

Un peu plus court et en supposant des s non circulaires: faites glisser des fenêtres de taille décroissante sur s.

def maxlen(s, k):
    for win in range(k, 0, -1):
        for i in range(0, len(s) - win):
            if sum(s[i:i+win]) == k:
                return win
    return None

s = [3,1,2,3]
k = 4
print(maxlen(s, k))
1
hvwaldow

Approches à ce problème 

O(n) - Approche à deux pointeurs

Initialise le premier élément en s et e et subArray_sum = arr [0]

Maintenant si subArray_sum <k incrémente e alors que subArray_sum <= k 

Une fois que subArray_sum devient> = k incrémente s jusqu'à devenir <= k

O (nlog n) - Recherche binaire

Considérez toutes les longueurs possibles de sous-matrices i. (1 <= i <= n) .Parmi tous les sous-matrices de longueur i, trouvez celle avec la somme minimale. Maintenant, pour tout sous-tableau de longueur i si le sous-tableau de longueur i mais avec la somme minimale a la somme <= k, on peut trouver i longueur cette longueur avec la somme du sous-tableau <= k . Effectuez une recherche binaire sur la plage de i avec start = 1 et end = n;

O (n * n) - Force brute

Considérez tous les sous-matrices possibles (n * n en nombre) et trouvez le plus long avec sum <= k

Variante du problème ci-dessus

Longueur du sous-réseau le plus long avec une moyenne inférieure ou égale à k

Toutes les approches ci-dessus sont applicables ici aussi

1
Vinayak Sangar

Cet article peut vous aider beaucoup.

https://e2718281828459045.wordpress.com/2013/08/19/longest-subarray-whose-sum-k/

Il peut être résolu en utilisant
Tableau de somme + Recherche binaire.

La première observation que vous obtenez est que si nous considérons que jeth élément alors nous devons continuer avec (i + 1)thetc. C'est-à-dire que nous devons ajouter tous les éléments d'une commande jusqu'au dernier élément ou nous atteignons la somme. Donc, l'ordre est important.

Comment pouvons-nous ajouter les chiffres. Il y a n manières présentes. La première façon est que nous pouvons commencer avec le premier élément et ajouter jusqu'à atteindre k ou atteindre le dernier élément. La deuxième façon est que nous pouvons commencer avec le deuxième élément et lire jusqu'à atteindre k ou atteindre le dernier élément.

Donc, un algorithme de force brute vous donnera un O (n2)Solution. Comment pouvons-nous améliorer cela? Ce n'est évidemment pas la solution attendue. Pour chaque i

nous calculons la somme des éléments et vérifions que le total dépasse la valeur donnée de 'k' ou non. Pour éviter cela, nous pouvons créer un tableau de somme.

N'oubliez pas que chaque fois que vous rencontrez un problème avec la somme de séquence (ou la somme d'éléments continus dans un tableau donné), cela peut probablement être résolu à l'aide de la technique de somme. Sum array est un tableau nouvellement construit utilisant le tableau donné. Il peut être généré en utilisant la formule suivante,

sum[i] = sum[i−1] + array[i]

pour tout je> 0. et

sum[i]=array[i]

pour i = 0.

Somme tableau peut être créé dans O(n) temps. Trouver la somme entre ith et jth devient facile. C'est la différence,

sum[j]−sum[i], j>i

va vous donner la réponse. Mais ça reste O (n2) Solution. 
Le problème est que pour chaque valeur de i nous prenons O(n) le temps de trouver j
Alors comment pouvons-nous réduire cela?
Bingo! Voici la recherche binaire. En utilisant la recherche binaire modifiée sur l'intervalle i et n pour chaque i, nous pouvons trouver la j dans O(logn) heure. Donc, cela prend seulement O(nlogn) temps. Nous avons besoin d'une variable supplémentaire et d'une condition pour stocker la longueur du sous-tableau, c'est-à-dire j−i.

J'espère que cela t'aides.

0
nikoo28