Problema de TopK (el mayor / menor número de k)

Problema de TopK (el mayor / menor número de k)

Primero, plantea brevemente el problema. Para una matriz de enteros, encuentra el knúmero más pequeño . Por ejemplo, si ingresas los 8 números 4, 5, 1, 6, 2, 7, 3 y 8, los 4 números más pequeños son 1. , 2, 3, 4.

Enlace de Leetcode , este blog se refiere a la respuesta oficial de Leetcode .

El método para encontrar los k números máximos o mínimos es el mismo. Las siguientes soluciones son todas para los k números mínimos .

1. Ordenar

¡La idea más simple es ordenar directamente! Después de ordenar, simplemente seleccione el número k más pequeño directamente y cargue directamente el código:

#include <vector>
#include <iostream>
#include <algorithm>
void getLeastNumbers(vector<int>& arr, int k) {
    
    
    sort(arr.begin(), arr.end());
}
int main() {
    
    
	ios::sync_with_stdio(false);
	vector<int> arr = {
    
    3,2,1,4,6,5};
	int k = 3;
	getLeastNumbers(arr, k);
	for(int i; i<k; i++){
    
    
		cout<<arr[i]<<" ";
	}
	return 0;
}

Resultado de salida:

1 2 3

Análisis de algoritmos

  • Complejidad de tiempo: O * (* nlogn), donde n es la arrlongitud de la matriz . La complejidad temporal del algoritmo es la complejidad temporal de la clasificación.
  • Complejidad del espacio: O (logn), la complejidad del espacio adicional necesaria para la clasificación es O (logn)

2. Montón

Usamos priority_queue en el contenedor STL para implementar el mantenimiento en tiempo real de los valores k superiores de un montón raíz grande (si se requieren los valores k superiores, se requiere un montón raíz pequeño para mantener los valores k superiores de la matriz en tiempo real. Priority_queue toma como valor predeterminado el montón raíz grande, agregando parámetros Use una pila de raíces pequeña)

Directamente en el código:

void getLeastNumbers(vector<int>& arr, int k) {
    
    
    priority_queue<int>Q;
    for (int i = 0; i < k; ++i) Q.push(arr[i]);
    for (int i = k; i < (int)arr.size(); ++i) {
    
    
        if (arr[i] < Q.top()) {
    
    
            Q.pop();
            Q.push(arr[i]);
        }
    }
    for (int i = 0; i < k; ++i) {
    
    
    	cout<<Q.top()<<" ";
        Q.pop();
    }
}
int main() {
    
    
	ios::sync_with_stdio(false);
	vector<int> arr = {
    
    3,2,1,4,6,5};
	int k = 3;
	getLeastNumbers(arr, k);
	return 0;
}

Resultado de salida:

3 2 1

Implemente un montón raíz pequeño, manteniendo así los valores k más grandes:

priority_queue<int, vector<int>, greater<int>>Q;

Análisis de algoritmos

  • Complejidad de tiempo: O (nlogk), donde n es la longitud de la matriz arr. Dado que el montón raíz grande mantiene los primeros k valores pequeños en tiempo real, la inserción y eliminación son todas de complejidad de tiempo O (logk). En el peor de los casos, se insertarán n números en la matriz, por lo que se requiere un total de complejidad de tiempo O (nlogk).

  • Complejidad del espacio: O (k), porque hay como máximo k números en el montón de raíces grandes

3. Pensamientos de clasificación rápida

Para aprender de la idea de ordenación rápida, cada división de ordenación rápida dividirá la matriz en dos partes, y ahora necesitamos encontrar el número más pequeño de k es en realidad para dividir la matriz en dos partes, una de las cuales es ky menor que Punto de división .

Definimos randomized_selected(arr, l, r, k)dividir el arreglo, [l,r]para dividir el rango del arreglo, kpara esperar que el número de puntos sea menor que el número de puntos, llamamos a la función de clasificación rápida para dividir la [l,r]parte del arreglo, asumiendo que las coordenadas de la posición de división obtenida son pos( posel valor menor que la posición está a la izquierda , Los mayores que están a la derecha), y luego habrá lo siguiente:

  1. Si pos - l + 1 == k, indica pivotque números pequeños de k, el acceso directo a la izquierda del valor de k es el número más pequeño k.
  2. Si pos - l + 1 < k, k representa un número pequeño en el lado derecho del pivote, entonces la llamada recursiva randomized_selected(arr, pos + 1, r, k - (pos - l + 1))a
  3. Si pos - l + 1 > k, k representa un número pequeño en el lado izquierdo del pivote, las llamadas recursivas randomized_selected(arr, l, pos - 1, k)pueden ser.

