ソートされたベクトルに、特定の値に加算されるペアがあるかどうかを線形時間で調べます


8

std::vector昇順で並べ替えられた個別の要素を考慮して、合計が特定の値であるコレクションに2つの要素があるかどうかを判断するアルゴリズムを開発したいと思いますsum

私はそれぞれのトレードオフで2つの異なるアプローチを試しました:

  1. ベクトル全体をスキャンし、ベクトルの各要素std::lower_boundに対してsum、現在の要素との差に対応する要素を検索するために、ベクトルにバイナリ検索()を適用できます。これは、追加のスペースを必要としないO(n log n)時間ソリューションです。

  2. ベクトル全体をトラバースしてにデータを入力できstd::unordered_setます。次に、ベクトルをスキャンし、各要素std::unordered_setについて、とsum現在の要素の違いを調べます。ハッシュテーブルの検索は平均して一定の時間で実行されるため、このソリューションは線形時間で実行されます。ただし、このソリューションでは、std::unordered_setデータ構造のために追加の線形スペースが必要です。

それにもかかわらず、線形時間で実行され、追加の線形スペースを必要としないソリューションを探しています。何か案は?スピードとスペースのトレードオフを強いられているようです。

回答:


10

std::vectorすでにソートされていて、ペアの合計を計算することができ、その場では、あなたはO(1)スペースを持つベクトルのサイズの線形時間ソリューションを実現することができます。

以下は、追加のスペースを必要とせず、線形時間で実行されるSTLのような実装です。

template<typename BidirIt, typename T>
bool has_pair_sum(BidirIt first, BidirIt last, T sum) {
    if (first == last)
        return false; // empty range

   for (--last; first != last;) {
      if ((*first + *last) == sum)
         return true; // pair found

      if ((*first + *last) > sum)
         --last; // decrease pair sum
      else // (*first + *last) < sum (trichotomy)
         ++first; // increase pair sum
   }

    return false;
}

この考え方は、ベクトルを両端(正面と背面)から同時に反対方向にトラバースし、その間に要素のペアの合計を計算することです。

最初は、ペアはそれぞれ最小値と最大値を持つ要素で構成されています。結果の合計がより小さい場合sumは、前進firstします。イテレータは左端を指しています。それ以外の場合は、移動last–右端を指すイテレータ–逆方向。このようにして、結果の合計は徐々にに近づきsumます。両方のイテレータが同じ要素を指してしまい、合計が等しいペアsumが見つからない場合、そのようなペアはありません。

auto main() -> int {
   std::vector<int> vec{1, 3, 4, 7, 11, 13, 17};

   std::cout << has_pair_sum(vec.begin(), vec.end(), 2) << ' ';
   std::cout << has_pair_sum(vec.begin(), vec.end(), 7) << ' ';
   std::cout << has_pair_sum(vec.begin(), vec.end(), 19) << ' ';
   std::cout << has_pair_sum(vec.begin(), vec.end(), 30) << '\n';
}

出力は次のとおりです。

0 1 0 1

関数テンプレートの一般的な性質のおかげで、has_pair_sum()双方向の反復子が必要なだけなので、このソリューションは次の場合にも機能しstd::listます。

std::list<int> lst{1, 3, 4, 7, 11, 13, 17};
has_pair_sum(lst.begin(), lst.end(), 2);

5

私は眠りネロクの答えと同じ考えを持っていましたが、少しわかりやすい実装でした。

bool has_pair_sum(std::vector<int> v, int sum){
    if(v.empty())
        return false;

    std::vector<int>::iterator p1 = v.begin();
    std::vector<int>::iterator p2 = v.end(); // points to the End(Null-terminator), after the last element
    p2--; // Now it points to the last element.

    while(p1 != p2){  
        if(*p1 + *p2 == sum)
            return true;
        else if(*p1 + *p2 < sum){ 
            p1++;
        }else{
            p2--;
        }
    }

    return false;
}

コメントしてくださってありがとうございます。投稿を編集しました。
Atr0x

2

並べ替え済みの配列が既に与えられているので、2つのポインターのアプローチでそれを行うことができます。最初に配列の先頭に左ポインターを、配列の最後に右ポインターを保持し、次に各反復で値の合計が左ポインタインデックスと右ポインタインデックスの値が等しいか等しくない場合は、ここから戻ります。それ以外の場合は、境界を減らす方法を決定する必要があります。つまり、左ポインタを増やすか右ポインタを減らすかのどちらかなので、一時的な合計を与えられた合計とこの一時的な合計が与えられた合計よりも大きい場合は、右のポインタを減らすことを決定します。左のポインタを増やすと、一時的な合計は同じままか、増加するだけですが、決して減少しないので、右のポインタを減らすことを決定します。一時的な合計が減少し、指定された合計に近づきます。同様に、一時的な合計が指定された合計よりも小さい場合、したがって、一時的な合計は合計のままか減少するが増加しないので、右のポインタを減らす意味はありません。左のポインタを増やして一時的な合計を増やし、指定された合計に近づきます。同じプロセスを何度も繰り返します。等しい合計または左ポインターインデックス値が右右ポインターインデックスより大きくなるか、またはその逆になります。以下はデモ用のコードです。不明な点がある場合はお知らせください不明な点がある場合はお知らせください不明な点がある場合はお知らせください

bool pairSumExists(vector<int> &a, int &sum){
    if(a.empty())
    return false;

    int len = a.size();
    int left_pointer = 0  , right_pointer = len - 1;

    while(left_pointer < right_pointer){
        if(a[left_pointer] + a[right_pointer] == sum){
            return true;
        }
        if(a[left_pointer] + a[right_pointer] > sum){
            --right_pointer;
        }
        else
        if(a[left_pointer] + a[right_poitner] < sum){
            ++left_pointer;
        }
    }
    return false;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.