web-dev-qa-db-ja.com

長さnの配列内の長さ3の個別のサブシーケンスの数

長さnの配列内の長さ_3_(または一般に長さ_k < n_)の個別のサブシーケンスの数を計算する方法は?

注: 2つのサブシーケンスは、それらの要素の順序が異なる場合、異なると見なされます。

例:配列_A = [1, 2, 1, 1]_を想定すると、以下に示すように、長さ_3_の3つの異なるサブシーケンスしかないため、答えは_3_になります。

_[1, 1, 1]
[1, 2, 1]
[2, 1, 1]
_

配列のサイズ_n <= 10^5_、配列内の各要素_A_i <= n_。

私のアプローチ:

強引なアプローチ、つまり長さ_3_のタプルを取得してマップに挿入する方法を考えました。しかし、これは空間/時間効率ではありません。

編集:これはインタビューの質問であり、k = 3の場合予想される時間と空間の複雑さはO(n)であると言われました。

12
dodobhoot

面接の問題でよくあることですが、動的計画法の解決策があります。 T(m, k)を最初のk要素の異なる長さ-mサブシーケンスの数とします。次に、入力Aに1ベースのインデックスを付けると、2Dの繰り返しがあります。

_T(m, 0) = 1
T(m, k) = T(m-1, k) + T(m-1, k-1) -
          ^^^^^^^^^   ^^^^^^^^^^^
     A_m not chosen   A_m chosen

            { T(i-1, k-1), if i < m is the maximum index where A_i = A_m
            { 0,           if no such index exists
_

減算された項は、重複をカウントしないことを保証します。詳細については、 https://stackoverflow.com/a/5152203/2144669 を参照してください。

実行時間(表示された各シンボルのこれまでの右端の出現を維持するためのハッシュマップを使用)はO(k n)であり、これは_k = 3_の場合はO(n)です。

10
David Eisenstat

これは少し異なるテイクです。要素mがサブシーケンスでk番目になることができる方法の数は、任意の要素(mを含む)の以前の発生すべての方法の合計と考えることができます。 )は_(k-1)_ thにすることができます。ただし、右に移動すると、必要な更新はmのみです。他の合計は一定のままです。

例えば、

_// We want to avoid counting [1,1,1], [1,2,1], etc. twice
[1, 2, 1, 1, 1]
_

(便宜上、配列を垂直に表示します)

_            <-  k  ->
[1,  ->  1: [1, 0, 0]
 2,  ->  2: [1, 1, 0]
 1,  ->  1: [1, 2, 1]
 1,  ->  1: [1, 2, 3]
 1]  ->  1: [1, 2, 3]
_

ここで、別の要素、たとえば3を追加した場合

_...
 3]  ->  3: [1, 2, 3]

 // 1 means there is one way
 // the element, 3, can be first

 // 2 means there are 2 ways
 // 3 can be second: sum distinct
 // column k[0] = 1 + 1 = 2

 // 3 means there are 3 ways
 // 3 can be third: sum distinct
 // column k[1] = 2 + 1 = 3
_

個別の_k[2]_列の合計:

_0 + 3 + 3 = 6 subsequences

[1,2,1], [2,1,1], [1,1,1]
[1,1,3], [2,1,3], [3,2,1]
_

各列のsum-distinctは、反復ごとにO(1)で更新できます。現在の要素のk合計(各要素のリストを1つ更新します)は、O(k)(この場合はO(1))を取ります。

JavaScriptコード:

_function f(A, k){
  A.unshift(null);
  
  let sumDistinct = new Array(k + 1).fill(0);
  let hash = {};

  sumDistinct[0] = 1;

  for (let i=1; i<A.length; i++){
    let newElement;
    
    if (!hash[A[i]]){
      hash[A[i]] = new Array(k + 1).fill(0);
      newElement = true;
    }
    
    let prev = hash[A[i]].slice();

    // The number of ways an element, m, can be k'th
    // in the subsequence is the sum of all the ways
    // the previous occurence of any element
    // (including m) can be (k-1)'th
    for (let j=1; j<=k && j<=i; j++)
      hash[A[i]][j] = sumDistinct[j - 1];

    for (let j=2; j<=k && j<=i; j++)
      sumDistinct[j] = sumDistinct[j] - prev[j] + hash[A[i]][j];

    if (newElement)
      sumDistinct[1] += 1;

    console.log(JSON.stringify([A[i], hash[A[i]], sumDistinct]))
  }

  return sumDistinct[k];
}

var arr = [1, 2, 1, 1, 1, 3, 2, 1];

console.log(f(arr, 3));_
4