Dada una matriz de entrada de enteros de tamaño n, y una matriz de consulta de enteros de tamaño k, ¿cómo encuentro la ventana más pequeña de la matriz de entrada que contiene todos los elementos de la matriz de consulta, preservando el orden?

Si suponemos que todos los elementos de nuestra matriz de consultas son distintos (una suposición razonable), entonces podemos usar el hash para resolver el problema en [matemática] O (n) [/ matemática] usando [matemática] O (m) [ / matemáticas] espacio. Donde [math] n [/ math] es el tamaño de la matriz y [math] m [/ math] es el tamaño de la matriz de consulta. Tenga en cuenta que [math] m [/ math] es como máximo [math] n [/ math].

Aquí está el enfoque:
Deje que la matriz original sea [matemática] X [0,1,…, {n-1}] [/ matemática] y que la matriz de consulta sea [matemática] Q [0,1,2,…, {m-1} ][/matemáticas]. Deje que [math] hash (t) [/ math] sea una tabla hash que asigne el valor [math] t [/ math] a algún número.

Tenga en cuenta que en cada índice al recorrer la matriz [matemática] X [/ matemática], solo necesitamos saber si hemos encontrado [matemática] q_1, q_2, …, q_r [/ matemática] para todas [matemática] r \ le { m-1} [/ matemáticas]. Entonces podemos simplemente almacenar esta información en una tabla hash.

Esto lleva al siguiente algoritmo:

  para i = 0 a n    
     Si x [i] == q [0]
            hash (q [0]) = i
     más si x [i] == q [r] donde r es mayor que 0 y menor que m)
            Si hash (q [r-1]) no está vacío
                   hash (q [r]) = hash (q [r-1])
                   hash (q [r-1]) = vacío

                   Si r == (m-1)
                           min_length = min (i-hash (q [r]), min_length)
                           hash (q [r]) = vacío

Está claro que el algoritmo anterior da la respuesta correcta, ya que cada vez que encontramos una ventana que contiene [matemáticas] q_0, q_1, …, q_ {m-1} [/ matemáticas] tomamos la ventana con una longitud mínima.

El único pequeño detalle que queda es cómo realizaremos las comprobaciones para determinar si [math] x [i] == q [r] [/ math]. Esto se puede hacer usando otra tabla hash que mapea cada elemento en [math] Q [0,1, …, {m-1}] [/ math] a su índice correspondiente en [math] Q [/ math].

Aquí hay una implementación del algoritmo en Ruby (lenguaje de programación)

#find minimum window x = [0,1,2,1,1,5,3,4,5,6,2,1,3] q = [1,2,3] hash = {} map = {} start_index = -1 end_index = x.length min_length = end_index - start_index for j in 0...(q.length) #fill hash tables map[q[j]] = j hash[q[j]] = -1 end for i in 0...(x.length) #traverse array if x[i] == q[0] hash[q[0]] = i elsif map[x[i]] r = map[x[i]] if hash[q[r-1]] != -1 #if not empty hash[q[r]] = hash[q[r-1]] hash[q[r-1]] = -1 if r == (q.length-1) if min_length > (i-hash[q[r]]) #find minimum min_length = (i-hash[q[r]]) start_index = hash[q[r]] end_index = i end hash[q[r]] = -1 end end end end if start_index != -1 puts "minimum length : #{min_length+1}, start index : #{start_index}, end index: #{end_index}" else puts "no window found" end 

EDITAR
Vea mi comentario para un enfoque diferente: aquí

No puedo pensar en nada mejor que [matemáticas] O (n \ log k) [/ matemáticas] tiempo y [matemáticas] O (n) [/ matemáticas] espacio.

