web-dev-qa-db-ja.com

k以下の合計の最も長いサブ配列の長さ

インタビューで私はこの質問をされました:正の整数sの配列が与えられた場合、すべての値の合計がいくつかの正の整数k以下になるように、最も長いサブ配列の長さを見つけます。各入力には常に少なくとも1つのソリューションがあります。配列は円形ではありません。

私は、0からkへと徐々に大きくなる値で最大長を見つけることで機能する動的プログラミングソリューションの作成を始めました。

これが私のpythonコードです。コード内に見つけられなかったエラーがあり、私の答えは常に数桁ずれています:

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]

入力1:s = [1,2,3], k = 4

出力1:2

入力2:s=[3,1,2,1], k = 4

出力2:3

16
nonsequiter

これは線形(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
_

元の質問といくつかの混乱がありました。そこでは、合計がkに等しい(ただしk以上)*のサブ配列を探していると思いました。私の元の答えは以下です。このソリューションの線形性に関する情報もそこにありますので、興味があれば読んでください。

元の回答

ここに私がそれをする方法があります:

_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
_

これは、連続するサブアレイを探しているという事実を利用して、線形(O(n))時間の複雑さを持つソリューションを取得します。 currentは、合計kになるサブ配列を作成する現在の試みです。 sをループして、sからcurrentまでのすべての要素を追加します。 currentの合計が大きすぎる(kより大きい)場合は、合計がcurrent以下になるまでkの左側から要素を削除します。いずれかの時点で、合計がkと等しい場合は、長さを記録します。


うーん...私は嘘をついた、そしてフランシスコ・クーゾはコメントで私を捕まえた。上記のコードは実際にはありませんO(n)私はlen(current)およびsum(current)を呼び出しています。これらは最大nステップを実行し、アルゴリズムを実行します二次時間(O(n ^ 2))。current自体のサイズと合計を追跡することでこれを修正できます。

以下のバージョンではO(n)に近づいていますが、作成中に問題が発生しました。

_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
_

このコードはO(n)のように見える可能性があり、Goで記述した場合はそうなります。 _current = current[1:]_を参照してください。 Python wiki のTimeComplexities記事によると、リストからスライスを取得するとO(n)が取得されます。

最初から要素を削除するリスト操作は、必要がないことに突然気づくまで見つかりませんでした。 currentは常にsの連続したサブ配列になるので、その開始と終了をマークしないでください。

だからここに私の最終的な解決策があります:

_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
_

配列が循環的であると考える場合、これは質問の最初の例のケースが示すように思われますが、配列を2回通過することができます。

_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
_

最初のパスで遭遇した値に基づいて、その2番目のパスをより早く抜け出すために実行できるチェックがおそらくあります。

22
bigblind

元の回答

最初の問題は、合計するとkになる最長のサブ配列の長さを見つけることでした。

リストインデックスを実行し、各インデックスを合計するウィンドウの開始点として使用できます。次に、開始インデックスから最後までインデックスを実行して、ウィンドウの終わりを示します。各ステップで、合計、またはさらに良い場合は、合計項に追加します。合計が目標を超える場合は、内側のループから抜け出し、次に進みます。

次のようになります。

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

これは、外側のループで1ステップ移動するときにウィンドウサイズをリセットする必要がないため、さらに高速化できます。実際、ウィンドウサイズを追跡し、外側のループが移動した場合はウィンドウサイズを1だけ減らす必要があります。このようにして、内部ループを取り除き、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

更新された質問への回答

更新された質問では、合計がk以下の最も長いサブ配列が求められます。

この質問の場合、基本的なアプローチは同じであり、合計に対して2つの条件しかないため、実際にはソリューションはさらに単純になります。

1)合計がk以下である。

2)合計がkよりも大きい。

ソリューションは次のようになります。

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

私はこれがうまくいくと思います...(再帰的にcontiguous要件を質問から取り出します、それはで提供されるサンプル出力と一致しないようです質問)とOPは、質問は次のとおりであると述べています:

正の整数sの配列が与えられた場合、すべての値の合計がいくつかの正の整数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 __name__ == "__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))

出力:

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

編集01:

合計が最も長いリストをフェッチしたい場合は、常に次のように実行できます。

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 __name__ == "__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))

あなたが見るでしょう:

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