De esta manera, finalmente se puede encontrar el punto de división.

Aquí está la parte del código:

#include <vector>
#include <iostream>
#include <algorithm>
#include <time.h>
// 快排划分的过程,守卫放在最右侧 
int partition(vector<int>& nums, int l, int r) {
    
    
    int pivot = nums[r];
    int i = l - 1;
    for (int j = l; j <= r - 1; ++j) {
    
    
        if (nums[j] <= pivot) {
    
    
            i = i + 1;
            swap(nums[i], nums[j]);
        }
    }
    swap(nums[i + 1], nums[r]);
    return i + 1;
}
// 基于随机的划分
int randomized_partition(vector<int>& nums, int l, int r) {
    
    
    int i = rand() % (r - l + 1) + l;	// 随机选取划分元素 
    swap(nums[r], nums[i]);
    return partition(nums, l, r);	// 返回划分的pos 
}
void randomized_selected(vector<int>& arr, int l, int r, int k) {
    
    
    if (l >= r) return;
    int pos = randomized_partition(arr, l, r);
    int num = pos - l + 1;
    if (k == num) return;	// 划分位置刚好为k,直接返回 
    else if (k < num) randomized_selected(arr, l, pos - 1, k);	// 否则继续划分 
    else randomized_selected(arr, pos + 1, r, k - num);   
}
void getLeastNumbers(vector<int>& arr, int k) {
    
    
    srand((unsigned)time(NULL));
    randomized_selected(arr, 0, (int)arr.size() - 1, k);
}
int main() {
    
    
	ios::sync_with_stdio(false);
	vector<int> arr = {
    
    3,2,1,4,6,5};
	int k = 3;
	getLeastNumbers(arr, k);
	for(int i = 0; i < k; i++){
    
    
		cout<<arr[i]<<" ";
	}
	return 0;
}

Resultado de salida:

1 2 3

Análisis de algoritmos

  • Complejidad temporal: la expectativa es O (n), la complejidad temporal en el peor de los casos es O (n 2) O (n ^ 2)O ( n2 ) Cuandola situación es la peor, cada punto de división es el máximo o mínimo, se requiere un total de n-1 divisiones y una división requiere complejidad de tiempo lineal O (n), por lo que el tiempo es complicado en el peor de los casos El grado esO (n 2) O (n ^ 2)O ( n2 )
  • Complejidad del espacio: la expectativa es O (logn), la profundidad esperada de las llamadas recursivas es O (logn), el espacio requerido para cada capa es O (1) y solo hay variables constantes.

Algoritmo 4.bfprt

Está relacionado con la mejora en la idea de clasificación rápida, porque la complejidad temporal del pensamiento de clasificación rápida alcanzará O (n 2) O (n ^ 2) en el peor de los casos.O ( n2 ), el algoritmo bfprt se basa en algunas mejoras realizadas en este, más específicamente, la selección de guardia durante la cola rápida (cuando la guardia es el valor máximo / pequeño, el algoritmo degenerará aO (n 2) O (n ^ 2) )O ( n2 )), seleccione el primer lugar encontrando la mediana dos veces. Para obtener más información, consulte esteblog, que también contiene códigos prácticos ~

5. Llamar a las funciones de la biblioteca directamente

Como insistes en ver esto, debo haber aprendido los métodos anteriores, jajaja, no esperaba que hubiera funciones de biblioteca listas para usar (la parte dijo que es muy incómodo, no encontré esta función QAQ antes ...)

Hay una función mágica en la poderosa biblioteca STL nth_element, que se usa para encontrar el k-ésimo entero más pequeño. Es muy conveniente (pero no se siente demasiado exagerado. Se siente similar al topK de la idea de ordenación rápida escrita por mí, pero Este conveniente pato)

El siguiente código presenta brevemente cómo usarlo, se puede decir que es claro de un vistazo_

int a[n];
nth_element(a,a+k,a+n);		// 将第k小的元素就位
cout<<a[k]<<endl;

Supongo que te gusta

Origin blog.csdn.net/weixin_44338712/article/details/108076737
Recomendado
Clasificación