Escribo [math] S [/ math] para la entrada y [math] T [/ math] para la consulta. Luego, para cada carácter en [matemáticas] S [/ matemáticas], calcule [matemáticas] N1 [i] = [/ matemáticas] “# índice de la próxima aparición en [matemáticas] S [/ matemáticas] del carácter que sigue a [matemáticas] S [i] [/ math] en [math] T [/ math] “. Podemos calcular esto rápidamente creando un TreeMap de donde cada personaje ocurre en [math] S [/ math]. Luego calcule [matemática] N2 [i] = N1 [N1 [i]] [/ matemática], [matemática] N4 [i] = N2 [N2 [i]] [/ matemática], hasta [matemática] NK [i] [/ matemáticas]. Luego, [math] NK [i] [/ math] muestra la ventana que comienza en [math] i [/ math], y puede tomar el mínimo.

Obviamente hay algunos detalles que resolver, pero creo que la idea está bien.

Un detalle: Parece que esto toma espacio [matemático] O (n \ log k) [/ matemático] porque tenemos matrices [matemático] O (\ log k) N [/ matemático]. Sin embargo, solo necesitamos matrices [math] 2 [/ math]:

  NK = nueva int [n]
 para (int i = 0; i 
 para (int p2 = 1; p2 :
     para (int i = 0; i 

Aquí hay una solución de programación dinámica simple.

Sean S [1 … N] y T [1 … M] las cadenas. Queremos encontrar la subcadena más pequeña de S que contiene T como subsecuencia.

Defina f (i, j) como el valor más pequeño de k de manera que S [i … k] contenga T [j … M] como una subsecuencia. Lo que queremos encontrar es min (f (i, 1) -i + 1) sobre i = 1 … N. Podemos usar la programación dinámica para calcular estos valores de manera eficiente.

Supongamos que hemos calculado el valor de f (i, j) para todos los valores de j para un cierto valor de i. Ahora queremos calcular f (i-1, j). Si S [i-1] = T [j], obviamente tiene sentido que coincidamos con este par de caracteres. Entonces necesitamos encontrar el valor más pequeño de k de tal manera que S [i … k] contenga T [j + 1 … M] como subsecuencia. Es decir, f (i-1, j) = f (i, j + 1) en este caso. Si S [i-1]! = T [j], no tenemos una coincidencia con el carácter actual y necesitamos obtener T [j … M] como una subsecuencia de S [i … k]. Es decir, f (i-1, j) = f (i, j). Con esta recurrencia, f (i, j) se puede calcular para todas las posiciones en el tiempo O (NM), lo que sería significativamente mejor que O (N²) si la cadena que se ubicará es pequeña. El caso base para la recurrencia sería f (i, M + 1) = i-1 para todo i, f (N + 1, j) = infinito para j≤M.

Supongo que la consulta solo tiene valores distintos.

Creo que se puede resolver en [matemáticas] O (n log {k}) [/ matemáticas]. La complejidad del espacio será [matemática] O (k) [/ matemática] si los elementos en la matriz están delimitados (menos de MAXINT – len (consulta)), o podemos modificarlos. De lo contrario, la complejidad del espacio será [matemática] O (n + k) [/ matemática].

Si [math] k << n [/ math], es básicamente un algoritmo [math] O (n) [/ math] que usa espacio constante.

Permítanme primero explicar la idea.

Estoy usando el ejemplo de John Kurlak. Suponer que:
matriz de entrada = [matemática] [1, 43, 4, 23, 1, 3, 5, 9, 1, 12, 4, 3, 4] [/ matemática]
matriz de consulta = [matemáticas] [1, 4, 3] [/ matemáticas]

  1. [matemática] O (k log {k}) [/ matemática] – Primero cree una matriz ordenada de elementos de consulta junto con sus índices: sorted_query = [matemática] [(1,0), (3,2), (4, 1)] [/ matemáticas]
  2. [matemática] O (n log {k}) [/ matemática] – Luego itere sobre la matriz de entrada y reemplace los valores con sus índices en la cola utilizando la matriz de consulta ordenada: matriz = [matemática] [0, -1, 1, -1, 0, 2, -1, -1, 0, -1, 1, 2, 1] [/ matemáticas]
  3. [matemáticas] O (n) [/ matemáticas] Ahora cree dos punteros de barrido, bajo y alto, que solo se mueven de izquierda a derecha. También necesita una tabla hash de frecuencia que almacene la frecuencia de cada elemento de consulta en el rango [bajo, alto] (también puede usar una matriz ordenada para actualizar las frecuencias y la complejidad se convierte en [matemáticas] O (n log {k}) [/ matemática] pero el peor de los casos está limitado). Puede aumentar las frecuencias cuando se mueve alto y disminuirlas cuando se mueve bajo. Siempre está buscando el número más pequeño con una frecuencia de 0 a la derecha de “alto”. Por ejemplo, cuando [low, high] = [math] [0, 2] [/ math] las frecuencias son [math] {0: 1, 1: 0, 2: 0} [/ math] y estamos buscando. Cuando [low, high] = [math] [8, 11] [/ math] las frecuencias deben ser [math] {0: 1, 1: 1, 2: 1} [/ math] y tenemos un rango. En realidad, la frecuencia del primer y último elemento no es importante. Además, tenga en cuenta que puede aumentar la frecuencia de un elemento, solo cuando su frecuencia es menor que la frecuencia de su predecesor. Por ejemplo, si la frecuencia de 1 y 2 son ambas 1, no debe incrementar la frecuencia de 2 si encuentra un 2.

Dejemos demostrar el ejemplo, paso a paso:
PASO 1:
consulta = [(1,0), (3,2), (4,1)]
matriz = [0, -1, 1, -1, 0, 2, -1, -1, 0, -1, 1, 2, 1]
freq = {0: 0, 1: 0, 2: 0}
bajo = 0
alto = 0
index_to_find = 0

PASO 2:
array [low] está en 0 y array [high] == index_to_find
Por lo tanto, establecemos index_to_find en 1 porque necesitamos buscar un 1 ahora.

PASO 3:
¡array [alto] no es 1! Ahora actualizamos el mapa de frecuencia {0: 1, 1: 0, 2: 0}.

Incrementamos alto a 1. array [alto] no a 1, y es un número negativo, por lo que no actualizamos la frecuencia.

Incrementamos alto nuevamente a 2. array [alto] a 1! Ahora actualizamos index_to_update a 2.

PASO 4:
array [high] no es 2. Por lo tanto, actualizamos el mapa de frecuencia {0: 1, 1: 1, 2: 0} e incrementamos high a 3.

Continuamos así hasta que high sea 5. Para entonces, el mapa de frecuencia debería verse como {0: 2, 1: 1 :, 2: 0}. Ahora tenemos un rango y lo almacenamos como el rango mínimo.

PASO 5:
Ahora queremos incrementar bajo para encontrar un nuevo rango. Pero antes de eso necesitamos actualizar el mapa de frecuencia y disminuir el valor para 0: el mapa de frecuencia se convierte en {0: 1, 1: 1, 2: 0}.

low no está en un 0, por lo que incrementamos eso hasta que la izquierda se convierte en 5. Mean mientras el mapa de frecuencia se actualiza a {0: 1, 1: 0, 2: 0}, y estamos buscando un 1.

Entonces, en general, en cada paso estamos realizando operaciones constantes y debido a que los valores bajos y altos solo se incrementan, tendremos operaciones [matemáticas] O (n) [/ matemáticas]. Si el mapa de frecuencia es un árbol binario o una matriz ordenada, la complejidad de cada paso se convierte en [matemática] O (log {k}) [/ matemática].

AHORA que desea que la matriz tenga sus valores originales después de este algoritmo, debemos asumir un límite para los valores de la matriz de entrada. Si todos los elementos de la matriz son inferiores a [MAXINT – len (consulta)], aún podemos mantener todo en su lugar:
Si un elemento es negativo, no lo tocamos.
Si 0 <= e De lo contrario, almacenamos e + len (consulta).

Una vez que hayamos terminado, simplemente podemos actualizar la matriz de entrada a sus valores originales en [math] O (n) [/ math].

Este es un código C ++ 11 para esto:

 #include  #include  #include  using std::lower_bound; using std::max; using std::min; using std::pair; using std::sort; using std::vector; using std::unordered_map; pair find_query(vector& array, const vector& query) { // We need to create a sorted query array along with their original indexes. vector> sorted_query; for (int i = 0; i < query.size(); i++) { // O(k) sorted_query.push_back({query[i], i}); } std::sort(sorted_query.begin(), sorted_query.end()); // O(k log(k)) // Better wrapping in Quora. 🙂 auto sqb = sorted_query.begin(); auto sqe = sorted_query.end(); // We need a list representing indexes of elements in array in query. // If array = [2, 1, 6, 3] and query is [1, 3], we want something like: // [-1, 0, -1, 1] // // If we assume that elements in array are bounded by "MAXINT - len(query)", // we can save a space of O(n). To that end, we modify the array in place, so // that a number less than query.size() is an index in the query, and // larger numbers are the original value + query.size() and negative numbers // have their own original value. // // If array = [2, 1, -6, 3] and query is [1, 3], we want something like: // [4, 0, -6, 1] for (auto& e : array) { // O(n log(k)) // Binary search for each element to find its index in the query. pair p = {e, 0}; auto l = std::lower_bound(sqb, sqe, p); if (l == sqe || e != l->first) { if (e >= 0) { e += query.size(); } } else { e = l->second; } } // Frequency of indexes (not the array values) in [low, high]. // If you use a tree map instead, the order of the following loop will be // O(n log(k)), but the space is guaranteed to be O(k). unordered_map freq; // Now we get two sweeping pointers to find the range [low, high] auto low = array.begin(); auto high = array.begin(); auto index_to_find = 0; pair min_range = {0, array.size()}; // We sweep from low to high. Both high and low only move towards high. while (low != array.end()) { // O(n) (NOTE the comments for freq) if (*low != 0) { if (0 <= *low && *low < query.size()) { freq[*low] = max(0, freq[*low] - 1); if (freq[*low] == 0) { index_to_find = min(index_to_find, *low); } } low++; if (high < low) { // low passed high, so we need to reset high and index_to_find. high = low; index_to_find = 0; } continue; } while (index_to_find != query.size() && high != array.end()) { while (*high != index_to_find) { if (0 <= *high && *high < query.size()) { if (*high == 0 || freq[*high] < freq[*high - 1] ) { freq[*high]++; } } high++; } index_to_find++; } if (high == array.end()) { break; } // We found a new range. if (high - low < min_range.second - min_range.first) { min_range = {low - array.begin(), high - array.begin()}; } low++; } // Transform array to its original values. for (auto& e : array) { if (e < 0) { continue; } if (e >= query.size()) { e -= query.size(); continue; } e = query[e]; } return min_range; } int main() { vector array{ 1, 43, 4, 23, 1, 3, 5, 9, 1, 12, 4, 3, 4 }; vector query{ 1, 4, 3 }; vector c = array; auto r = find_query(array, query); printf("Min range is [%d,%d]\n", r.first, r.second); return 0; } 

Digamos que la longitud de S es n y la longitud de T es m. Aquí hay una solución O (mn) (como señaló John Kurlak).

Paso 1: itere a través de S y para cada carácter agregue una entrada a la tabla hash con el carácter como clave y la lista ordenada de índice que aparecen en S como valor (operaciones O (n)).

Paso 2: ahora itera cada carácter de T, encuentra el personaje en la tabla hash. Obtiene la lista de índice donde aparece este personaje en las operaciones S (O (m))

Paso 3: iterar a través de las listas manteniendo la propiedad que
Índice T [i] en S> índice T [i-1] en S para todo i desde 1..m

Ahora T [m-1] – T [0] es el primer candidato para la ventana mínima. Para encontrar los próximos candidatos, aumente el índice de T [0], mientras mantiene la propiedad

Índice T [i] en S> índice T [i-1] en S.

Mantenga un registro del mínimo actual. Cuando agota cualquiera de la lista, hemos terminado (operaciones O (n), vinculadas por la longitud combinada de las listas), incorrecto en caso de caracteres repetidos en T.

A continuación se muestra el código Java para el mismo (no es muy limpio). Gracias John Kurlak por encontrar múltiples problemas con el código.

  Cadena estática pública findMinWindow (Cadena S, Cadena T) {
         HashMap  h = new HashMap  ();
         int i;
         ArrayList  l = nulo;
         ArrayList > a = new ArrayList > ();
         int I [] = new int [T.length ()];
         booleano encontrado = falso;
         int inicio = 0;
         int end = 0;
         int minwindow = Integer.MAX_VALUE;
        
         / * Iterate S, agrega sus caracteres en una tabla hash con la lista de 
          * índice en el que aparecen como valor * / 
         para (i = 0; i  ();
             }
             l.add (nuevo entero (i));
             h.put (S.charAt (i), l);
         }
        
         / * Iterate T, encuentra sus caracteres en la tabla hash, si hay algún personaje 
          * no se encuentra no hay ventana.  * / 
         para (i = 0; i  0) {
                 / * Asegúrese de que los caracteres aparezcan en el mismo orden en que aparecen en T * /
                 while (I [i] 

Hay un algoritmo que resuelve esto en [matemática] O (nk) [/ matemática] tiempo y [matemática] O (k) [/ matemática] espacio para el caso general. Se modifica fácilmente para resolver el problema en [math] O (n) [/ math] time y [math] O (k) [/ math] space cuando los valores en la matriz de consulta son distintos. Por lo que puedo decir, este algoritmo es mejor que cualquiera de los otros algoritmos que se han publicado porque usa menos espacio y es más simple.

Explicación

La idea básica es mantener una matriz de tamaño [math] k [/ math]. Llamemos a esta matriz [math] start [/ math].

[math] start [0] [/ math] almacena la posición de inicio del segmento más reciente que contiene la subsecuencia [math] [input [0]] [/ math].

[math] start [1] [/ math] almacena la posición de inicio del segmento más reciente que contiene la subsecuencia [math] [input [0], input [1]] [/ math].

[math] start [i] [/ math] almacena la posición de inicio del segmento más reciente que contiene la subsecuencia [math] [input [0], input [1], …, input [i]] [/ math].

Podemos iterar sobre los valores de [math] input [/ math] y actualizar [math] start [/ math] a medida que avanzamos.

El valor en [math] start [query.length – 1] [/ math] almacenará las posiciones de inicio de los sectores que contienen todos los valores de [math] input [/ math] en orden. Si hacemos un seguimiento de los valores que se almacenan en [math] start [query.length – 1] [/ math], podemos encontrar la ventana más corta.

Código Java

  import java.util.Arrays;

 public class MinimumWindow {
   public static void main (String [] args) {
     System.out.println (Arrays.toString (findMinimumWindow (
       nuevo int [] {1, 43, 4, 23, 1, 3, 5, 9, 1, 12, 4, 3, 4},
       nuevo int [] {1, 4, 3}
     )));
   }
  
   public static int [] findMinimumWindow (int [] input, int [] query) {
     if (input == null) {
       lanzar nueva IllegalArgumentException ("¡la matriz de entrada no puede ser nula!");
     }
     if (consulta == nulo) {
       lanzar nueva IllegalArgumentException ("¡la matriz de consulta no puede ser nula!");
     }
    
     int [] start = new int [query.length];
     for (int i = 0; i  = 0; --j) {
         if (consulta [j] == input [i]) {
           inicio [j] = (j == 0)?  i: inicio [j - 1];
          
           if (j == query.length - 1 && start [j]! = -1) {
             int currentLen = i - inicio [j] + 1;
             if (currentLen 
    

Solo se puede encontrar un algoritmo de tiempo y espacio O (n * k), el siguiente código explica bien.

  analizar (s, t):
     # en eso
     f = [[-1] * len (t) para _ en xrango (len (s))]
     prev = -1
     para i en xrange (len (s)):
         si s [i] == t [0]:
             f [i] [0] = 1
             prev = i
         elif prev! = -1:
             f [i] [0] = i - anterior + 1
     para i en xrange (1, len (s)):
         para j en xrange (1, len (t)):
             si s [i] == t [j] yf [i - 1] [j - 1]! = -1:
                 f [i] [j] = f [i - 1] [j - 1] + 1
             elif s [i]! = t [j] yf [i - 1] [j]! = -1:
                 f [i] [j] = f [i - 1] [j] + 1
     volver f


 def resolver (s, t):
     f = analizar (s, t)
     s.reverse ()
     t.reverse ()
     g = analizar (s, t)
     ret = min (f [len (s) - 1] [len (t) - 1], g [len (s) - 1] [len (t) - 1])
     para p en xrange (0, len (s) - 1):
         para q en xrange (0, len (t) - 1):
             si f [p] [q]! = -1 yg [len (s) - 2 - p] [len (t) - 2 - q]! = -1:
                 tmp = f [p] [q] + g [len (s) - 2 - p] [len (t) - 2 - q]
                 si ret == -1:
                     ret = tmp
                 más:
                     ret = min (ret, tmp)
     volver ret


 inputarray = [int (x) para x en raw_input (). split ()]
 queryarray = [int (x) para x en raw_input (). split ()]
 print solve (inputarray, queryarray)

El siguiente algoritmo solo es correcto cuando no le importa el orden de la matriz de consulta, en O (n) tiempo y O (k) espacio

  # S: matriz de entrada
 # P: matriz de consultas
 # return: longitud de la ventana, -1 no es respuesta


 def ventana de corte (s, p):
     dnum = {} # el recuento de números en la matriz de consulta
     para n en p:
         si n en dnum:
             dnum [n] + = 1
         más:
             dnum [n] = 1

     dscan = {} # el recuento de números en la matriz de entrada
     begin, end = 0, 0 # el cursor de inicio y fin en la matriz de entrada
     clave = 0 # si dScan [x]> = dNum [x]: clave ++
     ret = -1
     mientras cierto:
         si clave 

More Interesting

Quiero prepararme para trabajos de programador / desarrollador para enero o febrero de 2017. ¿Cómo debo asignar tiempo eficientemente si actualmente trabajo a tiempo completo?

¿Cuáles son los tutoriales que proporcionan la comprensión profunda de los conceptos básicos de Java?

¿Por qué usar Java sobre C? (mejor respuesta para entrevista)

¿Cuáles son las preguntas técnicas que se hacen durante la entrevista en el sitio como desarrollador front-end?

¿Cuánto importa la velocidad en la programación de entrevistas y hay escenarios en los que la velocidad es más importante que en otros?

Cómo estar bien preparado para responder algoritmos y estructuras de datos en una entrevista de Google

¿Cómo aprovechar al máximo el rechazo de una empresa de software?

¿Qué se debe preparar para una entrevista técnica en pantalla telefónica?

¿Cuántas subcadenas se pueden formar a partir de una cadena de caracteres de longitud n?

¿Cuál es la lógica para aplicar la gravedad a un vector?

¿Los libros sobre preguntas de entrevistas de programación ayudan a los candidatos?

¿Cuáles son algunas consultas SQL que un estadístico debería poder responder / escribir durante una entrevista técnica para un rol orientado a las estadísticas?

¿Cuáles son los conceptos más importantes y frecuentes de C que uno debe saber antes de ir a una entrevista de empresa de TI?

Cómo prepararse para la entrevista en el campus de Triveni Turbines

¿Cómo se preparó para sus entrevistas técnicas de ingeniería de software? Para todos los pasantes e ingenieros de software actuales, cuando recibió un correo electrónico para decirle que tiene una entrevista, ¿cuáles son los pasos que tomó para prepararse?