PS 1:再帰が提供する迅速なバックトラック機能がないと、これを行う方法が本当にわかりません...すみません:-(

PS 2:これが必要なものでない場合(私は要件から連続要件を取り出したと述べた)let私は知っている、そして私はこの答えを削除します。

3
BorrajaX

これは、任意の反復可能なs(イテレータであっても)で機能するソリューションです。これは基本的には bigblindの答え と同じアルゴリズムですが、ksの値に比べて大きい場合(関連するサブシーケンスの長さが長い):

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

反復するときに調査しているサブシーケンスのリストを保持しないため、この反復子ベースのアプローチは、実際のコンテンツではなく、最も長いサブシーケンスの長さが必要な場合にのみ役立ちます。

最長のサブシーケンスのコピーを取得する場合は、リストの代わりにcollections.dequeueを使用して(左からのポップが高速になるように)、実行中の合計を追跡して、bigblindの回答に別のバリエーションを使用できます私のコードのように(したがって、sumを何度も呼び出す必要はありません):

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

質問のタイトルが誤解を招くものであり、サブシーケンスが連続しているかどうかを実際に気にしない場合は、現在の動的プログラミングアプローチで目的を達成できると思います。私はあなたのループがどのように機能することを意図していたのかを完全に理解しているわけではありません。私は、入力項目に対する外部ループと、その値(lengthsリストへのインデックス)を含む潜在的な合計に対する2番目のループで最も自然だと思います。また、None0以外のすべての長さの初期値として使用することをお勧めします。そうしないと、特別な場合なしに条件を正しく機能させることが困難になります。

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

少し短く、非円形のsを想定:サイズが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

この問題への取り組み

O(n)-2ポインターアプローチ

最初の要素をsとeに初期化し、subArray_sum = arr [0]

ここで、subArray_sum <= kのときにsubArray_sum <kがeをインクリメントする場合

SubArray_sumが> = kになると、それが<= kになるまでsをインクリメントします

O(nlog n)-バイナリ検索

可能なすべてのサブ配列の長さi。(1 <= i <= n)を検討します。長さiのすべてのサブ配列の中で、合計が最小のサブ配列を見つけます。与えられたiの値に対して、これはO(n)で実行できます。ここで、長さiのサブ配列の場合、長さiのサブアレイの合計が最小<= kである場合、合計<= kのi長さのサブ配列を見つけることができます。次に、次のサブアレイが存在するような最長のiを見つけます。その長さはサブ配列合計<= kです。 start = 1およびend = nでiの範囲を二分探索します。

O(n * n)-ブルートフォース

可能なすべてのサブ配列(n * nは数)を検討し、合計<= kで最長のものを見つける

上記の問題の変形

平均がk以下の最も長いサブアレイの長さ

ここでも適用可能な上記のすべてのアプローチ

1
Vinayak Sangar

この記事は非常に役立ちます。

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

それを使用して解決することができます
Sum Array + Binary Search。

あなたが得る最初の観察は、私たちが私を考慮した場合、番目 要素、次に(i + 1)を続行する必要があります番目 等々。つまり、最後の要素または合計に到達するまで、すべての要素を順番に追加する必要があります。したがって、順序は重要です。

どうすれば数字を追加できますか?存在する方法はn個あります。最初の方法は、最初の要素から開始して、kに到達するか、最後の要素に到達するまで追加できることです。 2番目の方法は、2番目の要素から開始して、kに到達するか最後の要素に到達するまで読み取ることができるということです。

したがって、ブルートフォースアルゴリズムはO(n2) 解決。それをどのように改善できますか?明らかにそれは期待される解決策ではありません。それぞれについて

要素の合計を計算し、合計が指定された「k」の値を超えているかどうかを確認します。これを回避するために、合計配列を作成できます。

シーケンスの合計(または特定の配列内の連続要素の合計)で問題が発生した場合は、合計配列の手法を使用して解決できる可能性が高いことを覚えておいてください。 Sum配列は、指定された配列を使用して新しく構築された配列です。次の式を使用して生成できます。

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

すべてのi> 0。そして

sum[i]=array[i]

i = 0の場合。

合計配列はO(n)時間で作成できます。i間の合計を見つける番目 とj番目 簡単になります。それが違いです

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

あなたに答えを与えます。しかし、それでもO(n2) 解決。
問題は、iの値ごとに、O(n) jを見つけるのに時間がかかることです。
では、どうすればそれを削減できますか?
ビンゴ!ここで二分探索が来ます。間隔inに対して変更されたバイナリ検索を使用することにより、各ijをO(logn)時間です。つまり、O(nlogn)時間しかかかりません。サブアレイの長さを格納するための追加の変数と条件、つまりj−i

お役に立てれば。

0
nikoo28