web-dev-qa-db-ja.com

合計が固定数以下の最長のサブシーケンスを見つける

  • 解決しようとしていた 問題279-B

    自然数tと自然数の配列a[0],...,a[n-1]を指定して、連続サブアレイの最大長a[i],...,a[j] in aを検索しますa[i]+...+a[j] <= t。例えばa = [3,1,2,1]およびt = 5-> 3(1,2,1); a = [2,2,3]およびt = 3-> 1

  • O(n2) 解決。

    合計と総当たりの種類を計算する二重ループ。

  • また、動的プログラミングによる配列内のサブ配列の最大合計についても考えました。
  • 私は this solution を見た-O(n lg n)

次のように定義された合計配列を作成します。

sum[i]=sum[i−1]+array[i]//  for  all   i>0.
sum[i]=array[i]    // for   i=0

次に、次のようなjを見つけます(バイナリ検索により)

sum[j]−sum[i]//j>i

ソリューション1:

    int n; long t; scanf("%d %ld", &n, &t);
    long *minutes = new long[n];
    long totalTime = 0;
    for(int k = 0; k < n; k++){scanf("%ld", minutes + k); totalTime += minutes[k];}

    long start(0), finish(0), currentSum(0), output(0);

    while(finish < n){
        currentSum += minutes[finish++];
        while(currentSum > t){currentSum -= minutes[start++];}
        if(output < finish - start){output = finish - start;}
    }

    printf("%ld\n", output);

    delete[] minutes;
    return 0;

ソリューション2:

    int curr_sum = arr[0], start = 0, i;

    for (i = 1; i <= n; i++)
    {
    // If curr_sum exceeds the sum, then remove the starting elements
       while (curr_sum > sum && start < i-1)
       {
            curr_sum = curr_sum - arr[start];
            start++;
        }
        if (curr_sum == sum)
        {
            printf ("Sum found between indexes %d and %d", start, i-1);
            return 1;
        }
        if (i < n)
          curr_sum = curr_sum + arr[i];
    }
    printf("No subarray found");
    return 0;

なぜ最後のアプローチが機能するのですか(合計が固定数よりも小さい場合のソリューション1と合計が固定数に等しい場合のソリューション2)。その理由や証拠が見つかりませんでした。名前付きアルゴリズムかどうかはわかりません。

4
RE60K

これで投稿を修正しましたが、実装からアルゴリズムを理解しようとするのはめったにありません。アルゴリズムとコードを開発する方がはるかに簡単です。

簡単なことから始めましょう:配列の開始位置が与えられた場合、合計がt以下になるように、開始位置からいくつの配列要素を追加できますか?つまり、インデックス 'start'が与えられた場合、インデックス 'end'≤nを見つけ、配列[start] +配列[start + 1] + ... +配列[end-1]≤t、およびいずれかのend = nまたは配列[end]を追加すると、合計は> tになります。

とても簡単です。まず始めに、

end = start; 
sum = 0; 
while (end < n && sum + array [end] <= t) {
    sum += array [end];
    end += 1;
}

シンプルですが、遅くなる可能性があります。 n = 1,000,000、t = 1,000,000で、すべての配列要素が1または2であるとすると、常に少なくとも50万個の値を合計し、100万個の可能な値のstartを合計します。

次の値start + 1をチェックすると、配列[start]から配列[end-1]までの配列要素の合計が≤tであることがわかります。したがって、配列[start]≥0であるため、合計配列[配列[end + 1]の[start + 1]は≤tです。そして、現在の合計から配列[開始]を引くことにより、1回の操作でその合計を計算します。したがって、startの各値の「end」を計算するには、次のコードを使用します。

end = 0;
sum = 0;
for (start = 0; start < n; ++start) {
    while (end < n && sum + array [end] <= t) {
        sum += array [end];
        end += 1;
    }
    /* Do something with start, end, sum */
    sum -= array [start];
}

Start = 0の場合、最初のバージョンと同じことを行います。大きなnの場合、内側のループを、大きすぎないendの値で開始し、そのポイントまでの合計を正しく加算します。最大の合計を見つける、最大または最小の数の要素を含む合計を見つけるなどの作業を簡単に実行できることは明らかです。

実行時間はO(n)です。これは、開始と終了の両方が1だけ正確にn倍になるためです。

2
gnasher729

遅いのは分かりますが、ここに行きます、

私の解決策は再帰的で、各関数呼び出しに2つのオプションがあります。

  • 配列がすでに条件tを満たしている場合は、その長さを返します
  • それ以外の場合、サブシーケンスを生成するには2つのオプションがあります。最初から要素を削除するか、最後から要素を削除します。したがって、2つのオプションの最大長を返す両方のオプションについて、まったく同じ関数を再帰的に呼び出します。

    def search_subs(a, k):
       #print "array is: ", a
       if sum(a) <= k:
           return a.__len__()
       else:
           return max(search_subs(a[1:], k), search_subs(a[:-1], k))
    
    print search_subs([2,2,3], 3)
    

最悪の場合の複雑さはO(n ln n)だと思います。

1
Vineet Menon

「なぜ最後のアプローチが機能するのか」と尋ねる代わりに、「最後のアプローチが機能するのか」と尋ねるべきです。そうではありません。 startは、ループの反復ごとに1回だけ1ずつ増加し、curr_sumはstartが増加した後に0に等しくなります。 sum == 0でない限り、これはソリューションを見つけることはありません。

配列[start]を減算してstartを増やすコードの前に「while」ステートメントがあるはずだと私は強く思います。

0
gnasher729