Averigüe en tiempo lineal si hay un par en el vector ordenado que se suma a cierto valor

8

Dada una std::vectorde elementos distintos clasifican en orden ascendente, quiero desarrollar un algoritmo que determina si hay dos elementos de la colección cuya suma es un cierto valor, sum.

He intentado dos enfoques diferentes con sus respectivas compensaciones:

  1. Puedo escanear todo el vector y, para cada elemento del vector, aplicar la búsqueda binaria ( std::lower_bound) en el vector para buscar un elemento correspondiente a la diferencia entre sumy el elemento actual. Esta es una solución de tiempo O (n log n) que no requiere espacio adicional.

  2. Puedo atravesar todo el vector y llenar un std::unordered_set. Luego, escaneo el vector y, para cada elemento, busco std::unordered_setla diferencia entre sumy el elemento actual. Dado que la búsqueda en una tabla hash se ejecuta en tiempo constante en promedio, esta solución se ejecuta en tiempo lineal. Sin embargo, esta solución requiere espacio lineal adicional debido a la std::unordered_setestructura de datos.

Sin embargo, estoy buscando una solución que se ejecute en tiempo lineal y no requiera espacio lineal adicional. ¿Algunas ideas? Parece que me veo obligado a cambiar la velocidad por el espacio.

Lui
fuente

Respuestas:

10

Como std::vectorya está ordenado y puede calcular la suma de un par sobre la marcha , puede lograr una solución de tiempo lineal en el tamaño del vector con espacio O (1).

La siguiente es una implementación similar a STL que no requiere espacio adicional y se ejecuta en tiempo lineal:

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;
}

La idea es atravesar el vector desde ambos extremos, adelante y atrás, en direcciones opuestas al mismo tiempo y calcular la suma del par de elementos mientras lo hace.

Al principio, el par consta de los elementos con los valores más bajos y más altos, respectivamente. Si la suma resultante es menor que sum, entonces avance first: el iterador apunta al extremo izquierdo. De lo contrario, mueva last, el iterador apuntando al extremo derecho, hacia atrás. De esta manera, la suma resultante se acerca progresivamente a sum. Si ambos iteradores terminan apuntando al mismo elemento y no sumse ha encontrado ningún par cuya suma sea igual a , entonces no existe tal par.

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';
}

El resultado es:

0 1 0 1

Gracias a la naturaleza genérica de la plantilla de función has_pair_sum()y dado que solo requiere iteradores bidireccionales, esta solución también funciona con std::list:

std::list<int> lst{1, 3, 4, 7, 11, 13, 17};
has_pair_sum(lst.begin(), lst.end(), 2);
眠 り ネ ロ ク
fuente
5

Tuve la misma idea que la de la respuesta de 眠 り ネ ロ ク , pero con una implementación un poco más comprensible.

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
fuente
Gracias por tus comentarios. Edité ahora el Post.
Atr0x
2

bueno, dado que ya tenemos una matriz ordenada, podemos hacerlo con un enfoque de dos punteros, primero mantenemos un puntero izquierdo al comienzo de la matriz y un puntero derecho al final de la matriz, luego en cada iteración verificamos si la suma del valor de el índice del puntero izquierdo y el valor del índice del puntero derecho son iguales o no, en caso afirmativo, regrese de aquí, de lo contrario, tenemos que decidir cómo reducir el límite, es decir, aumentar el puntero izquierdo o disminuir el puntero derecho, por lo que comparamos la suma temporal con suma dada y si esta suma temporal es mayor que la suma dada, entonces decidimos reducir el puntero derecho, si aumentamos el puntero izquierdo, la suma temporal permanecerá igual o solo aumentará pero nunca menor, por lo que decidimos reducir el puntero derecho para que la suma temporal disminuye y llegamos cerca de nuestra suma dada, de manera similar si la suma temporal es menor que la suma dada,así que no tiene sentido reducir el puntero derecho como suma temporal, ya sea suma o disminución más, pero nunca aumentará, así que aumentamos nuestro puntero izquierdo para que nuestra suma temporal aumente y lleguemos a la suma dada, y hacemos el mismo proceso una y otra vez a menos que obtener la suma igual o el valor del índice del puntero izquierdo se vuelve mayor que el índice del puntero derecho derecho o viceversa a continuación se encuentra el código para la demostración, avíseme si algo no está claroavíseme si algo no está claroavíseme si algo no está claro

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;
}
mss
fuente