Galería de mapas mentales Recopilación de código de ideas clave para las preguntas sobre algoritmos de deducción más potentes
He recopilado las ideas principales y los códigos de las preguntas de algoritmo anteriores. Este mapa mental continuará actualizándose. Los amigos que lo compraron pueden agregarme como amigos a través del blog en mi presentación personal. También discute conmigo el problema algorítmico.
Editado a las 2021-06-29 20:35:23,Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Este es un mapa mental sobre una breve historia del tiempo. "Una breve historia del tiempo" es una obra de divulgación científica con una influencia de gran alcance. No sólo presenta los conceptos básicos de cosmología y relatividad, sino que también analiza los agujeros negros y la expansión. del universo. temas científicos de vanguardia como la inflación y la teoría de cuerdas.
¿Cuáles son los métodos de fijación de precios para los subcontratos de proyectos bajo el modelo de contratación general EPC? EPC (Ingeniería, Adquisiciones, Construcción) significa que el contratista general es responsable de todo el proceso de diseño, adquisición, construcción e instalación del proyecto, y es responsable de los servicios de operación de prueba.
Los puntos de conocimiento que los ingenieros de Java deben dominar en cada etapa se presentan en detalle y el conocimiento es completo, espero que pueda ser útil para todos.
Tabla de contenido
algoritmo de Likou
0. Pensamiento inverso
6.Transformación en zigzag
1. Ordenar por fila
0.Título
Organice una cadena determinada en forma de Z de arriba a abajo y de izquierda a derecha de acuerdo con el número de líneas dado. Después de eso, su salida debe leerse línea por línea de izquierda a derecha para generar una nueva cadena.
1.Ideas
Repita ss de izquierda a derecha, agregando cada carácter a la línea apropiada. La fila apropiada se puede rastrear usando la fila actual y las variables de dirección actual. La dirección actual solo cambiará cuando subamos a la fila superior o bajemos a la fila inferior.
2.Código
solución de clase { conversión de cadena pública (cadena s, int numRows) { si (numRows == 1) devuelve s; List<StringBuilder> filas = new ArrayList<>() //Almacena los caracteres de cada fila después de la transformación. para (int i = 0; i < Math.min(numRows, s.length()); i ) filas.add(new StringBuilder()); int curRow = 0; booleano yendo hacia abajo = falso; for (char c : s.toCharArray()) { //Atraviesa s directamente, conviértelo en una matriz de caracteres y asígnalo a c uno por uno filas.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) yendoAbajo = !yendoAbajo; curRow = yendo hacia abajo? 1: -1; } StringBuilder ret = nuevo StringBuilder(); para (StringBuilder fila: filas) ret.append(fila); devolver ret.toString(); } }
3. Complejidad
Tiempo O(n), donde n=len(s), espacio O(n)
1. Algoritmo puro
7. Inversión de enteros
1 pregunta
Dado un entero de 32 bits con signo, es necesario invertir cada bit del entero. Supongamos que nuestro entorno solo puede almacenar enteros con signo de 32 bits. Si el entero se desborda después de la inversión, se devolverá 0.
2.Ideas
Para "sacar" y "empujar" números sin la ayuda de una pila/matriz auxiliar, podemos usar las matemáticas, sacar primero el último dígito, luego dividir por 10 para deshacernos del último dígito, invertir el número y seguir multiplicándose. Después de 10, agregue el último dígito extraído y primero determine si se desbordará.
3.Código
solución de clase { público int reverso (int x) { int rev = 0; mientras (x! = 0) { int pop = x % 10; //Saca el último dígito de x x /= 10; //Eliminar el último dígito de x //Cuando int ocupa 32 bits, el rango de valores es -2147483648~2147483647, por lo que pop>7 o pop<-8 if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > Integer.MAX_VALUE % 10)) devuelve 0; if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < Integer.MIN_VALUE % 10)) devuelve 0; rev = rev * 10 pop; //Después de que el conjunto avance un dígito, agregue el último dígito } volver rev; } }
4. Complejidad
Tiempo: O(log(x)), espacio O(1)
2.matriz
3. Lista enlazada
4. Cuerda
5.Búsqueda binaria
6.árbol
1. árbol binario
7.La amplitud primero
8. La profundidad primero
9.Doble puntero
10. Clasificación
11.Método de retroceso
12. tabla hash
13.Pila
14. Programación dinámica
subtema
Preguntas de entrevista clásicas
15,
Conceptos básicos de algoritmos
1. Complejidad del tiempo
1.Definición
La complejidad del tiempo es una función que describe cualitativamente el tiempo de ejecución del algoritmo.
2. ¿Qué es la Gran O?
Big O se usa para representar el límite superior. Cuando se usa como el límite superior del tiempo de ejecución en el peor de los casos del algoritmo, es el límite superior del tiempo de ejecución para cualquier entrada de datos. la complejidad temporal del algoritmo en general.
3. Diferencias en diferentes tamaños de datos.
Debido a que Big O es la complejidad del tiempo que se muestra cuando la magnitud de los datos atraviesa un punto y la magnitud de los datos es muy grande. Esta cantidad de datos es la cantidad de datos donde el coeficiente constante ya no juega un papel decisivo, por lo que llamamos complejidad del tiempo. Se omiten los coeficientes de términos constantes porque el tamaño de datos predeterminado es generalmente lo suficientemente grande. En base a este hecho, una clasificación de la complejidad temporal del algoritmo dada es la siguiente:
O(1) orden constante < O(logn) orden logarítmico < O(n) orden lineal < O(n^2) orden cuadrado < O(n^3) (orden cúbico) < O(2^n) (orden exponencial )
4. ¿Cuál es la base del registro en O (logn)?
Pero colectivamente decimos logn, es decir, ignorando la descripción de la base, el logaritmo de n con base 2 = el logaritmo de 10 con base 2 * el logaritmo de n con base 10, y el logaritmo de 10 con base 2 es un constante que puede ignorarse
Resumen de preguntas
0.interesante
Una pasada de codificación es tan feroz como un tigre, derrotando al 500% en sumisión.
Una serie de ideas florecieron con risas y la presentación superó el 80%.
1. No entiendo
1.árbol
145. Recorrido posterior al orden Recorrido de Morris
105. ¿Por qué no es posible utilizar el mapeo de índices en el método conciso?
2. Frecuencia de las entrevistas
1.Búsqueda binaria
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
2.La amplitud primero
200.279.301.199.101.127.102.407.133.107.103.126.773.994.207.111.847.417.529.130.542.690,,,743.210.913.512
3. tabla hash
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
4. Método de retroceso
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
5. Lista enlazada
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
6. Ordenar
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
7. La profundidad primero
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
8.árbol
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
9.matriz
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
10.Doble puntero
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
11.Pila
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
12 cuerdas
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
subtema
Preguntas similares
1.La suma de dos números
1,15,
Relacionado con el código
1. Estilo de código
1. Principios básicos
1.Sangría
Se prefiere utilizar 4 espacios. En la actualidad, casi todos los IDE convierten pestañas a 4 espacios de forma predeterminada, por lo que no hay gran problema.
2. Longitud máxima de línea
79 caracteres, sería mejor usar barras invertidas para los saltos de línea
3.Importar
1.Formato
La importación se realiza en la parte superior del archivo, después del comentario del archivo. La importación suele ser una importación de una sola línea o desde... importar.
2. Secuencia
1. Importación de biblioteca estándar 2. Importaciones de terceros relevantes 3. Importaciones de bibliotecas/aplicaciones locales específicas Coloque una línea en blanco entre cada grupo de importación. Se recomiendan las importaciones absolutas porque son más legibles; se pueden utilizar importaciones relativas explícitas en lugar de importaciones absolutas cuando se trata de diseños de paquetes complejos, que son demasiado detallados.
3. Atención
4. Comentarios
5. Cadenas de documentos cadenas de documentos
6. Convención de nomenclatura
Las funciones, variables y propiedades se escriben en letras minúsculas, con guiones bajos entre las palabras, por ejemplo, lowercase_underscore. Las clases y excepciones deben nombrarse con la primera letra de cada palabra en mayúscula (mayúsculas altas), como CapitalizedWord. Las propiedades de la instancia protegida deben comenzar con un guión bajo. Las propiedades de instancia privada deben comenzar con dos guiones bajos. Las constantes a nivel de módulo deben escribirse con letras mayúsculas y con guiones bajos entre las palabras. El primer parámetro del método de instancia en la clase debe llamarse self, que representa el objeto mismo. El primer parámetro del método de clase (método cls) debe llamarse cls, lo que indica la clase en sí.
2. Presta atención a los detalles
0.Herramientas
1. Ajuste de código
Las nuevas líneas deben preceder a los operadores binarios
2. Línea en blanco
Hay dos líneas en blanco entre la función de nivel superior y la definición de clase. Hay una línea en blanco entre las definiciones de funciones dentro de la clase.
3. Comillas en cadena
Las comillas dobles y las comillas simples son lo mismo, pero es mejor seguir un estilo. Estoy acostumbrado a usar comillas simples porque: No es necesario mantener presionada la tecla Shift al escribir código para mejorar la eficiencia Algunas cadenas de lenguaje deben usar comillas dobles y Python no necesita agregar una barra invertida como escape al procesarlas.
4. Espacio
Utilice espacio para sangría en lugar de la tecla de tabulación. Utilice cuatro espacios para cada nivel de sangría relacionada con la sintaxis. Para expresiones largas que abarcan varias líneas, todas menos la primera línea deben tener una sangría de 4 espacios por encima del nivel habitual. Cuando utilice subíndices para recuperar elementos de una lista, llamar a funciones o asignar valores a argumentos de palabras clave, no agregue espacios alrededor de ellos. Al asignar un valor a una variable, se debe escribir un espacio en los lados izquierdo y derecho del símbolo de asignación, y solo uno
Inmediatamente dentro de paréntesis, corchetes o llaves Inmediatamente antes de una coma, punto y coma o dos puntos, debe haber un espacio después. Inmediatamente antes del paréntesis de apertura de una lista de parámetros de función Inmediatamente antes del paréntesis de apertura de una operación de índice o corte Coloque siempre 1 espacio a cada lado de los siguientes operadores binarios: asignación (=), asignación incremental (=, -=, etc.), comparación (==, <, >, !=, <>, <=, > = , en, no, en, es, no es), booleano (y, o, no) Pon espacios alrededor de los operadores matemáticos. Cuando se usa para especificar argumentos de palabras clave o valores de argumentos predeterminados, no use espacios alrededor = return magic(r=real, i=imag)
Agregue los espacios necesarios, pero evite los espacios innecesarios. Evite siempre los espacios en blanco finales, incluidos los caracteres invisibles. Por lo tanto, si su IDE admite la visualización de todos los caracteres invisibles, ¡actívelo! Al mismo tiempo, si el IDE admite la eliminación de contenido en blanco al final de la línea, ¡habilítelo también! yapf puede ayudarnos a resolver esta parte. Solo necesitamos formatear el código después de escribirlo.
5. Declaración compuesta
No se recomienda incluir varias declaraciones en una línea.
6. Coma al final
Cuando los elementos de la lista, los parámetros y los elementos de importación pueden seguir aumentando en el futuro, dejar una coma al final es una buena opción. El uso habitual es que cada elemento esté en su propia línea, con una coma al final y una etiqueta de cierre escrita en la línea siguiente después del último elemento. Si todos los elementos están en la misma línea, no es necesario hacer esto.
7. Solucionar problemas detectados por linter
Utilice flake8 para comprobar el código Python y modificar todos los errores y advertencias marcados, a menos que existan buenas razones.
2. Códigos de uso común
1. Diccionario
1. Cuente el número de ocurrencias
cuenta[palabra] = cuenta.get(palabra,0) 1
2. Lista
1. Listar la clasificación de palabras clave especificadas
items.sort(key=lambda x:x[1], reverse=True)# Ordena el segundo elemento en orden inverso
2. Convierta cada elemento de la lista de cadenas en una lista de números.
lista(mapa(eval,lista))
3. Invierta todos los elementos de la lista.
res[::-1]
4. Intercambia dos números de la lista.
res[i],res[j] = res[j],res[i] #No se requieren variables intermedias
5. Asigne una lista de longitud fija
G = [0]*(n 1) #Lista de longitud n 1
6. Encuentre el elemento más grande en el intervalo [i,j] en la lista de arr.
máx(arr[i:j 1])
7. Utilice este elemento para dividir la lista en orden.
left_l = orden[:idx] right_l = orden[idx 1:]
3. Bucle/juicio
1. Cuando solo necesita determinar el número de bucles y no necesita obtener el valor
para _ en rango (len (cola)):
2.Abreviatura de if...else
node.left = recur_func(left_l) si left_l else Ninguno
3. Ciclo inverso
para i en rango(len(postorder)-2, -1,-1)
4. Obtenga elementos y subíndices al mismo tiempo.
para i, v en enumerar (numeros):
5. Recorre varias listas al mismo tiempo y devuelve varios valores.
para net, opt, l_his en zip(redes, optimizadores, pérdidas_his):
Sin zip, solo se generará un valor cada vez
6. Recorre varias listas y devuelve un valor.
para etiqueta en ax.get_xticklabels() ax.get_yticklabels():
4. Mapeo
1. Convierta rápidamente estructuras transitables en asignaciones correspondientes a subíndices
índice = {elemento: i para i, elemento en enumerar (en orden)}
También se puede lograr usando list.index (x), pero la lista debe recorrerse cada vez y el tiempo es O (n). Lo anterior es O (1).
3. Métodos comúnmente utilizados
1. Viene con el sistema
0.Clasificación
1.Conversión de tipo
1.int(x)
1. Cuando el tipo de coma flotante se convierte a un tipo entero, la parte decimal se descarta directamente.
2.flotar(x)
3.cadena(x)
1.ordenado(núm)
1. Ordena los elementos especificados.
2.mapa (función, lista)
1. Aplicar la función del primer parámetro a cada elemento del segundo parámetro.
mapa(evaluación,lista)
3.len(i)
1. Obtén la longitud, que puede ser de cualquier tipo.
4.enumerar()
1. Combine un objeto de datos transitable (como una lista, tupla o cadena) en una secuencia de índice y enumere el subíndice de datos y los datos al mismo tiempo. Generalmente utilizado en bucles for: para i, elemento en enumerar (seq):
2.enumerar (secuencia, [inicio = 0]), el subíndice comienza desde 0 y devuelve el objeto de enumeración (enumeración)
5.tipo(x)
1. Determinar el tipo de variable x, aplicable a cualquier tipo de datos.
2. Si necesita utilizar el tipo de variable como condición en el juicio condicional, puede utilizar la función type() para realizar una comparación directa.
si tipo(n) == tipo(123):
2.Lista[]
1. Cuando se utiliza la pila
1. Empuje hacia la pila
pila.append()
2. Sal de la pila
pila.pop()
Pop el último elemento
3. Devuelve el elemento superior de la pila.
No hay ningún método de vista previa en la lista. Solo puede aparecer primero y luego agregar.
2. Cuando se usa en una cola
1. Únete al equipo
cola.append()
2.Dejar el equipo
cola.pop(0)
Quitar de cola el primer elemento
3. Obtener el subíndice del elemento
m_i = números.index(max_v)
3. Deque de cola
1. La cola de dos extremos se utiliza como cola.
1. Únete al equipo
cola.append()
2.Dejar el equipo
cola.popleft()
4. Errores/diferencias comunes
1. Formato de pregunta
1.IndentationError: se esperaba un bloque sangrado
Hay un problema con la sangría. El error más común es mezclar las teclas Tab y Espacio para lograr la sangría del código.
Otra razón común para el error anterior es que no hay sangría en la primera línea. Por ejemplo, al escribir una declaración if, agregue dos puntos después. Si cambia directamente la línea, muchos editores de código sangrarán automáticamente la primera línea. Sin embargo, es posible que algunos editores de código no tengan esta función. En este caso, es mejor desarrollar el hábito de sangrar manualmente. No presione la barra espaciadora varias veces seguidas. Se recomienda presionar simplemente la tecla Tab.
1.problema de pycharm
1. Cómo deshacerse del mensaje de que el certificado del servidor no es de confianza en pycharm
Haga clic en Archivo > Configuración > Herramientas > Certificados de servidor > Aceptar certificados no confiables automáticamente
1.habilidades de pycharm
1. Importar archivos Python al proyecto actual
Copie directamente el archivo correspondiente en el administrador de archivos al directorio correspondiente al proyecto actual y aparecerá directamente en pycharm. Si el problema de la versión no ocurre, contraiga usted mismo el directorio del proyecto actual y luego expándalo nuevamente.
2. Error de operación
0. Para ver el tipo de error, asegúrese de mirar el error en la línea encima de la última línea divisoria.
Otros errores
1."ningún módulo llamado XX"
A medida que el nivel de desarrollo de todos mejora y la complejidad del programa aumenta, se utilizarán cada vez más módulos y bibliotecas de terceros en el programa. El motivo de este error es que la biblioteca "XX" no está instalada, pip install ww.
1.error
1.Error: error en el comando con el estado de salida 1
Descargue el paquete de instalación de terceros correspondiente e ingrese al directorio de descarga para instalar el nombre completo del archivo descargado.
2.error: se requiere Microsoft Visual C 14.0
Instale Microsoft Visual C 14.0, el blog tiene la dirección de descarga visualcppbuildtools_full
3.error: comando no válido 'bdist_wheel'
rueda de instalación pip3
2.Error de atributo error de atributo
0. Solución general
Si le pregunta qué archivo de módulo tiene un error, busque este módulo, elimínelo y reemplácelo con el mismo archivo de un amigo que pueda ejecutarse.
1.AttributeError: el módulo 'xxx' no tiene el atributo 'xxx'
1. El nombre del archivo entra en conflicto con las palabras clave propias de Python y similares.
2. Dos archivos py se importan entre sí, lo que provoca que uno de ellos se elimine.
2.AttributeError: el módulo 'seis' no tiene el atributo 'principal'
El problema de la versión 10.0 de pip no tiene main(), Es necesario degradar la versión: python -m pip install –upgrade pip==9.0.1
Algunas API cambiaron después de Pip v10, lo que resultó en incompatibilidad entre las versiones antiguas y nuevas, lo que afecta nuestra instalación y actualización de paquetes. ¡Simplemente actualice el IDE y listo! Al actualizar, el IDE también nos dio indicaciones amigables. PyCharm -> Ayuda -> Buscar actualizaciones
No actualice la versión descifrada; de lo contrario, la información de activación dejará de ser válida.
3.AttributeError: el módulo 'async io' no tiene el atributo 'ejecutar'
Nombraste un archivo asyncio py Si la verificación no es la primera, debe verificar su versión de Python porque python3.7 y versiones posteriores solo admiten el método de ejecución. 1 Actualizar la versión de Python 2 ejecución se reescribe de la siguiente manera bucle = asyncio.get_event_loop() resultado = loop.run_until_complete(coro)
4.AttributeError: el módulo 'asyncio.constants' no tiene el atributo '_SendfileMode'
Reemplazar archivos de constantes en asyncio
3.Error de tipo
1. "TypeError: el objeto 'tupla' no se puede interpretar como un número entero"
t=('a','b','c') para i en el rango (t):
Problema típico de error de tipo En el código anterior, la función range() espera que el parámetro entrante sea un número entero (entero), pero el parámetro entrante es una tupla (tupla). La solución es cambiar el parámetro de entrada tupla t a The. El número de tuplas puede ser de tipo entero len(t). Por ejemplo, cambie rango(t) en el código anterior a rango(len(t)).
2. "TypeError: el objeto 'str' no admite la asignación de elementos"
Causado al intentar modificar el valor de una cadena, que es un tipo de datos inmutable
s[3] = 'a' se convierte en s = s[:3] 'a' s[4:]
3. "TypeError: no se puede convertir el objeto 'int' a str implícitamente"
Causado por intentar concatenar un valor que no es una cadena con una cadena, simplemente convierta el valor que no es una cadena en una cadena con str()
4. "TypeError: tipo no comparable: 'lista'
Cuando se utiliza la función set para construir un conjunto, los elementos de la lista de parámetros dada no pueden contener listas como parámetros.
5.TypeError: no se puede utilizar un patrón de cadena en un objeto similar a bytes
Al cambiar entre python2 y python3, inevitablemente encontrará algunos problemas. Las cadenas Unicode en python3 están en el formato predeterminado (tipo str) y las cadenas codificadas en ASCII (tipo bytes) contienen valores de bytes y en realidad no lo son. un carácter. String, python3 y tipo de matriz de bytes bytearray) debe estar precedido por el operador b o B, en python2, es lo contrario, la cadena codificada en ASCII es la predeterminada y la cadena Unicode debe estar precedida por el operador u o U;
import chardet #Necesita importar este módulo y detectar el formato de codificación tipo_codificación = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica en consecuencia y asígnalo al identificador original (variable)
4.IOError
1. "IOError: Archivo no abierto para escritura"
>>> f=abrir ("hola.py") >>> f.escribir ("prueba")
La causa del error es que el modo de parámetro del modo lectura-escritura no se agrega a los parámetros entrantes de open ("hello.py"), lo que significa que la forma predeterminada de abrir el archivo es de solo lectura.
La solución es cambiar el modo modo a permiso de modo de escritura w, f = open("hello. py", "w ")
5.Error de sintaxis Errores gramaticales
1.SyntaxError: sintaxis no válida”
Causado por olvidar agregar dos puntos al final de declaraciones como if, elif, else, for, while, class y def.
Usar incorrectamente "=" en lugar de "==" En los programas Python, "=" es un operador de asignación y "==" es una operación de comparación igual.
6.UnicodeDecodeError Error de interpretación de codificación
1. El códec 'gbk' no puede decodificar el byte
La mayoría de las veces esto se debe a que el archivo no está codificado en UTF8 (por ejemplo, puede estar codificado en GBK) y el sistema utiliza la decodificación UTF8 de forma predeterminada. La solución es cambiar al método de decodificación correspondiente: con open('acl-metadata.txt','rb') como datos:
abrir (ruta, '-modo-', codificación = 'UTF-8'), es decir, abrir (nombre del archivo de ruta, modo de lectura-escritura, codificación)
Modos de lectura y escritura más utilizados
7.Error de valor
1.demasiados valores para descomprimir
Al llamar a una función, no hay suficientes variables para aceptar el valor de retorno.
8.OSError
1.WinError 1455] El archivo de página es demasiado pequeño y la operación no se puede completar.
1. Reinicie pycharm (básicamente inútil)
2. Establezca num_works en 0 (quizás inútil)
3. Aumente el tamaño del archivo de página (resuelva completamente el problema)
9.Error de importación
1.Error al cargar la DLL: el archivo de página es demasiado pequeño y la operación no se puede completar.
1. No solo se está ejecutando un proyecto, sino que también se está ejecutando el programa Python de otro proyecto, simplemente apáguelo.
2. El sistema operativo Windows no admite la operación multiproceso de Python. La red neuronal utiliza múltiples procesos en la carga de conjuntos de datos, así que establezca el parámetro num_workers en DataLoader en 0.
1.Error al cargar la DLL: el sistema operativo no puede ejecutar %1
0. Recientemente, cuando estaba ejecutando un proyecto scrapy, el marco scrapy que se instaló de repente informó un error y me tomó por sorpresa.
1. Debido a que no es difícil instalar scrapy en Anaconda, no es tan simple y eficiente como reinstalar para encontrar una solución.
2. No puedo desinstalarlo por completo simplemente usando el comando conda remove scrapy. La razón puede ser que instalé scrapy dos veces usando pip y conda respectivamente. Los lectores pueden probar los comandos de desinstalación pip y conda.
pip desinstalar scrapy conda eliminar scrapy
Reinstale pip install scrapy
error de clase
1. Cuestiones patrimoniales de herencia múltiple de clases.
Solo modificamos A.x, ¿por qué también se modificó C.x? En los programas Python, las variables de clase se tratan internamente como diccionarios, que siguen el orden de resolución de métodos (MRO) comúnmente citado. Entonces, en el código anterior, dado que no se encuentra el atributo x en la clase C, buscará su clase base (aunque Python admite la herencia múltiple, solo hay A en el ejemplo anterior). En otras palabras, la clase C no tiene su propio atributo x, que es independiente de A. Por lo tanto, C.x es en realidad una referencia a A.x.
error de alcance
1. Variables globales y variables locales
1. La variable local x no tiene valor inicial y la variable externa X no se puede introducir internamente.
2. Diferentes operaciones en listas
Fool no asignó un valor a lst, pero Fool2 sí lo hizo. Ya sabes, lst = [5] es la abreviatura de lst = lst [5], y estamos intentando asignar un valor a lst (Python lo trata como una variable local). Además, nuestra asignación a lst se basa en el propio lst (que Python vuelve a tratarlo como una variable local), pero aún no se ha definido, por lo que se produce un error. Entonces aquí debemos distinguir entre el uso de variables locales y variables externas.
2.1 Error de actualización de Python2 a Python3
1.print se convierte en print()
1. En la versión Python 2, imprimir se usa como una declaración. En la versión Python 3, imprimir aparece como una función. En la versión Python 3, todo el contenido impreso debe estar entre paréntesis.
2.raw_Input se convierte en entrada
En la versión Python 2, la funcionalidad de entrada se implementa a través de raw_input. En la versión Python 3, se implementa mediante entrada
3. Problemas con números enteros y división
1. "TypeError: el objeto 'flotante* no se puede interpretar como un número entero"
2. En Python 3, int y long están unificados en el tipo int. Int representa un número entero de cualquier precisión. El tipo largo ha desaparecido en Python 3 y el sufijo L también ha quedado obsoleto cuando el uso de int excede el tamaño del entero local. , no provocará la excepción OverflowError
3. En versiones anteriores de Python 2, si el parámetro es int o long, se devolverá el resultado redondeado hacia abajo (piso) de la división, y si el parámetro es flotante o complejo, se devolverá el resultado equivalente, una buena aproximación. del resultado después de la división
4. "/" en Python 3 siempre devuelve un número de punto flotante, lo que siempre significa división hacia abajo. Simplemente cambie "/" a "//" para obtener el resultado de la división entera.
4. Actualización importante del manejo de excepciones.
1. En los programas Python 2, el formato para detectar excepciones es el siguiente: excepto excepción, identificador
excepto ValueError, e: # Python 2 maneja excepciones únicas excepto (ValueError, TypeError), e: # Python 2 maneja múltiples excepciones
2. En los programas Python 3, el formato para detectar excepciones es el siguiente: excepto excepción como identificador
excepto ValueError como e: # Python3 maneja una única excepción excepto (ValueError, TypeError) como e: # Python3 maneja múltiples excepciones
3. En los programas Python 2, el formato para generar excepciones es el siguiente: plantear excepción, argumentos
4. En los programas Python 3, el formato para generar excepciones es el siguiente: plantear excepción (args)
aumentar ValueError, e # método Python 2.x aumentar ValueError(e) # método Python 3.x
5.xrange() se convierte en rango()
"NameError: el nombre 'xrange' no está definido"
6. La recarga no se puede utilizar directamente
"el nombre 'recargar' no está definido y AttributeError: el módulo 'sys' no tiene att"
importar importarlib importarlib.recargar (sys)
7. No más tipos Unicode
"Python Unicode no está definido"
En Python 3, el tipo Unicode ya no existe y se reemplaza por el nuevo tipo str. El tipo str original en Python 2 fue reemplazado por bytes en Python 3
8.has_key ha sido abandonado
"AttributeError: el objeto 'dieta' no tiene el atributo 'has_key' "
has_key ha sido abandonado en Python 3. El método de modificación es utilizar en en lugar de has_key.
9.urllib2 ha sido reemplazado por urllib.request
"lmportError: No hay ningún módulo llamado urllib2"
La solución es modificar urllib2 a urllib.request
10. Problemas de codificación
TypeError: no se puede utilizar un patrón de cadena en un objeto similar a bytes
Al cambiar entre python2 y python3, inevitablemente encontrará algunos problemas. Las cadenas Unicode en python3 están en el formato predeterminado (tipo str) y las cadenas codificadas en ASCII (tipo bytes) contienen valores de bytes y en realidad no lo son. un carácter. String, python3 y tipo de matriz de bytes bytearray) debe estar precedido por el operador b o B, en python2, es lo contrario, la cadena codificada en ASCII es la predeterminada y la cadena Unicode debe estar precedida por el operador u o U;
import chardet #Necesita importar este módulo y detectar el formato de codificación tipo_codificación = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica en consecuencia y asígnalo al identificador original (variable)
2.1 Comando de línea del símbolo del sistema
1. Directorio de operaciones
1.cd cambia el subdirectorio actual, puede copiar directamente la ruta e ingresarla de una vez
2. El comando CD no puede cambiar el disco actual. CD... regresa al directorio anterior. CD\ significa regresar al directorio del disco actual. Cuando el CD no tiene parámetros, se muestra el nombre del directorio actual.
3.d: cambiar la ubicación del disco
2.Relacionado con Python
1.pip
0.Obtén ayuda
ayuda pipa
0. Cambiar fuente de pips
1. Uso temporal
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nombre del paquete
2. Establecer como predeterminado
conjunto de configuración de pip global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Después de configurarlo como predeterminado, las bibliotecas de instalación futuras se descargarán desde la fuente de Tsinghua y no es necesario agregar la URL de la fuente reflejada.
3. Dirección de origen espejo convencional
1. Instale la biblioteca especificada
nombre del paquete de instalación de pip
2. Instale la versión especificada de la biblioteca especificada.
instalación de pip nombre_paquete==1.1.2
3. Verifique qué bibliotecas están instaladas
lista de pipas
4. Ver la información específica de la biblioteca.
pip show -f nombre del paquete
5. ¿Dónde está instalado pip?
pipa -V
6. Biblioteca de instalación por lotes
instalación de pip -r d:\\requisitos.txt Escriba directamente el nombre del paquete == número de versión en el archivo
7. Instale la biblioteca usando el archivo wheel.
1. Busque el archivo .whl de la biblioteca correspondiente en el sitio web a continuación, busque con Ctrl F y preste atención a la versión correspondiente. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. En la carpeta donde se encuentra .whl, presione la tecla Shift y haga clic con el botón derecho del mouse para abrir la ventana CMD o PowerShell. (O ingrese a esta carpeta a través de la línea de comando)
3. Ingrese el comando: instalación de pip matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Desinstalar la biblioteca
pip desinstalación nombre_paquete
9.Actualizar biblioteca
instalación de pip --actualizar nombre_paquete
10. Guarde la lista de bibliotecas en el archivo especificado.
congelación de pip > nombre de archivo.txt
11. Verifique las bibliotecas que deben actualizarse
lista de pips -o
12. Verifique si hay problemas de compatibilidad
Verifique si la biblioteca instalada tiene dependencias compatibles pip check nombre-paquete
13. Descargue la biblioteca a local.
Descargue la biblioteca en un archivo local especificado y guárdela en formato whl pip descargar nombre_paquete -d "Ruta del archivo para guardar"
2. Empaquetar el programa en un archivo ejecutable.
pyinstaller -F nombre de archivo.py
3. Escritura de código
1. Sin formato
1. Sin i, sólo se puede escribir como i =1
2. Diferentes formatos
1. clase
1. Parámetros
0. Al definir una clase, el primer parámetro debe ser self y lo mismo se aplica a todos los métodos. Al llamar a miembros de esta clase, se deben usar self.
1. Para los parámetros de la clase, primero escriba el nombre de la variable, agregue dos puntos y luego escriba el nombre del tipo, separado por comas.
2. Una vez completada la definición de la clase, puede agregar -> escriba el nombre al final para indicar el tipo de valor de retorno.
2. Llamar
1. Para llamarse a sí mismo para recursividad en una clase, debe usar la función self.self (no necesita agregar self en los parámetros)
2. Cuando se usa una clase, no es necesario crear una nueva clase, solo úsela directamente root = TreeNode(max_num)
3.Método
1._Comience con un guión bajo para definir el método de protección
2.__Comience con dos guiones bajos para definir métodos privados
subtema
2. Números
1. Expresión de más y menos infinito
flotante("inf"), flotante("-inf")
3. Símbolos
1. En Python / significa división normal con resto, // significa división entera sin resto.
2. ¡No! en Python está representado por no
3.El cuadrado en Python es **
4. Declaración
1. Se deben agregar dos puntos después de todas las declaraciones relacionadas con palabras clave: excepto retorno
2. No es necesario agregar () a las condiciones en bucles, juicios y otras declaraciones similares, y no hay {} en los bloques de declaraciones. Utilice sangría para representar estrictamente el formato.
5. Comentarios
1. Comentario de una sola línea #
2. Comentarios de varias líneas ''' o """
4. Códigos diferentes
1. Lista
1. Cuando necesite usar subíndices de lista explícitamente, debe usar G = [0]*(n 1) # para crear una lista con una longitud de n 1; de lo contrario, el subíndice estará fuera de los límites.
2. Al crear una lista bidimensional de longitud fija, si *n falla, intente usar un bucle
dp = [[float('inf') para _ en rango(n)] para _ en rango(n)]
3. Si el valor de retorno de la función es una lista, pero solo se recibe una variable, agregue una coma
línea, = ax.plot(x, np.sin(x))
5.Programación en Python
1. Problema de archivo
1. Al importar otros proyectos que requieran un archivo, utilice la ruta absoluta del archivo.
2. Archivo de ejecución de línea de comando
Nombre de archivo Python.py
3. Recursos espejo de paquetes de terceros
Cuando utilice pycharm para descargar paquetes de terceros, agréguelo en Administrar repositorios http://mirrors.aliyun.com/pypi/simple/, elimine la URL original
6.Teclas de acceso directo de Pycharm
1. Comentar (añadir/eliminar)
Control /
Comentarios de una sola línea#
2.Código de desplazamiento a la derecha
Pestaña
3. Código de desplazamiento a la izquierda
Pestaña Mayús
4. Sangría automática
Ctrl-alt I
5. correr
Ctrl mayús F10
6.Formato estándar PEP8
Ctrl-alt L
También es la tecla de acceso directo de bloqueo de QQ. Cancela la configuración en QQ.
6.1 Solución rápida
alt enter y presione enter
7. Copie una línea/múltiples líneas/parte seleccionada
CtrlD
8. Eliminar una o varias líneas
Ctrl Y
9.Encontrar
CtrlF
9.1 Búsqueda global
Ctrl mayús F
10.Reemplazo
CtrlR
10.1 Reemplazo global
Ctrl mayús R
11.Mueva el cursor a la siguiente línea.
cambio Entrar
12.Clic del cursor de varias líneas
clic con el botón izquierdo del mouse
13. Saltar al siguiente punto de interrupción
Alt F9
14.Cancelación
Ctrl-Z
14.1 Anti-cancelación
Ctrl mayúscula Z
15. Copie el código de la clase principal.
Ctrl o
16. Seleccione bloque de palabra/código
Ctrl W
17. Ver documentos rápidamente (información del código)
Ctrl-Q
18. Inserta una línea hacia abajo en cualquier posición.
cambio entrar
19. Inserta una fila hacia arriba en cualquier posición.
Ctrl-alt-entrar
20. Ver vista del proyecto
alternativa 1
21. Ver la vista de estructura.
alternativa 7
22. Ingrese al código rápidamente
Ctrl clic izquierdo
23. Ver historial rápidamente
Tecla Alt izquierda (regreso)/derecha (adelante)
24. Ver rápidamente diferentes métodos
Alt arriba/abajo
25. Cambiar de vista
Pestaña Ctrl
26. Ver archivos de recursos
cambiar dos veces
27. Vea dónde se llama el método.
Ctrl alt H Doble clic para determinar la posición
28.Ver clase principal
Ctrl-U
29. Ver relación de herencia
Ctrl-H
7.página pycharm
0.Barra de menú
1.Ver ventana
1.Barra de navegación barra de navegación
2.Refactorización Refactorización
3.Herramientas
4.Control de versiones VCS
1. Depurar el código
1. Haga clic delante del código para insertar un punto de interrupción, haga clic en el rastreador para depurarlo y haga clic en el cuadro al lado del rastreador para finalizar la depuración.
2. Haga clic en el icono ↘ para saltar al siguiente punto de interrupción y podrá observar continuamente el valor de la variable.
2.configuración de configuración
1.apariencia y comportamiento interfaz y comportamiento
1.apariencia estilo general
1.Tema temático
1.Darcula tema negro
2. Tema de alto contraste y alto contraste.
3.Tema brillante Intellij
2.fuente personalizada fuente personalizada
2.configuración del sistemaconfiguración del sistema
1.actualizar
La detección automática de actualizaciones se puede desactivar
2.teclas de acceso directo del mapa de teclas
1. Según la vista del sistema, puede buscar directamente (nota de comentario)
3.editor solo edita el área
1. Fuente del código de fuente (tamaño ajustable)
2.Esquema de color Esquema de color del área del código
3. Estilo de código estilo de código
0.Python puede realizar cambios en cada detalle.
1.Python
1.Número de sangría
2.Configuración del espacio espacial
3.Envoltura y tirantes
1. Ajuste duro con el número máximo de códigos en una línea
4.Líneas en blanco líneas en blanco
4.Inspecciones
1.PEP 8 es un código estándar, no un error gramatical. Intente mantenerlo lo más estándar posible.
2. Puede establecer qué contenido se verifica y también puede establecer el rigor de la verificación en Gravedad.
5.Plantillas de archivos y códigos Plantillas de archivos y códigos
1. Agregue información en Python.Script que se mostrará cada vez que se cree un nuevo archivo. Descubra qué información puede agregar en línea
6.Codificación de archivos de codificaciones de archivos
1.UTF-8 predeterminado
7.Plantillas dinámicas Live Templates (fáciles de usar y dominar)
0. Puede hacer clic en el signo más para agregar la plantilla usted mismo y asegurarse de configurar la ubicación de uso.
1.para bucle
Escribe iter para seleccionar un bucle y sigue presionando Enter para escribir código
2. Utilice un bucle for para escribir una lista
Solo escribe completo
4.Complementos
5.Proyecto
1.Intérprete del proyecto Project Interpret
1. Puede administrar y agregar bibliotecas de terceros (haga clic en el signo más y busque)
2. Se pueden configurar diferentes intérpretes para el proyecto actual.
3.Gestión de proyectos de proyectos.
1. Haga clic derecho en el archivo y seleccione Mostrar en el Explorador para abrir directamente la ubicación del archivo.
2.Nuevo
1.Archivo
Pueden ser varios otros archivos, no necesariamente archivos Python.
2.Nuevo archivo borrador
Crear archivos temporales, equivalentes a papel borrador, utilizados para probar parte del código.
3.Directorio directorio
Crear una nueva carpeta de nivel inferior
4.Archivo Python
En comparación con las carpetas normales, hay más archivos de inicialización de Python vacíos.
5. Al crear un nuevo archivo HTML, puede hacer clic derecho en él y abrirlo en un navegador para ver el efecto de visualización.
4. Página de resultados del código
1.terminal terminal
1. Es lo mismo que el CMD del sistema. Puede instalar el paquete directamente aquí y usar el comando dos.
2.Consola de consola Python
Puede escribir código Python directamente, ejecutarlo de forma interactiva y editarlo línea por línea.
3.TODO
Es equivalente a una nota. Escriba TODO ('') en el punto del código, para que pueda encontrarlo rápidamente y continuar trabajando. Puede usarse para la cooperación mutua.
4. El avatar de la extrema derecha.
El rigor de la verificación del código se puede ajustar. El modo de ahorro de energía equivale a colocar la flecha en el extremo izquierdo. No se verificarán todos los errores gramaticales.
5.Entorno virtual
1.
2. El entorno virtual se crea porque en el desarrollo real es necesario utilizar diferentes versiones del intérprete de Python y diferentes versiones de la misma biblioteca al mismo tiempo. Por lo tanto, es necesario crear un entorno virtual para aislar el entorno del proyecto de otros entornos (entorno del sistema, otros entornos virtuales)
3. Hay tres formas de crear un entorno virtual en PyCharm: virtualen, conda y pipen.
4. Se puede imaginar que Virtualen crea una copia aislada del entorno del sistema actual. El intérprete utilizado es el mismo que instaló (copia).
5. Conda selecciona una versión de Python específica según sus necesidades, luego descarga la versión relevante de Internet y crea un nuevo entorno que es diferente del entorno del sistema. El intérprete utilizado también es diferente del que instaló.
6. Pipen es similar a virtualen. También crea una copia basada en el entorno del sistema existente, pero pipen usa Pipfile en lugar del request.txt de virtualen para la gestión de dependencias, lo cual es más conveniente.
6.Interrelación
7.SVN
1. Al descargar desde el sitio web oficial, seleccione la versión 1.10 en inglés, que es diferente de la versión china.
2. Al instalar TortoiseSVN.msi, asegúrese de verificar las herramientas de línea de comando
3. Aparecerá una serie de archivos svn.exe en el directorio bin instalado.
4. Configure en pycharm y busque el archivo svn.exe en el directorio bin en configuración/control de versiones/subversión.
5. Haga clic derecho en el archivo modificado en la carpeta para ver las modificaciones.
6. Haga clic derecho en el proyecto y aparecerá un nuevo acceso directo de subversión. Puede enviar confirmaciones y deshacer todos los cambios revertidos.
8. Complementos fáciles de usar
5. Fragmentos de código comunes
1.Entrada
1. Obtener entrada del usuario de longitud variable
def getNum(): #Obtener entrada del usuario de longitud variable números = [] iNumStr = input("Ingrese un número (presione Enter para salir): ") mientras iNumStr != "": números.append(eval(iNumStr)) iNumStr = input("Ingrese un número (presione Enter para salir): ") devolver números
2.Texto
1. Normalización y eliminación de ruido del texto en inglés.
def obtenerTexto(): txt = abrir("hamlet.txt", "r").read() txt = txt.lower() #Convertir todo a letras minúsculas para ch en '!"#$%&()* ,-./:;<=>?@[\\]^_'{|}~': txt = txt.replace(ch, " ") #Reemplazar caracteres especiales en el texto con espacios devolver texto
2. Cuente la frecuencia de las palabras en el texto en inglés.
hamletTxt = obtenerTexto() palabras = hamletTxt.split() #Separar texto con espacios y convertirlo en una lista counts = {} #Crear un nuevo diccionario para palabra en palabras: #Cuente la frecuencia de cada palabra, el valor predeterminado es 0 cuenta[palabra] = cuenta.get(palabra,0) 1 items = list(counts.items()) #Convertir diccionario en lista items.sort(key=lambda x:x[1], reverse=True)# Ordena el segundo elemento en orden inverso para i en el rango (20): palabra, recuento = elementos[i] imprimir ("{0:<10}{1:>5}".formato(palabra, recuento))
3. Estadísticas de frecuencia de palabras en textos chinos
importar jieba txt = open("tresreinos.txt", "r", codificación='utf-8').read() palabras = jieba.lcut(txt) cuenta = {} para palabra en palabras: si len(palabra) == 1: continuar demás: cuenta[palabra] = cuenta.get(palabra,0) 1 items = list(counts.items()) #Convertir a lista para ordenar items.sort(clave=lambda x:x[1], reverso=Verdadero) para i en el rango(15): palabra, recuento = elementos[i] imprimir ("{0:<10}{1:>5}".formato(palabra, recuento))
3.matriz
1. Encuentra el valor máximo en la matriz.
1. Los elementos se repiten.
max_v, m_i = flotante(-inf), 0 #Combinar un objeto de datos transitable (como una lista, tupla o cadena) en una secuencia de índice, Enumerar subíndices de datos y datos al mismo tiempo. para i, v en enumerar (numeros): si v > max_v: máx_v = v m_i = yo
2. Sin elementos repetidos
max_v = max(núms) m_i = números.index(max_v)
2. Dicotomía
clase Solución: def searchInsert(self, nums: Lista[int], destino: int) -> int: izquierda, derecha = 0, len(nums) #Utilice el intervalo cerrado por la izquierda y abierto por la derecha [izquierda, derecha) while left < right: # Abierto a la derecha, por lo que no puede haber =, el intervalo no existe mid = izquierda (derecha - izquierda)//2 # Evita el desbordamiento, //Indica división de enteros if nums[mid] < target: # El punto medio es menor que el valor objetivo. En el lado derecho, se pueden obtener posiciones iguales. left = mid 1 # Izquierda cerrada, entonces 1 demás: right = mid # Abierto a la derecha, el punto final derecho real es mid-1 return left # Cuando finaliza este algoritmo, se garantiza que izquierda = derecha, y el retorno será el mismo para todos.
4.Matplotlib
1. Mueva las coordenadas a (0,0)
hacha = plt.gca() ax.spines['derecha'].set_color('ninguno') ax.spines['arriba'].set_color('ninguno') ax.xaxis.set_ticks_position('abajo') ax.spines['bottom'].set_position(('datos', 0)) ax.yaxis.set_ticks_position('izquierda') ax.spines['izquierda'].set_position(('datos', 0))
matemáticas
1. Calcula el promedio
def media(números): #Calcular el promedio s = 0,0 para num en números: s = s número devolver s/len(números)
2. Calcula la varianza
def dev(números, media): #Calcular varianza desv = 0,0 para num en números: desv = desv (núm - media)**2 devolver pow(sdev / (len(números)-1), 0,5)
3. Calcula la mediana
def mediana(números): #Calcular la mediana ordenados (números) tamaño = longitud (números) si tamaño % 2 == 0: med = (números[tamaño//2-1] números[tamaño//2])/2 demás: med = números[tamaño//2] devolver medicina
6. El código se ejecuta localmente
De hecho, simplemente defina una función principal, construya un caso de uso de entrada, luego defina una variable de solución y llame a la función minCostClimbingStairs.
0. Habilidades de escritura de códigos
1.Formato
1. Quiere escribir varias líneas en una sola.
Agrega un punto y coma al final de cada línea;
2. Una línea es demasiado larga y quiero incluirla en una nueva línea.
Simplemente agregue una barra diagonal derecha al final de esta línea
Relacionado con el algoritmo
pensamiento clásico
0.Problemas encontrados con frecuencia
1. Prevenir el desbordamiento
1. Al calcular el producto de muchos números, para evitar el desbordamiento, puedes tomar el logaritmo del producto y cambiarlo a la forma de suma.
1.Estructura de datos
1.matriz
0.Básico
1. Siempre que vea que la matriz proporcionada en la pregunta de la entrevista es una matriz ordenada, puede pensar si puede utilizar el método de dicotomía.
2. Los elementos de la matriz son continuos en la dirección de memoria. Un elemento de la matriz no se puede eliminar individualmente, solo se puede sobrescribir.
1. El mismo elemento de la matriz no se puede atravesar dos veces.
para (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) {
2. Problema de matriz circular
Cuando existe la restricción de que la cabeza y la cola no pueden estar al mismo tiempo, descomponga la matriz circular en varios problemas de matriz ordinarios y encuentre el valor máximo
3. Problema del intervalo de dicotomía
1. Cerrado a la izquierda y cerrado a la derecha [izquierda, derecha]
int middle = left ((right - left) / 2);// evita el desbordamiento, equivalente a (left right)/2
while (izquierda <= derecha) { // Cuando izquierda == derecha, el intervalo [izquierda, derecha] sigue siendo válido
if (numeros[medio] > objetivo) { derecha = medio - 1; // el objetivo está en el rango izquierdo, entonces [izquierda, medio - 1]
} else if (nums[medio] <objetivo) { izquierda = medio 1; // el objetivo está en el rango derecho, entonces [medio 1, derecha]
2. Cerrar a la izquierda y abrir a la derecha [izquierda, derecha)
while (izquierda < derecha) { // Porque cuando izquierda == derecha, [izquierda, derecha) es un espacio no válido
if (numeros[medio] > objetivo) { derecha = medio; // el objetivo está en el intervalo izquierdo, en [izquierda, medio)
} else if (nums[medio] <objetivo) { izquierda = medio 1; // el objetivo está en el intervalo derecho, en [medio 1, derecha)
2. Tabla hash
0. Siempre que implique contar el número de apariciones de un determinado número/valor, utilice una tabla hash
1. La tabla hash contiene el número correspondiente a un determinado número y no es en sí misma.
para (int i = 0; i < nums.length; i ) { int complemento = objetivo - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i)
3. Lista enlazada
1. Suma los números en dos listas enlazadas.
1. En caso de inversión del orden: se deberá considerar por separado el posible problema de carry de la última incorporación.
2. En secuencia directa: invierta la lista vinculada/use la estructura de datos de la pila para lograr la inversión
2. Encuentre la mediana de una lista enlazada individualmente ordenada (cerrada a la izquierda y abierta a la derecha)
Supongamos que el extremo izquierdo de la lista vinculada actual es el izquierdo, el extremo derecho es el derecho y la relación de inclusión es "cerrada a la izquierda, abierta a la derecha". La lista vinculada dada es una lista vinculada unidireccional. elementos posteriores, pero no puede acceder directamente a los elementos predecesores. Por lo tanto, después de encontrar el nodo mediano en el medio de la lista vinculada, si establece la relación de "cerrado a la izquierda, abierto a la derecha", puede usar directamente (izquierda, medio) y (medio.siguiente, derecha) para representar la lista correspondiente a los subárboles izquierdo y derecho no necesitan mid.pre, y la lista inicial también se puede representar convenientemente mediante (head, null)
4.Personajes
1. Registre si aparece cada personaje.
Conjunto de hash: Set<Character> occ = new HashSet<Character>();
5. Números
1. Llevar adquisición para sumar dos números de un solo dígito
int suma = llevar x y; int llevar = suma/10;
2. Inversión de enteros
Para "sacar" y "empujar" números sin la ayuda de una pila/matriz auxiliar, podemos usar las matemáticas, sacar primero el último dígito, luego dividir por 10 para eliminar el último dígito, invertir el número y seguir multiplicándose después de 10. , agregue el último dígito extraído y primero determine si se desbordará.
6.árbol
1. Encuentra nodos precursores
Da un paso hacia la izquierda y luego sigue caminando hacia la derecha hasta que no puedas avanzar más.
predecesor = raíz.izquierda; while (predecesor.derecho! = nulo && predecesor.derecho! = raíz) { predecesor = predecesor.derecho; }
7. Colección
1. Eliminar elementos duplicados de la lista.
s = conjunto(ls); lt = lista(s)
8. tupla
1. Si no desea que el programa cambie los datos, conviértalos a un tipo de tupla
lt = tupla(ls)
2. Algoritmo clásico
1.Doble puntero
0. Comúnmente utilizado en matrices y listas vinculadas.
1. Cuando necesite enumerar dos elementos en una matriz, si descubre que a medida que el primer elemento aumenta, el segundo elemento disminuye, puede usar el método de doble puntero para mover el segundo puntero desde el final de la matriz. Comience a recorrer mientras asegurando que el segundo puntero sea mayor que el primer puntero, reduciendo la complejidad temporal de la enumeración de O(N^2) a O(N)
2. Cuando el resultado de la búsqueda tiene un rango determinado, los punteros dobles se utilizan para cambiar continuamente, similar al mecanismo de ventana deslizante.
2. Método de puntero rápido y lento
Inicialmente, tanto el puntero rápido como el puntero lento apuntan al punto final izquierdo de la lista vinculada. Mientras movemos el puntero rápido hacia la derecha dos veces, movemos el puntero lento hacia la derecha una vez hasta que el puntero rápido alcanza el límite (es decir, el puntero rápido alcanza el punto final derecho o el siguiente nodo del puntero rápido es el derecho punto final). En este momento, el elemento correspondiente al puntero lento es la mediana
3. Programación dinámica
1. El número requerido se puede obtener mediante alguna operación a través del número requerido anterior, y se puede encontrar la ecuación de transferencia dinámica.
2.Condiciones
Si un problema tiene muchos subproblemas superpuestos, la programación dinámica es más efectiva.
3. Cinco pasos
1. Determinar el significado de la matriz dp (tabla dp) y los subíndices 2. Determinar la fórmula de recursividad. 3.Cómo inicializar la matriz dp 4. Determinar el orden transversal. 5. Derivación de la matriz dp con ejemplos.
¿Por qué determinar primero la fórmula de recursividad y luego considerar la inicialización? Porque en algunos casos la fórmula recursiva determina cómo inicializar la matriz dp
4.Cómo depurar
1. Imprima la matriz dp y vea si se deduce según sus propias ideas.
2. Antes de escribir código, asegúrese de simular la situación específica de la transferencia de estado en la matriz dp y asegúrese de que el resultado final sea el resultado deseado.
5.Matriz de desplazamiento
Cuando la ecuación recursiva solo está relacionada con unos pocos números adyacentes, se puede usar una matriz móvil para optimizar la complejidad del espacio a O (1)
4. recursividad
0. Trilogía recursiva
Condición de terminación de la recursividad, qué hace esta recursividad y qué devuelve
3. Técnicas comúnmente utilizadas
1. Uso inteligente de subíndices de matriz
1.Aplicación
El subíndice de una matriz es una matriz implícitamente útil, especialmente cuando se cuentan algunos números (tratando el valor de la matriz correspondiente como el subíndice temp[arr[i]] de la nueva matriz), o cuando se determina si aparecen algunos números enteros.
2.Ejemplos
1. Cuando le damos una cadena de letras y le pedimos que determine la cantidad de veces que aparecen estas letras, podemos usar estas letras como subíndices al atravesar, si se atraviesa la letra a, entonces arr [a] se puede aumentar en. 1. Es decir, arr[a]. Mediante este uso inteligente de subíndices, no necesitamos juzgar letra por letra.
2. Se le proporcionan n matrices de enteros int desordenados, y el rango de valores de estos números enteros está entre 0 y 20. Debe ordenar estos n números de pequeño a grande en una complejidad de tiempo O (n). orden y use el valor correspondiente como subíndice de la matriz. Si este número ha aparecido antes, agregue 1 a la matriz correspondiente.
2. Usa el resto con habilidad
1.Aplicación
Al atravesar la matriz, se realizará un juicio fuera de límites. Si el subíndice está casi fuera de los límites, lo estableceremos en 0 y lo recorreremos nuevamente. Especialmente en algunas matrices en forma de anillo, como colas implementadas con matrices pos = (pos 1) % N
3. Utilice los punteros dobles con habilidad
1.Aplicación
Para punteros dobles, es particularmente útil cuando se hacen preguntas sobre listas enlazadas individualmente.
2.Ejemplos
1. Determinar si una lista enlazada individualmente tiene un ciclo
Configure un puntero lento y un puntero rápido para recorrer la lista vinculada. El puntero lento mueve un nodo a la vez, mientras que el puntero rápido mueve dos nodos a la vez. Si la lista vinculada no tiene un ciclo, el puntero rápido atravesará la lista primero. Si hay un ciclo, el puntero rápido lo hará. encuentre el puntero lento durante el segundo recorrido.
2. Cómo encontrar el nodo medio de la lista vinculada en un recorrido
Lo mismo es configurar un puntero rápido y un puntero lento. El lento mueve un nodo a la vez, mientras que el rápido mueve dos. Al atravesar la lista vinculada, cuando se completa el recorrido rápido del puntero, el puntero lento apenas llega al punto medio
3. El k-ésimo nodo del último en una lista enlazada individualmente
Establezca dos punteros, uno de los cuales mueve k nodos primero. Después de eso, ambos punteros se mueven a la misma velocidad. Cuando el primer puntero movido completa el recorrido, el segundo puntero está exactamente en el k-ésimo nodo desde abajo.
subtema
4. Utilice las operaciones por turnos con habilidad
1.Aplicación
1. A veces, cuando realizamos operaciones de división o multiplicación, como n/2, n/4, n/8, podemos usar el método de cambio para realizar la operación. A través de la operación de cambio, la velocidad de ejecución será más rápida.
2. También existen algunas operaciones como & (y) y | (o), que también pueden acelerar la operación.
2.Ejemplos
1. Para determinar si un número es impar, será mucho más rápido utilizar la operación AND.
5. Establecer la posición centinela
1.Aplicación
1. En temas relacionados con listas vinculadas, a menudo configuramos un puntero principal, y este puntero principal no almacena ningún dato válido. Solo por conveniencia de operación, podemos llamar a este puntero principal bit centinela.
2. Al operar una matriz, también puede configurar un centinela, utilizando arr[0] como centinela.
2.Ejemplos
1. Cuando queremos eliminar el primer nodo, si no se establece un bit centinela, la operación será diferente a la operación de eliminar el segundo nodo. Pero hemos configurado un centinela, por lo que eliminar el primer nodo y eliminar el segundo nodo tienen la misma operación, sin realizar juicios adicionales. Por supuesto, lo mismo ocurre al insertar nodos.
2. Cuando desee juzgar si dos elementos adyacentes son iguales, establecer un centinela no le preocupará el problema transfronterizo. Puede directamente arr[i] == arr[i-1]. No tengas miedo de cruzar el límite cuando i = 0
6. Algunas optimizaciones relacionadas con la recursividad.
1. Considere la preservación del estado para problemas que pueden ser recursivos.
1. Cuando usamos la recursividad para resolver un problema, es fácil calcular repetidamente el mismo subproblema. En este momento, debemos considerar la preservación del estado para evitar cálculos repetidos.
2.Ejemplos
0. Una rana puede saltar 1 o 2 escalones a la vez. Descubre de cuántas maneras puede la rana saltar una escalera de n niveles.
1. Este problema se puede resolver fácilmente mediante recursividad. Supongamos que f(n) representa el número total de pasos para n pasos, entonces f(n) = f(n-1) f(n - 2)
2. La condición final de la recursividad es cuando 0 <= n <= 2, f (n) = n, es fácil escribir código recursivo
3. Sin embargo, para los problemas que se pueden resolver mediante recursividad, debemos considerar si hay muchos cálculos repetidos. Obviamente, para la recursividad de f(n) = f(n-1) f(n-2), hay muchos cálculos repetidos.
4. Esta vez tenemos que considerar la preservación del Estado. Por ejemplo, puede usar hashMap para guardar. Por supuesto, también puede usar una matriz. En este momento, puede usar subíndices de matriz como dijimos anteriormente. Cuando arr [n] = 0, significa que n no se ha calculado. Cuando arr [n]! = 0, significa que f (n) se ha calculado. En este momento, el valor calculado se puede devolver directamente.
5. De esta forma, se puede mejorar enormemente la eficiencia del algoritmo. Algunas personas también llaman a este tipo de conservación del estado método memo.
2. Piensa de abajo hacia arriba
1. Para problemas recursivos, generalmente recurrimos de arriba a abajo hasta que la recursividad llega al final y luego devolvemos el valor capa por capa.
2. Sin embargo, a veces, cuando n es relativamente grande, como cuando n = 10000, es necesario recurrir 10000 niveles hasta n <= 2 antes de devolver lentamente el resultado. Si n es demasiado grande, es posible que no haya espacio en la pila. suficiente
3. Para esta situación, podemos considerar un enfoque ascendente.
4. Este enfoque ascendente también se denomina recursividad.
3. Ventajas frente a otros idiomas
1.matriz
1. Los parámetros son matrices parciales.
En Python, los parámetros pueden devolver directamente parte de la matriz nums[:i]. No es necesario rediseñar un método para interceptar la matriz según el subíndice como en Java.
2. Obtenga elementos y subíndices al mismo tiempo.
para i, v en enumerar (numeros):
Estructuras/algoritmos de datos de uso común
1. Lista enlazada
2.Pila
1. Pila monótona
1.Definición
Una pila en la que los elementos de la pila aumentan o disminuyen monótonamente. Una pila monótona solo se puede operar en la parte superior de la pila.
2. Naturaleza
1. Los elementos de la pila monótona son monótonos.
2. Antes de agregar elementos a la pila, todos los elementos que destruyan la monotonicidad de la pila se eliminarán de la parte superior de la pila.
3. Utilice la pila monótona para encontrar el elemento y recorrer hacia la izquierda hasta el primer elemento que es más pequeño que él (pila incremental)/buscar el elemento y recorrer hacia la izquierda hasta el primer elemento que es más grande que él.
3. Cola
4.árbol
1.BST: árbol de búsqueda binaria árbol de clasificación binaria
1.Definición
1. Las claves de todos los nodos en el subárbol izquierdo son menores que las del nodo raíz.
2. Las palabras clave de todos los nodos en el subárbol derecho son mayores que las del nodo raíz.
3. Los subárboles izquierdo y derecho son cada uno de ellos un árbol de clasificación binario.
El recorrido en orden puede obtener una secuencia ordenada creciente
2.AVL: árbol binario equilibrado
1.Definición
1. Árbol binario equilibrado: el valor absoluto de la diferencia de altura entre los subárboles izquierdo y derecho de cualquier nodo no supera 1
2. Factor de equilibrio: la diferencia de altura entre los subárboles izquierdo y derecho del nodo -1,0,1
3.mct: árbol de expansión mínimo
1.Definición
Cualquier árbol que consta sólo de las aristas de G y contiene todos los vértices de G se llama árbol de expansión de G.
5. Figura
1. Terminología
1. Clúster (subgrafo completo)
Conjunto de puntos: Hay una arista que conecta dos puntos cualesquiera.
1.1 Conjunto independiente de puntos
Conjunto de puntos: no hay borde entre dos puntos.
2. Gráfico de Hamilton Hamilton
Un gráfico no dirigido va desde un punto inicial específico hasta un punto final específico, pasando por todos los demás nodos solo una vez. Un camino hamiltoniano cerrado se llama ciclo hamiltoniano y un camino que contiene todos los vértices del gráfico se llama camino hamiltoniano.
6. Algoritmo
1.BFS: búsqueda en amplitud
1.Definición
Un algoritmo transversal jerárquico similar a un árbol binario, que da prioridad al nodo descubierto más temprano.
2.DFS: búsqueda en profundidad primero
1.Definición
De manera similar al recorrido de un árbol en orden anticipado, se le da prioridad al último nodo descubierto.
sentido común
1. ¿Cuántas operaciones por segundo?
2. Complejidad temporal del algoritmo recursivo
Número de recursiones * Número de operaciones en cada recursión
3. Complejidad espacial del algoritmo recursivo.
Profundidad de recursividad * complejidad espacial de cada recursividad
puntos de conocimiento labuladuo
0.Serie de lectura obligada
1. Pensamiento marco para aprender algoritmos y resolver preguntas.
1. Método de almacenamiento de la estructura de datos.
1. Solo hay dos tipos: matriz (almacenamiento secuencial) y lista vinculada (almacenamiento vinculado)
2. También hay varias estructuras de datos, como tablas hash, pilas, colas, montones, árboles, gráficos, etc., que pertenecen a la "superestructura", mientras que las matrices y las listas vinculadas son la "base estructural". esas diversas estructuras de datos Sus fuentes son operaciones especiales en listas o matrices vinculadas, y las API son simplemente diferentes.
3. Introducción a diversas estructuras.
1. Las dos estructuras de datos, "cola" y "pila", se pueden implementar mediante listas enlazadas o matrices. Si lo implementa con una matriz, debe lidiar con el problema de expansión y contracción; si lo implementa con una lista vinculada, no tiene este problema, pero necesita más espacio de memoria para almacenar punteros de nodo.
2. Dos métodos de representación de "gráfico", la lista de adyacencia es una lista vinculada y la matriz de adyacencia es una matriz bidimensional. La matriz de adyacencia determina la conectividad rápidamente y puede realizar operaciones matriciales para resolver algunos problemas, pero consume espacio si el gráfico es escaso. Las listas de adyacencia ahorran espacio, pero muchas operaciones definitivamente no son tan eficientes como las matrices de adyacencia.
3. La "tabla hash" asigna claves a una matriz grande mediante una función hash. Y como método para resolver conflictos de hash, el método de cremallera requiere características de lista vinculada, que es fácil de operar, pero requiere espacio adicional para almacenar punteros; el método de sondeo lineal requiere características de matriz para facilitar el direccionamiento continuo y no requiere espacio de almacenamiento para punteros; , pero la operación es un poco complicada algunos
4. "Árbol", implementado con una matriz es un "montón", porque el "montón" es un árbol binario completo para almacenar, no requiere punteros de nodo y la operación es relativamente simple para usar una lista vinculada; implementarlo es un "árbol" muy común, porque No es necesariamente un árbol binario completo, por lo que no es adecuado para el almacenamiento en matriz. Por esta razón, se han derivado varios diseños ingeniosos basados en esta estructura de "árbol" de lista enlazada, como árboles de búsqueda binaria, árboles AVL, árboles rojo-negro, árboles de intervalo, árboles B, etc., para abordar diferentes problemas.
4. Ventajas y desventajas
1. Dado que las matrices son compactas y de almacenamiento continuo, se puede acceder a ellas de forma aleatoria, los elementos correspondientes se pueden encontrar rápidamente a través de índices y se ahorra relativamente espacio de almacenamiento. Pero debido al almacenamiento continuo, el espacio de memoria debe asignarse al mismo tiempo. Por lo tanto, si la matriz desea expandirse, debe reasignar un espacio mayor y luego copiar todos los datos allí. La complejidad del tiempo es O (N); si desea Al insertar y eliminar en el medio de la matriz, todos los datos posteriores deben moverse cada vez para mantener la continuidad. La complejidad del tiempo es O (N).
2. Debido a que los elementos de una lista vinculada no son continuos, sino que dependen de punteros para señalar la ubicación del siguiente elemento, no hay problema de expansión de la matriz, si conoce el predecesor y el sucesor de un determinado elemento, puede eliminarlos; el elemento o inserte un nuevo elemento operando el puntero Complejidad de tiempo O (1). Sin embargo, debido a que el espacio de almacenamiento no es continuo, no se puede calcular la dirección del elemento correspondiente en función de un índice, por lo que el acceso aleatorio no es posible y debido a que cada elemento debe almacenar un puntero a la posición de los elementos anteriores, sí; consumirá relativamente más espacio de almacenamiento.
2. Operaciones básicas de estructuras de datos.
1. La operación básica no es más que acceso transversal. Para ser más específicos, es: agregar, eliminar, verificar y modificar.
2. Desde el nivel más alto, solo existen dos formas de recorrido y acceso a diversas estructuras de datos: lineal y no lineal.
3. Lineal se representa mediante iteración for/ while y no lineal se representa mediante recursividad.
4. Varios recorridos típicos.
1.matriz
recorrido vacío (int [] arr) { para (int i = 0; i <arr.length; i) { // Iterar sobre arr[i] } }
2. Lista enlazada
/* Nodo básico de lista enlazada individualmente */ clase Nodo de lista { valor int; ListNode siguiente; } recorrido vacío (cabeza de ListNode) { for (ListNode p = cabeza; p != nulo; p = p.siguiente) { //Accede iterativamente a p.val } } recorrido vacío (cabeza de ListNode) { // Accede recursivamente a head.val atravesar (cabeza.siguiente) }
3. árbol binario
/* Nodo de árbol binario básico */ claseNodoÁrbol { valor int; TreeNode izquierda, derecha; } recorrido vacío (raíz de TreeNode) { atravesar (raíz.izquierda) atravesar (raíz.derecha) }
4.árbol N-ario
/* Nodo de árbol N-ario básico */ claseNodoÁrbol { valor int; TreeNode[] hijos; } recorrido vacío (raíz de TreeNode) { para (niño TreeNode: root.children) atravesar(niño); }
5. El llamado marco es una rutina. Independientemente de agregar, eliminar, verificar o modificar, estos códigos son una estructura que nunca se puede separar. Puede usar esta estructura como un esquema y simplemente agregar códigos al marco de acuerdo con problemas específicos.
3. Guía de redacción de preguntas sobre algoritmos
1. ¡Cepille el árbol binario primero, cepille el árbol binario primero, cepille el árbol binario primero!
2. Los árboles binarios son el pensamiento marco más fácil de cultivar, y la mayoría de las técnicas de algoritmos son esencialmente problemas de recorrido de árboles.
3. No subestimes estas pocas líneas de código roto. Casi todos los problemas del árbol binario se pueden resolver utilizando este marco.
recorrido vacío (raíz de TreeNode) { // recorrido de reserva atravesar (raíz.izquierda) // recorrido en orden atravesar (raíz.derecha) // recorrido posterior al pedido }
4. Si no sabe cómo comenzar o tiene miedo de las preguntas, también puede comenzar con el árbol binario. Las primeras 10 preguntas pueden resultar un poco incómodas. Haga otras 20 preguntas según el marco. Tendrá cierta comprensión por su cuenta; termine todo el tema antes de hacerlo. Si mira hacia atrás en el tema de las reglas de división y conquista, encontrará que cualquier problema que involucre recursividad es un problema de árbol.
5. ¿Qué debo hacer si no puedo entender tantos códigos? Al extraer directamente el marco, puede ver la idea central: de hecho, muchos problemas de programación dinámica implican atravesar un árbol. Si está familiarizado con las operaciones de recorrido de árboles, al menos sabrá cómo convertir ideas en código y también lo sabrá. saber extraer otros La idea central de la solución.
2. Marco de rutina de resolución de problemas de programación dinámica
1. Conceptos básicos
1.Formulario
La forma general de un problema de programación dinámica es encontrar el valor óptimo. La programación dinámica es en realidad un método de optimización en la investigación de operaciones, pero se usa más comúnmente en problemas informáticos. Por ejemplo, le pide que encuentre la subsecuencia creciente más larga y la distancia mínima de edición.
2. Cuestiones fundamentales
El problema central es el agotamiento. Como requerimos el mejor valor, debemos enumerar exhaustivamente todas las respuestas posibles y luego encontrar el mejor valor entre ellas.
3.Tres elementos
1. Subproblemas superpuestos
0. Hay "subproblemas superpuestos" en este tipo de problemas. Si el problema es de fuerza bruta, la eficiencia será extremadamente ineficiente. Por lo tanto, se necesita una "nota" o "tabla DP" para optimizar el proceso exhaustivo y evitarlo. cálculos innecesarios.
2. Subestructura óptima
0. El problema de programación dinámica debe tener una "subestructura óptima", de modo que el valor óptimo del problema original pueda obtenerse a través del valor óptimo del subproblema.
3. Ecuación de transición de estado
0. Los problemas pueden cambiar constantemente y no es fácil enumerar exhaustivamente todas las soluciones factibles. Sólo enumerando la "ecuación de transición de estado" correcta podemos enumerarlas correctamente de forma exhaustiva.
1. Marco de pensamiento
Borrar caso base -> borrar "estado" -> borrar "selección" -> definir el significado de matriz/función dp
#Inicializar caso base dp[0][0][...] = base # Realizar transferencia de estado para el estado 1 en todos los valores del estado 1: para el estado 2 en todos los valores del estado 2: para... dp[estado 1][estado 2][...] = encontrar el valor máximo (seleccione 1, seleccione 2...)
2. Secuencia de Fibonacci
1. Recursión violenta
1.Código
int fib(int N) { si (N == 1 || N == 2) devuelve 1; retorno fib(N - 1) fib(N - 2); }
2. árbol recursivo
1. Siempre que encuentre un problema que requiera recursividad, es mejor dibujar un árbol de recursividad. Esto le será de gran ayuda para analizar la complejidad del algoritmo y encontrar las razones de la ineficiencia del algoritmo.
2.Fotos
3. Complejidad temporal del algoritmo recursivo.
0. Multiplicar el número de subproblemas por el tiempo necesario para resolver un subproblema.
1. Primero calcule el número de subproblemas, es decir, el número total de nodos en el árbol de recursividad. Obviamente, el número total de nodos del árbol binario es exponencial, por lo que el número de subproblemas es O (2 ^ n)
2. Luego calcule el tiempo para resolver un subproblema. En este algoritmo, no hay bucle, solo f (n - 1) f (n - 2) una operación de suma, el tiempo es O (1).
3. La complejidad temporal de este algoritmo es la multiplicación de dos, es decir, O (2 ^ n), nivel exponencial, explosión.
4. Al observar el árbol de recursividad, es obvio que se encuentra la razón de la ineficiencia del algoritmo: hay muchos cálculos repetidos.
5. Ésta es la primera propiedad de los problemas de programación dinámica: subproblemas superpuestos. A continuación intentaremos solucionar este problema.
2. Solución recursiva con memo.
1. Dado que el motivo que requiere mucho tiempo son los cálculos repetidos, podemos crear una "nota" antes de regresar después de calcular la respuesta a una determinada subpregunta. encuentre una subpregunta. Primero verifique el problema en "Nota". Si descubre que el problema se resolvió antes, simplemente saque la respuesta y úsela en lugar de perder tiempo calculando.
2. Generalmente, se utiliza una matriz como esta "nota". Por supuesto, también puede utilizar una tabla hash (diccionario).
3.Código
int fib(int N) { si (N < 1) devuelve 0; //La nota se inicializa a 0 vector<int> memo(N 1, 0); // Realiza recursividad con memo ayudante de devolución (nota, N); } int ayudante(vector<int>& memo, int n) { // caso base si (n == 1 || n == 2) devuelve 1; //Ya calculado if (memo[n]!= 0) devolver memo[n]; memorándum[n] = ayudante(memorándum, n - 1) ayudante(memorándum, n - 2); devolver nota[n]; }
4. árbol recursivo
De hecho, el algoritmo recursivo con "memo" transforma un árbol recursivo con gran redundancia en un gráfico recursivo sin redundancia mediante "poda", lo que reduce en gran medida el número de subproblemas (es decir, la recursión del número de nodos en el gráfico).
5. Complejidad
No hay cálculos redundantes en este algoritmo, el número de subproblemas es O (n) y la complejidad temporal de este algoritmo es O (n). Comparado con los algoritmos violentos, es un ataque de reducción de dimensiones.
6.Comparación con la programación dinámica.
La eficiencia de la solución recursiva con memo es la misma que la de la solución de programación dinámica iterativa. Sin embargo, este método se llama "de arriba hacia abajo" y la programación dinámica se llama "de abajo hacia arriba".
7. De arriba hacia abajo
El árbol de recursividad (o imagen) dibujado se extiende de arriba a abajo, comenzando desde un problema original más grande como f(20), y descompone gradualmente la escala hacia abajo hasta f(1) y f(2). Estos dos casos base luego devuelven el respuestas capa por capa.
8. De abajo hacia arriba
Simplemente comience desde abajo, el tamaño de problema más simple y más pequeño f (1) y f (2) y empuje hacia arriba hasta llegar a la respuesta que queremos f (20). y esta es la razón por la cual la planificación de programación dinámica generalmente rompe con la recursividad y, en cambio, completa los cálculos mediante iteración de bucle.
3.Solución iterativa de matriz dp.
1. Pensamientos
Con la inspiración del "memorándum" del paso anterior, podemos separar este "memorándum" en una tabla, llamémosla tabla DP. ¿No sería bueno completar cálculos "de abajo hacia arriba" en esta tabla?
2.Código
int fib(int N) { vector<int> dp(N 1, 0); // caso base dp[1] = dp[2] = 1; para (int i = 3; i <= N; i ) dp[yo] = dp[yo - 1] dp[yo - 2]; devolver dp[norte]; }
3. Ecuación de transición de estado
1. Es el núcleo de la resolución de problemas. Y es fácil encontrar que, de hecho, la ecuación de transición de estado representa directamente la solución de fuerza bruta
2. No menosprecies las soluciones violentas. Lo más difícil de los problemas de programación dinámica es escribir esta solución violenta, es decir, la ecuación de transición de estado. Siempre que escriba una solución de fuerza bruta, el método de optimización no es más que usar una nota o una tabla DP, no hay ningún misterio.
4. Compresión de estado
1. El estado actual solo está relacionado con los dos estados anteriores. De hecho, no necesita una tabla DP tan larga para almacenar todos los estados. Solo necesita encontrar una manera de almacenar los dos estados anteriores. Por lo tanto, se puede optimizar aún más para reducir la complejidad del espacio a O (1)
2.Código
int fib(int n) { si (n == 2 || n == 1) devolver 1; int anterior = 1, actual = 1; para (int i = 3; i <= n; i ) { int suma = curso anterior; anterior = actual; curr = suma; } devolver corriente; }
3. Esta técnica es la llamada "compresión de estado". Si encontramos que cada transferencia de estado solo requiere una parte de la tabla DP, entonces podemos intentar usar la compresión de estado para reducir el tamaño de la tabla DP y registrar solo la tabla DP. datos necesarios En términos generales, es comprimir una tabla DP bidimensional en una dimensión, es decir, comprimir la complejidad del espacio de O (n ^ 2) a O (n).
3. El problema de cobrar el cambio
0.Pregunta
Te dan k monedas con valores nominales de c1, c2...ck. La cantidad de cada moneda es ilimitada. Luego te dan una cantidad total de dinero. Te pregunto cuántas monedas necesitas al menos para compensar. esta cantidad Si es imposible compensar la cantidad, el algoritmo devuelve -1.
1. Recursión violenta
1. En primer lugar, este problema es un problema de programación dinámica porque tiene una "subestructura óptima". Para cumplir con la "subestructura óptima", los subproblemas deben ser independientes entre sí.
2. Volviendo al problema de cobrar el cambio, ¿por qué se dice que está en consonancia con la subestructura óptima? Por ejemplo, si desea encontrar la cantidad mínima de monedas cuando la cantidad = 11 (pregunta original), si conoce la cantidad mínima de monedas cuando la cantidad = 10 (subpregunta), solo necesita agregar una a la respuesta a la subpregunta (elija otra moneda de 1 valor nominal) es la respuesta a la pregunta original. Debido a que la cantidad de monedas es ilimitada, no hay control mutuo entre los subproblemas y son independientes entre sí.
3. Cuatro pasos principales
1. Determinar el caso base. Esto es muy simple. Obviamente, cuando la cantidad objetivo es 0, el algoritmo devuelve 0.
2. Determine el "estado", es decir, las variables que cambiarán en el problema y subproblemas originales. Dado que el número de monedas es infinito y la denominación de la moneda también viene dada por la pregunta, sólo la cantidad objetivo seguirá acercándose al caso base, por lo que el único "estado" es la cantidad objetivo.
3. Determinar la "elección", que es el comportamiento que hace que el "estado" cambie. ¿Por qué cambia la cantidad objetivo? Porque estás eligiendo monedas. Cada vez que eliges una moneda, equivale a reducir la cantidad objetivo. Entonces el valor nominal de todas las monedas es tu "elección"
4. Aclare la definición de función/matriz dp. De lo que estamos hablando aquí es de una solución de arriba hacia abajo, por lo que habrá una función dp recursiva. En términos generales, el parámetro de la función es la cantidad que cambiará durante la transición de estado, que es el "estado" mencionado anteriormente; El valor de retorno de la función es la cantidad que la pregunta requiere que calculemos. En lo que respecta a esta pregunta, solo hay un estado, que es "cantidad objetivo". La pregunta requiere que calculemos la cantidad mínima de monedas necesarias para alcanzar la cantidad objetivo. Entonces podemos definir la función dp así: La definición de dp (n): ingrese una cantidad objetivo n y devuelva la cantidad mínima de monedas para completar la cantidad objetivo n
4. Pseudocódigo
def coinChange(monedas: Lista[int], cantidad: int): # Definición: Para compensar la cantidad n, se necesitan al menos dp(n) monedas definición dp(n): # Haga una elección y elija el resultado que requiera la menor cantidad de monedas. para moneda en monedas: res = min(res, 1 dp(n - moneda)) devolver resolución #El resultado final requerido por la pregunta es dp(cantidad) devolver dp(monto)
5.Código
def coinChange(monedas: Lista[int], cantidad: int): definición dp(n): # caso base si n == 0: devuelve 0 si n < 0: devuelve -1 # Encuentra el valor mínimo, así que inicialízalo a infinito positivo. res = flotante('INF') para moneda en monedas: subproblema = dp(n - moneda) # El subproblema no tiene solución, sáltalo si subproblema == -1: continuar res = min(res, 1 subproblema) devolver res si res! = float('INF') más -1 devolver dp(monto)
6. Ecuación de transición de estado
7. árbol recursivo
8. Complejidad
El número total de subproblemas es el número de nodos del árbol recursivo. Esto es difícil de ver. En resumen, es exponencial. Cada subproblema contiene un bucle for con una complejidad de O(k). Entonces la complejidad del tiempo total es O(k * n^k), nivel exponencial
2. Recursividad con memo
1.Código
def coinChange(monedas: Lista[int], cantidad: int): # nota nota = dictar() definición dp(n): # Verifique la nota para evitar el doble conteo si n en la nota: devolver la nota [n] # caso base si n == 0: devuelve 0 si n < 0: devuelve -1 res = flotante('INF') para moneda en monedas: subproblema = dp(n - moneda) si subproblema == -1: continuar res = min(res, 1 subproblema) # Grabar en nota memo[n] = res si res != float('INF') else -1 devolver nota[n] devolver dp(monto)
2. Complejidad
Obviamente, el "memorándum" reduce en gran medida el número de subproblemas y elimina por completo la redundancia de subproblemas, por lo que el número total de subproblemas no excederá la cantidad de dinero n, es decir, el número de subproblemas. Está encendido). El tiempo para abordar un subproblema permanece sin cambios y sigue siendo O (k), por lo que la complejidad del tiempo total es O (kn)
3.Solución iterativa de matriz dp.
1.Definición de matriz dp
La función dp se refleja en los parámetros de la función, mientras que la matriz dp se refleja en el índice de la matriz: La definición de matriz dp: cuando la cantidad objetivo es i, se necesitan al menos monedas dp [i] para recolectarla
2.Código
int coinChange(vector<int>& monedas, cantidad int) { //El tamaño de la matriz es la cantidad 1 y el valor inicial también es la cantidad 1 vector<int> dp(cantidad 1, cantidad 1); // caso base dp[0] = 0; // El bucle for externo atraviesa todos los valores de todos los estados para (int i = 0; i < dp.size(); i ) { //El bucle for interno busca el valor mínimo de todas las opciones para (int moneda: monedas) { // El subproblema no tiene solución, omítelo si (i - moneda < 0) continúa; dp[i] = min(dp[i], 1 dp[i - moneda]); } } return (dp[monto] == monto 1) -1: dp[monto]; }
3. Proceso
4.Detalles
¿Por qué la matriz dp se inicializa en la cantidad 1? Porque la cantidad de monedas que componen la cantidad solo puede ser igual a la cantidad como máximo (se utilizan todas las monedas con un valor nominal de 1 yuan), por lo que inicializarla en la cantidad 1 es equivalente a inicializarlo a infinito positivo, lo que facilita el valor mínimo posterior
4. Resumen
1. En realidad, no existen trucos de magia para que las computadoras resuelvan problemas. Su única solución es agotar exhaustivamente todas las posibilidades. El diseño de algoritmos no es más que pensar primero en "cómo hacerlo de manera exhaustiva" y luego continuar con "cómo hacerlo de manera exhaustiva y exhaustiva".
2. Enumerar la ecuación de transferencia dinámica es resolver el problema de "cómo hacerlo exhaustivamente". La razón por la que es difícil es que, en primer lugar, muchos cálculos exhaustivos deben implementarse de forma recursiva y, en segundo lugar, porque el espacio de solución de algunos problemas es complejo y no es fácil completar el cálculo exhaustivo.
3. Los memorandos y los cuadros del PD tratan de buscar “cómo hacerlo de forma exhaustiva y exhaustiva”. La idea de intercambiar espacio por tiempo es la única forma de reducir la complejidad del tiempo. Además, déjame preguntarte, ¿qué otros trucos puedes hacer?
algoritmo de Likou
0. Pensamiento inverso
6.Transformación en zigzag
1. Ordenar por fila
0.Título
Organice una cadena determinada en forma de Z de arriba a abajo y de izquierda a derecha de acuerdo con el número de líneas dado. Después de eso, su salida debe leerse línea por línea de izquierda a derecha para generar una nueva cadena.
1.Ideas
Repita ss de izquierda a derecha, agregando cada carácter a la línea apropiada. La fila apropiada se puede rastrear usando la fila actual y las variables de dirección actual. La dirección actual solo cambiará cuando subamos a la fila superior o bajemos a la fila inferior.
2.Código
solución de clase { conversión de cadena pública (cadena s, int numRows) { si (numRows == 1) devuelve s; List<StringBuilder> filas = new ArrayList<>() //Almacena los caracteres de cada fila después de la transformación. para (int i = 0; i < Math.min(numRows, s.length()); i ) filas.add(new StringBuilder()); int curRow = 0; booleano yendo hacia abajo = falso; for (char c : s.toCharArray()) { //Atraviesa s directamente, conviértelo en una matriz de caracteres y asígnalo a c uno por uno filas.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) yendoAbajo = !yendoAbajo; curRow = yendo hacia abajo? 1: -1; } StringBuilder ret = nuevo StringBuilder(); para (StringBuilder fila: filas) ret.append(fila); devolver ret.toString(); } }
3. Complejidad
Tiempo O(n), donde n=len(s), espacio O(n)
1. Algoritmo puro
7. Inversión de enteros
1 pregunta
Dado un entero de 32 bits con signo, es necesario invertir cada bit del entero. Supongamos que nuestro entorno solo puede almacenar enteros con signo de 32 bits. Si el entero se desborda después de la inversión, se devolverá 0.
2.Ideas
Para "sacar" y "empujar" números sin la ayuda de una pila/matriz auxiliar, podemos usar las matemáticas, sacar primero el último dígito, luego dividir por 10 para deshacernos del último dígito, invertir el número y seguir multiplicándose. Después de 10, agregue el último dígito extraído y primero determine si se desbordará.
3.Código
solución de clase { público int reverso (int x) { int rev = 0; mientras (x! = 0) { int pop = x % 10; //Saca el último dígito de x x /= 10; //Eliminar el último dígito de x //Cuando int ocupa 32 bits, el rango de valores es -2147483648~2147483647, por lo que pop>7 o pop<-8 if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > Integer.MAX_VALUE % 10)) devuelve 0; if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < Integer.MIN_VALUE % 10)) devuelve 0; rev = rev * 10 pop; //Después de que el conjunto avance un dígito, agregue el último dígito } volver rev; } }
4. Complejidad
Tiempo: O(log(x)), espacio O(1)
2.matriz
0.frecuencia
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
35.Buscar posición de inserción
1. Dicotomía
0.Título
Dada una matriz ordenada y un valor objetivo, busque el valor objetivo en la matriz y devuelva su índice. Si el valor objetivo no existe en la matriz, devuelve la posición donde se insertará secuencialmente. Puedes asumir que no hay elementos duplicados en la matriz.
1. Pensamientos
1. El valor de retorno es la posición del primer valor dentro de [primero, último) que no sea menor que el valor. Tenga en cuenta que no se puede obtener el límite derecho del intervalo inicial, es decir, el izquierdo está cerrado y el derecho está abierto. , que también determina los cambios de izquierda y derecha (left=mid 1,right=mid)
2. Mid=first (último-primero)/2 se escribe así para evitar que se desborden números grandes
3. Finalmente, también puede regresar al final. Los dos se superponen. El código anterior también es aplicable incluso si el intervalo está vacío, la respuesta no existe, hay elementos repetidos y los límites superior/inferior de la búsqueda están abiertos. cerrado y el ajuste de posición de más o menos 1 solo aparece una vez.
4. ¿Qué sucede si nums[mid] < valor se cambia a nums[mid] <= valor? No es difícil pensar en esto. El resultado convergerá a la primera posición que sea mayor que el valor porque cuando se cumple la condición if cuando son iguales, el extremo izquierdo del intervalo aún cambiará y la posición igual no será. obtenido.
5. La matriz anterior está aumentando. Si está disminuyendo, ¿cómo deberíamos cambiarla? De hecho, solo necesitamos revertir la condición y cambiarla a nums[mid]>=value. El resultado convergerá al primero. mayor que la posición de valor
6. Volviendo a esta pregunta, busque el valor objetivo en la matriz y devuelva su índice. Si el valor objetivo no existe en la matriz, devuelva la posición donde se insertará en orden. Luego, usamos la plantilla para encontrar un valor que no sea menor que el objetivo y que sea el primero en la matriz. Luego, para cumplir con los requisitos de la pregunta, solo necesitamos devolver el índice del valor calculado, es decir, primero o último
2.Código
clase Solución: def searchInsert(self, nums: Lista[int], destino: int) -> int: izquierda, derecha = 0, len(nums) #Utilice el intervalo cerrado por la izquierda y abierto por la derecha [izquierda, derecha) while left < right: # Abierto a la derecha, por lo que no puede haber =, el intervalo no existe mid = izquierda (derecha - izquierda)//2 # Evita el desbordamiento, //Indica división de enteros if nums[mid] < target: # El punto medio es menor que el valor objetivo. En el lado derecho, se pueden obtener posiciones iguales. left = mid 1 # Izquierda cerrada, entonces 1 demás: right = mid # Abierto a la derecha, el punto final derecho real es mid-1 return left # Cuando finaliza este algoritmo, se garantiza que izquierda = derecha, y el retorno será el mismo para todos.
3. Complejidad
Complejidad del tiempo: O (logn), donde n es la longitud de la matriz. La complejidad temporal requerida para la búsqueda binaria es O (logn)
Complejidad espacial: O(1). Solo necesitamos espacio constante para almacenar varias variables.
1.La suma de dos números
1. Ley de violencia
0.Título
Dado un número de matriz de números enteros y un valor objetivo objetivo, busque los dos números enteros en la matriz cuya suma es el valor objetivo y devuelva sus subíndices de matriz. Suponga que cada entrada corresponde a una sola respuesta. Sin embargo, el mismo elemento en la matriz no se puede usar dos veces.
solución de clase { public int[] twoSum(int[] nums, int objetivo) { para (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) { if (nums[j] == objetivo - nums[i]) { devolver nuevo int[] {i, j}; } } } throw new IllegalArgumentException ("No hay solución de dos sumas"); } }
Tiempo O(n^2), espacio O(1)
2. Tabla hash de doble paso
solución de clase { public int[] twoSum(int[] nums, int objetivo) { Mapa<Entero, Entero> mapa = nuevo HashMap<>(); para (int i = 0; i < nums.length; i ) { map.put(numeros[i], i); } para (int i = 0; i < nums.length; i ) { int complemento = objetivo - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i) { devolver nuevo int[] {i, map.get(complemento) }; } } throw new IllegalArgumentException ("No hay solución de dos sumas"); } }
Se utilizaron dos iteraciones. En la primera iteración, agregamos el valor de cada elemento y su índice a la tabla. Luego, en la segunda iteración, comprobaremos si el elemento de destino (target-nums[i]) correspondiente a cada elemento existe en la tabla. Tenga en cuenta que el elemento de destino no puede ser el propio nums[i]. Este método reduce la complejidad del tiempo a O (n) y aumenta la complejidad del espacio a O (n).
Tiempo O(n), espacio O(n)
3. Pasar la tabla hash
solución de clase { public int[] twoSum(int[] nums, int objetivo) { Mapa<Entero, Entero> mapa = nuevo HashMap<>(); para (int i = 0; i < nums.length; i ) { int complemento = objetivo - nums[i]; if (map.containsKey(complemento)) { devolver nuevo int[] { map.get(complemento), i }; } map.put(numeros[i], i); } throw new IllegalArgumentException ("No hay solución de dos sumas"); } }
Hay una pequeña optimización para el método dos, que consiste en fusionar las dos iteraciones en una y crear una tabla hash para cada x, primero consultamos si el objetivo - x existe en la tabla hash y luego insertamos x en el hash. tabla, puede garantizar que x no coincidirá con usted.
4. Resumen
Algunas personas han planteado objeciones al algoritmo que utiliza la tabla hash. Hay un bucle en la clave contenida de HashMap, que sigue siendo O (n ^ 2). El mapa también aumenta la complejidad del espacio y la sobrecarga. más efectivo. , pero este punto de vista también tiene algunos problemas: el bucle en contieneKey solo entrará si hay un conflicto. Al mismo tiempo, si los conflictos son frecuentes, se utilizará el método getTreeNode para obtener el valor. getTreeNode obtiene el valor de un árbol rojo-negro y la complejidad del tiempo es como máximo O (logN).
167. Suma de dos números II matriz ordenada
1. Dicotomía
0.Título
Dada una matriz ordenada en orden ascendente, encuentre dos números cuya suma sea igual al número objetivo. La función debe devolver los dos valores de subíndice índice1 e índice2, donde índice1 debe ser menor que índice2 Los valores de índice devueltos (índice1 e índice2) no están basados en cero. Puedes asumir que cada entrada corresponde a una respuesta única y no puedes reutilizar los mismos elementos.
1.Código
solución de clase { public int[] twoSum(int[] números, int objetivo) { for (int i = 0; i < números.longitud; i) { int bajo = i 1, alto = números.longitud - 1; mientras (bajo <= alto) { int mid = (alto - bajo) / 2 bajo; if (números[medio] == objetivo - números[i]) { devolver nuevo int[]{i 1, mediados de 1}; } else if (números[medio] > objetivo - números[i]) { alto = medio - 1; } demás { bajo = medio 1; } } } devolver nuevo int[]{-1, -1}; } }
2. Complejidad
Tiempo: O(nlogn), espacio O(1)
1.Doble puntero
1.Código
solución de clase { public int[] twoSum(int[] números, int objetivo) { int bajo = 0, alto = números.longitud - 1; mientras (bajo <alto) { int suma = números[bajo] números[alto]; si (suma == objetivo) { devolver nuevo int[]{bajo 1, alto 1}; } más si (suma <objetivo) { bajo; } demás { --alto; } } devolver nuevo int[]{-1, -1}; } }
2. Complejidad
Tiempo: O(n), Espacio: O(1)
170.Suma de dos números III Diseño de estructura de datos VIP
653. La suma de dos números IV se ingresa en BST
1.Recursión HashSet
0.Título
Dado un árbol de búsqueda binario y un resultado objetivo, devuelve verdadero si hay dos elementos en el BST y su suma es igual al resultado objetivo dado
1. Pensamientos
Recorra sus dos subárboles (subárbol izquierdo y subárbol derecho) en cada nodo del árbol para encontrar otro número coincidente. Durante el proceso transversal, coloque el valor de cada nodo en un conjunto
2.Código
Solución de clase pública { público booleano findTarget (raíz de TreeNode, int k) { Establecer <Entero> conjunto = nuevo HashSet(); devolver buscar(raíz, k, conjunto); } búsqueda booleana pública (raíz de TreeNode, int k, conjunto <entero> conjunto) { si (raíz == nulo) falso retorno; si (set.contains(k - root.val)) devolver verdadero; set.add(raíz.val); devolver buscar(raíz.izquierda, k, establecer) || buscar(raíz.derecha, k, establecer); } }
Debido a que la colección de conjuntos debe actualizarse cada vez que se recorre, se debe colocar en la nueva función como parámetro, de modo que se agregue una nueva función.
3. Complejidad
Tiempo: O(n), espacio O(n)
2.HashSet BFS
1. Pensamientos
Recorrer un árbol binario utilizando la búsqueda en amplitud
2.Código
Solución de clase pública { público booleano findTarget (raíz de TreeNode, int k) { Establecer <Entero> conjunto = nuevo HashSet(); Cola <TreeNode> cola = nueva LinkedList(); cola.add(raíz); mientras (!queue.isEmpty()) { si (cola.peek()! = nulo) { Nodo TreeNode = cola.remove(); si (set.contains(k - nodo.val)) devolver verdadero; set.add(nodo.val); cola.add(nodo.derecha); cola.add(nodo.izquierda); } demás cola.remove(); } falso retorno; } }
3. Complejidad
Tiempo: O(n), espacio O(n)
3. Propiedades de los punteros dobles BST
1. Pensamientos
Los resultados del recorrido en orden de BST están ordenados en orden ascendente. Recorra el BST dado en orden y almacene los resultados del recorrido en la lista. Una vez completado el recorrido, utilice dos punteros l y r como índice principal y índice final de la lista.
2.Código
Solución de clase pública { público booleano findTarget (raíz de TreeNode, int k) { Lista <Entero> lista = nueva ArrayList(); orden(raíz, lista); int l = 0, r = lista.tamaño() - 1; mientras (l < r) { int suma = lista.get(l) lista.get(r); si (suma == k) devolver verdadero; si (suma <k) yo; demás r--; } falso retorno; } orden vacía pública (raíz de TreeNode, lista <entero>) { si (raíz == nulo) devolver; inorder(raíz.izquierda, lista); lista.add(root.val); inorder(raíz.derecha, lista); } }
3. Complejidad
Tiempo: O(n), espacio O(n)
1214. Encuentra la suma vip de dos árboles de búsqueda binarios.
560.El número de subarreglos consecutivos cuya suma es k
1. Enumeración
0.Título
Dada una matriz de números enteros y un número entero k, necesita encontrar el número de subarreglos consecutivos en la matriz que suman k
1.Ideas
Podemos enumerar todos los subíndices j en [0..i] para determinar si cumplen con las condiciones. Algunos lectores pueden pensar que, suponiendo que hemos determinado el principio y el final del subarreglo, todavía necesitamos una complejidad de tiempo O (n) para atravesarlo. el subarreglo. Para sumar el arreglo, la complejidad alcanzará O(n^3) y no se pasarán todos los casos de prueba. Pero si conocemos la suma de los subarreglos [j,i], podemos deducir la suma de [j-1,i] en O(1), por lo que esta parte del recorrido y la suma no es necesaria. La enumeración ya puede encontrar la suma de los subarreglos de [j,i] en O(1).
2.Código
Solución de clase pública { public int subarraySum(int[] números, int k) { recuento int = 0; for (int inicio = 0; inicio < nums.length; inicio) { suma int = 0; //Comience desde este número y encuentre la suma de las subsecuencias en secuencia. for (int fin = inicio; fin >= 0; --fin) { suma = números[fin]; si (suma == k) { contar ; } } } recuento de devoluciones; } }
3. Complejidad
Tiempo: O(n^2), Espacio: O(1)
2. Prefijo y tabla hash
1.Ideas
1. El cuello de botella del método uno es que para cada i, debemos enumerar todos los j para determinar si cumple las condiciones.
2. Defina pre[i] como la suma de todos los números en [0..i], entonces pre[i] se puede derivar recursivamente de pre[i−1], es decir: pre[i]=pre[i− 1 ] nums[i], entonces la condición "la suma de los subarreglos [j..i] es k" se puede convertir en pre[i]−pre[j−1]==k, y la siguiente condición se puede obtenido moviendo los términos El estándar j necesita satisfacer pre[j−1]==pre[i]−k
3. Al considerar el número de subarreglos consecutivos que terminan en i y que suman k, simplemente cuente cuántos prefijos pre[j] suman pre[i]−k. Construimos una tabla hash mp, con la suma como clave, el número de apariciones como el valor correspondiente, registramos el número de apariciones de pre[i], actualizamos mp de izquierda a derecha y calculamos la respuesta, luego la respuesta termina en i es mp[pre[i] −k] se puede obtener en tiempo O(1). La respuesta final es la suma del número de subarreglos que terminan en todos los subíndices con una suma de k
4. Cabe señalar que al actualizar el cálculo del borde de izquierda a derecha, se garantiza que el rango de subíndice de pre[j] registrado en mp[pre[i]−k] sea 0≤j≤i. Al mismo tiempo, dado que el cálculo de pre[i] solo está relacionado con la respuesta al elemento anterior, no necesitamos crear una matriz previa y usar directamente la variable pre para registrar la respuesta a pre[i-1] .
2.Código
Solución de clase pública { public int subarraySum(int[] números, int k) { int count = 0, pre = 0; //pre es la suma del prefijo HashMap <Entero, Entero> mp = nuevo HashMap < > (); mp.put(0, 1); //Inicialización, la suma es 0 y el número de apariciones es 1 para (int i = 0; i < nums.length; i ) { pre = nums[i];//El cálculo de pre[i] solo está relacionado con la respuesta al elemento anterior, no es necesario crear una matriz previa if (mp.containsKey(pre - k)) { //pre[i]−pre[j−1]==k contar = mp.get(pre - k); } mp.put(pre, mp.getOrDefault(pre, 0) 1); //la clave es pre, el valor es el número de apariciones } recuento de devoluciones; } }
3. Complejidad
Complejidad del tiempo: O (n), donde n es la longitud de la matriz. La complejidad temporal de atravesar la matriz es O (n), y la complejidad de usar la tabla hash para consultar y eliminar es O (1), por lo que la complejidad temporal total es O (n)
Complejidad espacial: O (n), donde n es la longitud de la matriz. La tabla hash puede tener n valores clave diferentes en el peor de los casos, por lo que requiere una complejidad espacial O (n)
523. Determinar los subarreglos continuos cuya suma es múltiplo de k
1. Suma de prefijo
0.Título
Dada una matriz que contiene números no negativos y un entero objetivo k, determine si la matriz contiene subarreglos consecutivos con un tamaño de al menos 2 y una suma que sea múltiplo de k, es decir, la suma es n*k, donde n también es un número entero
1.Ideas
1. Utilice una matriz de suma adicional para almacenar la suma acumulativa de la matriz. suma [i] almacena la suma del prefijo en la posición del i-ésimo elemento.
2. No recorreremos toda la submatriz para encontrar la suma. Solo necesitamos usar la suma del prefijo de la matriz para encontrar el número i-ésimo. Solo necesitamos encontrar la suma [j]. −suma[i] números[i]
2.Código
Solución de clase pública { checkSubarraySum booleano público (int [] números, int k) { int[] suma = nuevo int[nums.length]; suma[0] = números[0]; para (int i = 1; i < nums.length; i ) suma[i] = suma[i - 1] nums[i];//suma[i] guarda la suma del prefijo en la posición del i-ésimo elemento for (int inicio = 0; inicio < nums.length - 1; inicio) { for (int fin = inicio 1; fin < nums.length; fin) { //Para encontrar el número i-ésimo hasta el número j-ésimo, simplemente encuentre suma[j] - suma[i] nums[i] int suma = suma[fin] - suma[inicio] números[inicio]; if (summ == k || (k != 0 && suma % k == 0)) devolver verdadero; } } falso retorno; } }
3. Complejidad
Tiempo: O(n^2), Espacio: O(n)
2. Programación dinámica
1.Ideas
Calcule la suma de los subarreglos con longitudes 2, 3,...m respectivamente y determine si es múltiplo de k. Copiamos los datos de nums a dp Al calcular la suma de subarreglos de longitud 2: dp[j] = dp[j] nums[j 1] donde dp[j] es nums[j]; Al calcular la suma de subarreglos de longitud 3: dp[j] = dp[j] nums[j 2]; donde dp[j] es el dp[j] actualizado, y un dp[j] es equivalente a nums[j]. ] números[j 1] De esta manera, al calcular el tamaño del subarreglo de longitud p, puede usar el subarreglo calculado de longitud p-1 para actualizar y puede optimizar el bucle triple original en un bucle doble.
2.Código
solución de clase { int[] dp = nuevo int[10010]; checkSubarraySum booleano público (int [] números, int k) { if(nums.length < 2) devuelve falso; // Cuando k == 0, considérelo por separado. De hecho, la única diferencia entre este y k! = 0 es si se realiza una operación modular o no. si(k==0){ para(int i = 0; i < nums.length; i ){ for(int j = 0; j < nums.length-i; j ){ dp[j] = (dp[j] números[ji]); if(i!= 0 && dp[j] == 0) devuelve verdadero; } } falso retorno; } // Cuando i = k, dp [j] representa la suma de k 1 enteros consecutivos en nums con j como subíndice inicial. // Por ejemplo, cuando i = 0, equivale a copiar números a dp // Cuando i = 1, dp [0] equivale a comenzar con 0 como subíndice y la suma de los dos números enteros en nums, es decir, nums [0] nums [1] // Cada vez que calculas, puedes usar el dp original para actualizar, en lugar de agregarlos uno por uno. para(int i = 0; i < nums.length; i ){ for(int j = 0; j < nums.length-i; j ){ dp[j] = (dp[j] números[j i]) % k; if(i!= 0 && dp[j] == 0) devuelve verdadero; } } falso retorno; } }
3. Complejidad
Tiempo: O(n^2), Espacio: O(n)
3. tabla hash
1.Ideas
1. Use HashMap para guardar la suma acumulada hasta el i-ésimo elemento, pero dividimos esta suma de prefijo por k para tomar el resto
2. Atravesamos la matriz dada y registramos sum%k hasta la posición actual. Una vez que encontramos el nuevo valor de suma%k, insertamos un registro en el HashMap (suma%k, i)
3. Suponga que el valor de suma%k en la posición i-ésima es rem. Si la suma de cualquier subarreglo con i como punto final izquierdo es un múltiplo de k, por ejemplo, la posición es j, entonces el valor almacenado en el elemento j en el HashMap es (rem n*k)%k, y lo haremos busque (rem n ∗k)%k=rem, que es el mismo valor que el i-ésimo elemento guardado en HashMap
4. Llegue a la conclusión: siempre que el valor de suma% k se haya puesto en HashMap, significa que hay dos índices i y j, y la suma de los elementos entre ellos es un múltiplo entero de k. Siempre que exista la misma suma%k en HashMap, podemos devolver directamente {True}
2.Código
Solución de clase pública { checkSubarraySum booleano público (int [] números, int k) { suma int = 0; HashMap <Entero, Entero> mapa = nuevo HashMap < > (); mapa.put(0, -1); para (int i = 0; i < nums.length; i ) { suma = números[i]; si (k != 0) suma = suma %k; // Siempre que el valor de suma% k se haya puesto en HashMap, significa que hay dos índices i y j, y la suma de los elementos entre ellos es un múltiplo entero de k if (map.containsKey(suma)) { si (i - map.get(suma) > 1) devolver verdadero; } demás map.put(sum, i);//Guarde la suma acumulada hasta el i-ésimo elemento y divídala por k para obtener el resto } falso retorno; } }
3. Complejidad
Tiempo: O(n), Espacio: O(min(n,k)) HashMap contiene como máximo min(n,k) elementos diferentes.
713.El número de subarreglos consecutivos cuyo producto es menor que k
1. Dicotomía
0.Título
Dada una serie de números enteros positivos. Encuentre el número de subarreglos consecutivos en la matriz cuyo producto es menor que k
1.Ideas
1. Para una i fija, la búsqueda binaria encuentra el j más grande tal que el producto de nums[i] por nums[j] sea menor que k. Sin embargo, dado que el producto puede ser muy grande y provocar un desbordamiento numérico, debemos tomar el logaritmo de la matriz de números y convertir la multiplicación en suma.
2. Después de tomar el logaritmo de cada número en números, almacenamos su prefijo y prefijo. Durante la búsqueda binaria, para i y j, podemos usar prefijo [j 1] −prefijo [i] para obtener números [i] El logaritmo de. el producto a nums[j]. Para una i fija, cuando se encuentra la j más grande que satisface la condición, contendrá j−i 1 subarreglo continuo cuyo producto es menor que k.
2.Código
solución de clase { public int numSubarrayProductLessThanK(int[] números, int k) { si (k == 0) devuelve 0; doble registrok = Math.log(k); double[] prefix = new double[nums.length 1] //Obtiene la suma del prefijo después de los logaritmos para (int i = 0; i < nums.length; i ) { prefijo[i 1] = prefijo[i] Math.log(nums[i]);//El subíndice de la suma del prefijo comienza desde 1 } int respuesta = 0; for (int i = 0; i < prefix.length; i ) { // La dicotomía se realiza en la suma del prefijo, lo que equivale a realizarse en el resultado int lo = i 1, hola = prefijo.longitud; mientras (lo < hola) { int mi = lo (hola - lo) / 2; // Cuando i = 1, el prefijo [1] equivale a eliminar números [0], es decir, contar desde números [1] // Cuando i = 2, el prefijo [2] equivale a eliminar nums [0] nums [1], es decir, contar desde nums [2] if (prefijo[mi] < prefijo[i] logk - 1e-9) lo = mi 1; de lo contrario hola = mi; } ans = lo - i - 1;// Cuando i = 1, es equivalente al resultado después de eliminar el primer número } //Cuando i=2, es equivalente al resultado después de eliminar los dos primeros números. return ans; //-1 es porque lo en sí ya es mayor que el valor objetivo } }
3. Complejidad
Tiempo: O(nlogn), Espacio: O(n)
2. Punteros dobles
1.Ideas
1. Para cada derecha, necesitamos encontrar la izquierda más pequeña, de modo que el producto de los números entre ellos sea menor que k. Dado que cuando la izquierda aumenta, este producto no aumenta monótonamente, por lo que podemos usar el método del doble puntero para movernos hacia la izquierda. monótonamente.
2. Utilice un bucle para enumerar la derecha y establezca el valor inicial de la izquierda en 0. En cada paso del bucle, significa que la derecha se mueve una posición hacia la derecha y el producto se multiplica por números [derecha]. En este momento debemos movernos de izquierda a derecha hasta que se cumpla la condición de que el producto sea menor que k. En cada movimiento, el producto debe dividirse entre números [izquierda]. Cuando se completa el movimiento hacia la izquierda, para la derecha actual, contiene 1 subconjunto consecutivo derecha-izquierda cuyo producto es menor que k
2.Código
solución de clase { public int numSubarrayProductLessThanK(int[] números, int k) { si (k <= 1) devuelve 0; int prod = 1, ans = 0, izquierda = 0; for (int derecha = 0; derecha < nums.length; derecha) { prod *= nums[right];//Producto de prefijo actual while (prod >= k) prod /= nums[left];//Cuando no esté satisfecho, mueva el puntero izquierdo hacia la derecha continuamente // Para no repetir los cálculos, después de posicionar cada derecha, solo se calcula el número de subcadenas que terminan con el puntero derecho. // Todo lo que no termina en derecho se ha calculado antes y no se puede volver a calcular aquí. ans = derecha - izquierda 1; } volver y; } }
3. Complejidad
Tiempo: O(n), Espacio: O(1)
53. Suma máxima de subsecuencia
1. Programación dinámica
0.Título
Dada una matriz de números enteros, encuentre una submatriz continua (al menos un elemento) con la suma máxima y devuelva su suma máxima
1.Ideas
1. Utilice ai para representar números [i] y utilice f (i) para representar la "suma máxima de subarreglos consecutivos que terminan en el i-ésimo número"
2. ¿Cómo encontrar f(i)? Puede considerar si ai se convierte en un segmento solo o se une al segmento correspondiente a f (i − 1). Esto depende del tamaño de ai y f (i − 1) ai. Escriba una ecuación de transferencia de programación dinámica: f (i) =max{ f(i−1) ai,ai}
3. Teniendo en cuenta que f(i) solo está relacionada con f(i−1), solo podemos usar una variable pre para mantener el valor de f(i−1) para la f(i) actual, lo que complica el espacio. La velocidad se reduce a O (1), que es algo similar a la idea de "matriz rodante".
2.Código
solución de clase { público int maxSubArray(int[] números) { int pre = 0, maxAns = números[0]; para (int x: números) { //pre para mantener el valor de f(i−1) para el f(i) actual pre = Math.max(pre x, x);//Determinar si pre es un número negativo y si debe sumarse al número actual maxAns = Math.max(maxAns, pre);//Obtener el valor máximo } devolver maxAns; } }
3. Complejidad
Tiempo: O(n), Espacio: O(1)
2. Divide y vencerás: árbol de segmentos de línea
1.Ideas
1. Este método de dividir y conquistar es similar a la operación pushUp del "Problema LCIS para resolver el árbol de segmentos de línea". Se recomienda echar un vistazo al método de fusión de intervalos del árbol de segmentos de línea para resolver el "Problema de secuencia ascendente continua con intervalo más largo". "El problema de la suma de subsegmentos de intervalo máximo" que se ha preguntado muchas veces ".
2. Cómo fusionar la información en el intervalo [l,m] y la información en el intervalo [m 1,r] en la información en el intervalo [l,r]. Las dos preguntas más críticas son: ¿Qué información queremos mantener en el intervalo? ¿Cómo combinamos esta información?
3. Para un intervalo [l, r], podemos mantener cuatro cantidades:
4. Después de calcular las tres cantidades anteriores, es fácil calcular la suma m de [l, r]. Podemos considerar si el intervalo correspondiente a la mSum de [l, r] abarca m; puede que no abarque m, es decir, la mSum de [l, r] puede ser la mSum del "subintervalo izquierdo" y el "subintervalo derecho" Uno de mSum también puede abarcar m, y puede ser la suma de rSum del "subintervalo izquierdo" y lSum del "subintervalo derecho". El que sea mayor de los tres
2.Código
solución de clase { público int maxSubArray(int[] números) { if (nums == null || nums.length <= 0)// Verificación de entrada devolver 0; int len = nums.length;//Obtener la longitud de entrada devuelve getInfo(nums, 0, len - 1).mSum; } class wtevTree { //árbol de segmento de línea int lSum;//La suma máxima de subsegmentos con el intervalo izquierdo como punto final int rSum;//La suma máxima de subsegmentos con el intervalo correcto como punto final int iSum;//La suma de todos los números en el rango int mSum;//La suma máxima de subsegmentos de este intervalo wtevTree(int l, int r, int i, int m) { // constructor lSuma = l; rSuma = r; iSuma = i; mSuma = m; } } // Calcula los atributos de la capa anterior a través de los atributos existentes y regresa paso a paso para obtener el árbol de segmento de línea wtevTree pushUp(wtevTree izquierdaT, wtevTree derechaT) { // La suma de la nueva subsección es igual a la suma del intervalo izquierdo o la suma de los intervalos del intervalo izquierdo más la suma del intervalo derecho int l = Math.max(leftT.lSum, leftT.iSum rightT.lSum); // La rSum del nuevo subsegmento es igual a la rSum del intervalo derecho o la suma de los intervalos del intervalo derecho más la rSum del intervalo izquierdo int r = Math.max(leftT.rSum rightT.iSum, rightT.rSum); // La suma del intervalo del nuevo subsegmento es igual a la suma de las sumas de los intervalos izquierdo y derecho int i = leftT.iSum rightT.iSum; // La suma máxima de subsegmentos del nuevo subsegmento, su subsegmento puede pasar por los intervalos izquierdo y derecho, o el intervalo izquierdo, o el intervalo derecho int m = Math.max(leftT.rSum rightT.lSum, Math.max(leftT.mSum, rightT.mSum)); devolver nuevo wtevTree(l, r, i, m); } // Crea y obtiene recursivamente la estructura de todas las subsecciones del intervalo de entrada wtevTree getInfo(int[] números, int izquierda, int derecha) { if (izquierda == derecha) // Si la longitud del intervalo es 1, sus cuatro subsegmentos son todos sus valores devolver nuevo wtevTree(nums[izquierda], nums[izquierda], nums[izquierda], nums[izquierda]); int mid = (left right) >> 1;// Obtenga el punto medio mid, mover una posición hacia la derecha equivale a dividir por 2, y la operación es más rápida wtevTree leftT = getInfo(nums, left, mid); wtevTree rightT = getInfo(nums, mid 1, right); // mid 1, no hay intersección entre los intervalos izquierdo y derecho. return pushUp(leftT, rightT);//Después de que finalice la recursividad, realice la última fusión } }
3. Complejidad
Tiempo: O(n), Espacio: O(logn) pila recursiva
4. Resumen
1. El "Método 2" tiene la misma complejidad temporal que el "Método 1", pero debido a que utiliza recursividad y mantiene cuatro estructuras de información, el tiempo de ejecución es un poco más largo y la complejidad espacial no es tan buena como la del Método 1. Excelente y difícil comprender. Entonces, ¿cuál es el significado de este método?
2. Pero si observa de cerca el "Método 2", no solo puede resolver el problema del intervalo [0, n-1], sino que también puede usarse para resolver el problema de cualquier subintervalo [l, r] . Si memorizamos la información de todos los subintervalos que aparecen después de dividir y conquistar [0, n-1] usando almacenamiento dinámico, es decir, después de construir un árbol real, podemos hacerlo en tiempo O (logn). Modifique los valores en la secuencia y realice un mantenimiento simple, y luego aún podremos encontrar la respuesta en cualquier intervalo de tiempo O (logn). Para consultas a gran escala, se reflejan las ventajas de este método. Este árbol es una estructura de datos mágica mencionada anteriormente: árbol de segmento de línea
152. Producto de orden máximo de palabras
1. Programación dinámica
0.Título
Dado un número de matriz entera, busque el subarreglo continuo con el producto más grande en la matriz (el subarreglo contiene al menos un número) y devuelva el producto correspondiente al subarreglo.
1. Pensamientos
1. Según la experiencia de "53. Suma máxima de subsecuencia", es incorrecto escribir la ecuación de transferencia de programación dinámica: f(i)=max{f(i−1) ai, ai}
2. La definición aquí no satisface la "subestructura óptima". Es posible que la solución óptima en la posición actual no se obtenga transfiriendo la solución óptima a la posición anterior.
3. Clasifique y discuta según la positividad. Si la posición actual es un número negativo, entonces esperamos que el producto de un segmento que termina en su posición anterior también sea un número negativo, de modo que un negativo pueda ser positivo, y esperamos. que el producto intente “tomar la mayor cantidad posible”, es decir, lo más pequeño posible. Si la posición actual es un número positivo, preferimos que el producto de un segmento que termina en su posición anterior también sea un número positivo, y queremos que sea lo más grande posible. Podemos mantener otro fmin(i), que representa. el Producto del subconjunto más pequeño de productos que terminan con i elementos
4. Ecuación de transición de estado
5. Representa el producto fmax (i) del subconjunto con el producto más grande al final del i-ésimo elemento. Puede considerar agregar ai al producto del subconjunto con el producto más grande o más pequeño al final de i. -1 elemento, y agregue ai a los dos, el mayor de los tres es el producto del subconjunto más grande de productos al final del i-ésimo elemento. -ésimo elemento fmin(i) es el mismo.
2.Código
solución de clase { público int maxProduct(int[] números) { longitud int = números.longitud; int[] maxF = nuevo int[longitud]; int[] minF = new int[length];//El producto del subarreglo más pequeño que termina con el i-ésimo elemento System.arraycopy(nums, 0, maxF, 0, length);// Copiar números comenzando desde 0 a maxF comenzando desde 0, longitud longitud System.arraycopy(nums, 0, minF, 0, longitud); para (int i = 1; i < longitud; i) { //Dos ecuaciones de transición de estado maxF[i] = Math.max(maxF[i - 1] * números[i], Math.max(nums[i], minF[i - 1] * números[i])); minF[i] = Math.min(minF[i - 1] * números[i], Math.min(nums[i], maxF[i - 1] * números[i])); } int respuesta = maxF[0]; para (int i = 1; i < longitud; i) { respuesta = Math.max(ans, maxF[i]); } volver y; } }
3. Complejidad
El tiempo y el espacio son ambos O(n)
2. Optimizar el espacio
1. Pensamientos
Dado que el estado i-ésimo solo está relacionado con el estado i-1, de acuerdo con la idea de la "matriz rodante", solo podemos usar dos variables para mantener el estado en el momento i-1, una para mantener fmax y otra para mantener fmin. .
2.Código
solución de clase { público int maxProduct(int[] números) { int maxF = números[0], minF = números[0], ans = números[0]; longitud int = números.longitud; para (int i = 1; i < longitud; i) { int mx = maxF, mn = minF;//Solo se utilizan dos variables para mantener el estado en el tiempo i−1 y optimizar el espacio maxF = Math.max(mx * números[i], Math.max(nums[i], mn * números[i])); minF = Math.min(mn * números[i], Math.min(números[i], mx * números[i])); respuesta = Math.max(maxF, respuesta); } volver y; } }
3. Complejidad
Tiempo: O(n), Espacio: O(1)
198. Robo
1. Matriz rodante de programación dinámica
0.Título
Eres un ladrón profesional que planea robar casas en la calle. Hay una cierta cantidad de efectivo escondida en cada habitación. El único factor de restricción que afecta su robo es que las casas adyacentes están equipadas con sistemas antirrobo interconectados. Si los ladrones irrumpen en dos casas adyacentes la misma noche, el sistema. Alarmará automáticamente. Dada una serie de números enteros no negativos que representan la cantidad de dinero almacenada en cada casa, calcula la cantidad máxima de dinero que puedes robar en una noche sin activar el dispositivo de alarma.
1.Ideas
1. Considere primero el caso más simple. Si solo hay una casa, entonces roba esa casa y podrás robar hasta la cantidad total máxima. Si solo hay dos casas, dado que las dos casas son adyacentes, no puedes robar al mismo tiempo. Solo puedes robar una de las casas. Por lo tanto, elige la casa con una cantidad mayor para robar y podrás robar hasta la otra. importe total máximo.
2. Si hay más de dos casas, ¿cómo calcular el monto total máximo que se puede robar? Para la casa (k>2), hay dos opciones: 1. Si robas la k-ésima casa, no puedes robar la k-ésima casa. La cantidad total robada es la suma de la cantidad total máxima de las primeras k-2 casas y la cantidad de la k-ésima casa. 2. Si la k-ésima casa no es robada, el monto total robado es el monto total máximo de las primeras k-1 casas. Elija la opción con mayor monto total de robo entre las dos opciones. El monto total de robo correspondiente a esta opción es la cantidad total máxima de dinero que se puede robar de las primeras k casas.
3.dp[i] representa la cantidad total máxima que se puede robar de las primeras i casas, ecuación de transición de estado: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4. Condiciones de contorno: dp[0]=nums[0] y dp[1]=max(nums[0],nums[1]), la respuesta final es dp[n−1]
2.Código
solución de clase { public int rob(int[] números) { if (numeros == nul||numeros.length == 0) { devolver 0; } longitud int = números.longitud; si (longitud == 1) { devolver números[0]; } int[] dp = nuevo int[longitud]; dp[0] = nums[0];//Dos condiciones de contorno dp[1] = Math.max(numeros[0], numeros[1]); for (int i = 2; i < length; i ) {// Ecuación de transferencia dinámica dp[i] = Math.max(dp[i - 2] números[i], dp[i - 1]); } devolver dp[longitud - 1]; } }
3. Complejidad
Tiempo: O(n), Espacio: O(n)
4. Optimización
El método anterior utiliza una matriz para almacenar los resultados. Teniendo en cuenta que el monto total máximo de cada casa solo está relacionado con el monto total máximo de las dos primeras casas de la casa, puede usar una matriz rodante para almacenar solo el monto total máximo de las dos primeras casas en cada momento.
solución de clase { public int rob(int[] números) { if (numeros == nul||numeros.length == 0) { devolver 0; } longitud int = números.longitud; si (longitud == 1) { devolver números[0]; } //En cada momento, solo es necesario almacenar la cantidad total más alta de las dos primeras casas, matriz rodante int primero = números[0], segundo = Math.max(números[0], números[1]); para (int i = 2; i < longitud; i ) { temperatura int = segundo; segundo = Math.max(primeros números[i], segundo); primero = temporal; } regresar segundo; } }
Tiempo: O(n), Espacio: O(1)
213. Robo II Formar un círculo ×
1. Programación dinámica
0.Título
Eres un ladrón profesional que planea robar casas a lo largo de la calle, con una cierta cantidad de dinero escondida en cada habitación. Todas las casas en este lugar están en un círculo, lo que significa que la primera casa y la última casa están una al lado de la otra. Al mismo tiempo, las casas adyacentes están equipadas con sistemas antirrobo interconectados. Si ladrones irrumpen en dos casas adyacentes la misma noche, el sistema activará la alarma automáticamente. Dada una serie de números enteros no negativos que representan la cantidad de dinero almacenada en cada casa, calcula la cantidad máxima de dinero que puedes robar sin activar el dispositivo de alarma.
1.Ideas
1. La disposición circular significa que solo se puede elegir una de la primera casa y la última casa para robar, por lo que este problema de disposición circular de habitaciones se puede reducir a dos subproblemas de disposición de habitaciones de una sola fila:
2. Sin robar la primera casa (es decir, nums[1]), la cantidad máxima es p1 Sin robar la última casa (es decir, nums[n−1]), la cantidad máxima es p2 Integral La cantidad máxima de robo es max. (p1,p2)
3. Ecuación de transición de estado: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4.dp[n] solo está relacionado con dp[n−1] y dp[n−2], por lo que podemos configurar dos variables cur y pre para que se registren alternativamente, reduciendo la complejidad del espacio a O (1)
2.Código
solución de clase { public int rob(int[] números) { if(nums.length == 0) devuelve 0; if(nums.length == 1) devuelve números[0]; //La longitud de la copia se deja cerrada y se abre a la derecha [0, longitud-2] devolver Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)), myRob(Arrays.copyOfRange(nums, 1, nums.length))); } privado int myRob(int[] números) { int pre = 0, cur = 0, tmp; para (int número: números) { tmp = cur; cur = Math.max(pre num, cur); pre = tmp; } devolver cur; } }
3. Complejidad
Tiempo: O(n), Espacio: O(1)
337. Robo III Árbol binario
1. Programación dinámica
0.Título
Después de robar una calle y un círculo de casas la última vez, el ladrón encontró una nueva zona donde poder robar. A esta zona sólo existe una entrada, a la que llamamos "raíz". Además de la "raíz", cada casa tiene una y sólo una casa "padre" conectada a ella. Después de un poco de reconocimiento, el astuto ladrón se dio cuenta de que "todas las casas de este lugar están dispuestas como un árbol binario". Si dos casas directamente conectadas son asaltadas la misma noche, la casa llamará automáticamente a la policía. Calcula la cantidad máxima de dinero que un ladrón puede robar en una noche sin que salte la alarma
1.Ideas
1. Simplifique este problema: un árbol binario. Cada punto del árbol tiene un peso correspondiente. Cada punto tiene dos estados (seleccionado y no seleccionado). Pregunte sobre la situación en la que los puntos con una relación padre-hijo no se pueden seleccionar al mismo tiempo. ¿Cuál es la suma máxima de peso de los puntos que se pueden seleccionar?
2. Utilice f (o) para representar la suma de peso máxima de los nodos seleccionados en el subárbol del nodo o cuando se selecciona el nodo o; g (o) representa la suma de peso máxima del nodo seleccionado en el subárbol del nodo o cuando se selecciona o; el nodo no está seleccionado La suma de peso máximo del nodo seleccionado; l y r representan los hijos izquierdo y derecho de o.
3. Cuando se selecciona o, no se pueden seleccionar los hijos izquierdo o derecho de o. Por lo tanto, la suma de peso máximo de los puntos seleccionados en el subárbol cuando se selecciona o es la suma de las sumas de peso máximo de lyr cuando se seleccionan. no están seleccionados, es decir, f (o)= g(l) g(r)f(o)=g(l) g(r)
4. Cuando no se selecciona o, los hijos izquierdo y derecho de o se pueden seleccionar o no. Para un hijo específico x de o, su contribución a o es el valor mayor de la suma de pesos cuando se selecciona x y cuando no se selecciona x. Por lo tanto g(o)=max{f(l),g(l)} max{f(r),g(r)}
5. Use un mapa hash para almacenar los valores de función de f y g, y use un método de búsqueda en profundidad para recorrer el árbol binario en orden, y podemos obtener f y g de cada nodo. El valor máximo de f y g del nodo raíz es la respuesta que buscamos
2.Código
solución de clase { Map<TreeNode, Integer> f = new HashMap<TreeNode, Integer>(); Map<TreeNode, Integer> g = nuevo HashMap<TreeNode, Integer>(); public int rob (raíz de TreeNode) { dfs(root);// La profundidad primero atraviesa el nodo raíz return Math.max(f.getOrDefault(raíz, 0), g.getOrDefault(raíz, 0)); } dfs vacío público (nodo TreeNode) { si (nodo == nulo) { devolver; } dfs(node.left);//Adopta el método transversal posterior al pedido dfs(nodo.derecha); // Cuando se selecciona el nodo, la suma de peso máximo del punto seleccionado en el subárbol es la suma de las sumas de peso máximo de lyr cuando no están seleccionados. f.put(nodo, nodo.val g.getOrDefault(nodo.izquierda, 0) g.getOrDefault(nodo.derecha, 0)); //Un hijo específico x cuando no se selecciona el nodo, su contribución al nodo es la mayor entre la suma de pesos cuando se selecciona x y cuando no se selecciona x. g.put(nodo, Math.max(f.getOrDefault(nodo.left, 0), g.getOrDefault(nodo.left, 0)) Math.max(f.getOrDefault(nodo.derecha, 0), g.getOrDefault(nodo.derecha, 0))); } }
3. Complejidad
El algoritmo anterior realiza un recorrido posterior al orden del árbol binario, y la complejidad temporal es O (n), ya que la recursividad utilizará el espacio de la pila, el costo del espacio es O (n) y el costo del espacio del mapa hash es; también O (n), entonces La complejidad del espacio también es O (n)
4. Optimización
Ya sea f (o) o g (o), sus valores finales solo están relacionados con f (l), g (l), f (r), g (r), por lo que para cada nodo, solo nos importa sobre sus ¿Cuáles son las f y g de los nodos secundarios? Podemos diseñar una estructura para representar los valores f y g de un determinado nodo. Cada vez que regresa la recursividad, los f y g correspondientes a este punto se devuelven a la llamada de nivel anterior, lo que puede ahorrar el espacio del mapa hash. .
solución de clase { public int rob (raíz de TreeNode) { int[] estadoraíz = dfs(raíz); return Math.max(rootStatus[0], rootStatus[1]); } public int[] dfs(nodo TreeNode) { si (nodo == nulo) { devolver nuevo int[]{0, 0}; } int[] l = dfs(nodo.izquierda); int[] r = dfs(nodo.derecha); //l[0], r[0] representa el peso de agarrar, l[1], r[1] representa el peso de no agarrar int seleccionado = nodo.val l[1] r[1]; int notSelected = Math.max(l[0], l[1]) Math.max(r[0], r[1]); devolver nuevo int[]{seleccionado, no seleccionado}; } }
Complejidad del tiempo: O (n). Analizado arriba. Complejidad espacial: O (n). Aunque la versión optimizada elimina el espacio del mapa hash, el costo de usar el espacio de la pila sigue siendo O (n), por lo que la complejidad del espacio permanece sin cambios.
15.Suma de tres números
1. Clasificación de doble puntero
0.Título
Dada una matriz de números que contiene n números enteros, determine si hay tres elementos a, b, c en números tales que a b c = 0. Por favor encuentre todos los triples que cumplan las condiciones y no se repitan.
1. Sin repetición
No puede simplemente usar un bucle triple para enumerar todos los triples. Debe usar una tabla hash para realizar operaciones de deduplicación, ordenar los elementos en la matriz de pequeño a grande y luego usar un bucle triple ordinario para cumplir con los requisitos anteriores.
Para cada ciclo, los elementos de dos enumeraciones adyacentes no pueden ser iguales; de lo contrario, también provocará duplicaciones.
2. Punteros dobles
Cuando necesita enumerar dos elementos en una matriz, si encuentra que a medida que el primer elemento aumenta, el segundo elemento disminuye, puede usar el método de doble puntero para reducir la complejidad temporal de la enumeración de O (N ^ 2) reducida a O(N)
Mantenga el segundo bucle sin cambios y cambie el tercer bucle a un puntero comenzando desde el extremo derecho de la matriz y moviéndose hacia la izquierda.
Para agregar detalles, debe mantener el puntero izquierdo a la izquierda del puntero derecho (es decir, b ≤ c)
3.Código
solución de clase { Lista pública<Lista<Entero>> tresSuma(int[] números) { int n = números.longitud; Arrays.sort(nums); //Ordenar la matriz Lista<Lista<Integer>> ans = new ArrayList<List<Integer>>(); // enumera un for (int primero = 0; primero < n; primero) { // Debe ser diferente del último número enumerado if (primero > 0 && números[primero] == número[primero - 1]) { continuar; } // El puntero correspondiente a c inicialmente apunta al extremo derecho de la matriz int tercero = n - 1; int objetivo = -nums[primero]; // enumeración b for (int segundo = primero 1; segundo < n; segundo) { // Debe ser diferente del último número enumerado if (segundo > primer 1 && números[segundo] == números[segundo - 1]) { continuar; } // Es necesario asegurarse de que el puntero de b esté en el lado izquierdo del puntero de c while (segundo < tercero && números[segundo] números[tercero] > objetivo) { --tercero; } if (segundo == tercero) { // Si los punteros se superponen, la condición no se cumplirá posteriormente y podrá salir del ciclo romper; } if (nums[segundo] nums[tercero] == objetivo) { ans.add(Arrays.asList(nums[primero],nums[segundo],nums[tercero])); } } } volver y; } }
4. Complejidad
Complejidad del tiempo: O (N ^ 2), donde N es la longitud de los números de la matriz
Complejidad espacial: O (logN). Ignoramos el espacio para almacenar respuestas y la complejidad espacial de la clasificación adicional es O (logN). Sin embargo, modificamos los números de la matriz de entrada, lo que puede no estar permitido en situaciones reales. Por lo tanto, también se puede considerar que se utiliza una matriz adicional para almacenar una copia de los números y ordenarla.
18.Suma de cuatro números
1. Clasificación de doble puntero
0.Título
Dada una matriz de números que contiene n números enteros y un valor objetivo objetivo, determine si hay cuatro elementos a, b, cyd en números tales que el valor de a b c d sea igual al objetivo. Encuentre todos los cuádruples que cumplan la condición y no se repitan.
1.Ideas
Es agregar una capa de bucle basada en la suma de tres números y luego usar punteros dobles, pero usar los valores máximo y mínimo para juzgar de antemano, ahorrando tiempo.
2.Código
solución de clase { Lista pública<Lista<Entero>> cuatroSuma(int[] números,int objetivo){ List<List<Integer>> result=new ArrayList<>();/*Definir un valor de retorno*/ if(numeros==null||numeros.length<4){ resultado de devolución; } Arrays.sort(numeros); int longitud=números.longitud; /*Defina 4 punteros k, i, j, h k comienza a atravesar desde 0, i comienza a atravesar desde k 1, dejando j y h, j apunta a i 1, h apunta al valor máximo de la matriz*/ for(int k=0;k<longitud-3;k){ /*Ignorar cuando el valor de k es igual al valor anterior*/ if(k>0&&nums[k]==nums[k-1]){ continuar; } /* Obtenga el valor mínimo actual Si el valor mínimo es mayor que el valor objetivo, significa que los valores cada vez mayores posteriores no funcionarán en absoluto*/ int min1=numeros[k] numeros[k 1] numeros[k 2] numeros[k 3]; si(min1>objetivo){ break;// El descanso utilizado aquí sale directamente del ciclo, porque los números posteriores solo serán mayores. } /* Obtenga el valor máximo actual si el valor máximo es menor que el valor objetivo, significa que los valores cada vez más pequeños posteriores son simplemente inútiles, ignórelo*/ int max1=numeros[k] numeros[longitud-1] numeros[longitud-2] numeros[longitud-3]; if(max1<objetivo){ continue;//Use continuar aquí para continuar con el siguiente ciclo, porque el siguiente ciclo tiene un número mayor } /*Bucle de segundo nivel i, el valor inicial apunta a k 1*/ for(int i=k 1;i<longitud-2;i){ if(i>k 1&&nums[i]==nums[i-1]){/*Ignorar cuando el valor de i es igual al valor anterior*/ continuar; } int j=i 1;/*definir el puntero j para que apunte a i 1*/ int h=length-1;/*Defina el puntero h para que apunte al final de la matriz*/ int min=numeros[k] numeros[i] numeros[j] numeros[j 1]; si(min>objetivo){ romper; } int max=numeros[k] numeros[i] numeros[h] numeros[h-1]; si(max<objetivo){ continuar; } /*Inicie el rendimiento del puntero j y el puntero h, calcule la suma actual, si es igual al valor objetivo, j y deduplicación, h-- y deduplicación, cuando la suma actual es mayor que el valor objetivo, h-- , cuando la suma actual es menor que el valor objetivo j */ mientras (j<h){ int curr=números[k] números[i] números[j] números[h]; si(curr==objetivo){ result.add(Arrays.asList(nums[k],nums[i],nums[j],nums[h]));//Completar en un solo paso j; while(j<h&&nums[j]==nums[j-1]){ j; } h--; while(j<h&&i<h&&nums[h]==nums[h 1]){ h--; } }si no(curr>objetivo){ h--; }demás { j; } } } } resultado de devolución; } }
3. Complejidad
Tiempo: O (n ^ 3), la complejidad temporal de la clasificación es O (nlogn)
Espacio: O (logn), depende principalmente del espacio adicional utilizado al ordenar
454. Adición de cuatro matrices ×
Utilice el mapa para agrupar por pares
0.Título
Dadas cuatro listas de matrices A, B, C, D que contienen números enteros, cuente cuántas tuplas (i, j, k, l) hay tales que A[i] B[j] C[k] D[l] = 0
1.Ideas
1. El método se divide en dos grupos, HashMap almacena un grupo y el otro grupo se compara con HashMap.
2. En este caso, la situación se puede dividir en tres tipos: 1.HashMap almacena una matriz, como A. Luego calcule la suma de las tres matrices, como BCD. La complejidad del tiempo es: O (n) O (n ^ 3), lo que da como resultado O (n ^ 3). 2.HashMap almacena la suma de tres matrices, como ABC. Luego calcule una matriz, como D. La complejidad del tiempo es: O (n^3) O(n), lo que da como resultado O(n^3). 3.HashMap almacena la suma de dos matrices, como AB. Luego calcule la suma de las dos matrices, como CD. La complejidad del tiempo es: O (n^2) O(n^2), lo que da como resultado O(n^2).
3. Según el segundo punto, podemos concluir que dos matrices se cuentan como dos matrices.
4. Tomemos como ejemplo el almacenamiento de la suma de dos matrices AB. Primero, encuentre la suma sumAB de dos números cualesquiera A y B, use sumAB como clave y el número de veces que aparece sumAB como valor, y guárdelo en el mapa hash. Luego calcule sumCD, la inversa de la suma de dos números cualesquiera en C y D, y encuentre si la clave es sumCD en el mapa hash.
2.Código
solución de clase { public int fourSumCount(int[] A, int[] B, int[] C, int[] D) { HashMap<Integer, Integer> mapa = nuevo HashMap<Integer,Integer>(); int len=A.longitud; para(int a:A) { para (int b: B) { //Almacena el resultado de a b, no hay asignación de 1, existe en la base original 1 map.merge(a b, 1, (antiguo,nuevo_)->antiguo 1); } } intres=0; para(intc:C) { para(int d:D) { res =map.getOrDefault(0-cd, 0); } } devolver resolución; } }
3. Complejidad
Tiempo: O(n^2), Espacio: O(1)
4. Resumen
Los nuevos métodos de mapa merge y getOrDefault se utilizan en el algoritmo. En comparación con el método tradicional de escritura de juicios, la eficiencia ha mejorado enormemente.
subtema
3. Lista enlazada
0.frecuencia
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
2. Suma dos números en orden inverso
0.Título
Dadas dos listas enlazadas no vacías que representan dos números enteros no negativos. Entre ellos, sus respectivos dígitos se almacenan en orden inverso y cada uno de sus nodos solo puede almacenar un dígito. Si sumamos estos dos números, se devolverá una nueva lista vinculada para representar su suma. Puedes suponer que, excepto el número 0, ninguno de los números comenzará con 0.
1.Solución
clase pública ListNode addTwoNumbers (ListNode l1, ListNode l2) { ListNode dummyHead = nuevo ListNode(0); ListNode p = l1, q = l2, curr = dummyHead; acarreo int = 0; mientras (p != nulo || q != nulo) { int x = (p! = nulo)? p.val: 0; int y = (q! = nulo)? q.val: 0; int suma = llevar x y; llevar = suma / 10; curr.next = new ListNode(suma % 10); actual = actual.siguiente; si (p != nulo) p = p.siguiente; si (q != nulo) q = q.siguiente; } si (llevar > 0) { curr.next = nuevo ListNode(llevar); } devolver dummyHead.siguiente; }
El punto más importante de esta pregunta es la situación de acarreo después de la suma, que es fácil de ignorar. Otro punto importante es que después de completar la suma del último dígito, es posible que se produzca un acarreo. Esta situación debe manejarse por separado. La idea inicial era convertir las dos listas enlazadas en números y sumarlas, pero este método provocará un desbordamiento de números que son demasiado largos, por lo que las listas enlazadas sólo se pueden procesar.
Tiempo: O(max(m, n)), Espacio: O(max(m, n))
2. Suma dos números secuencialmente
public ListNode addTwoNumbersExt (ListNode l1, ListNode l2) { //Defina una nueva lista vinculada para almacenar los resultados del cálculo. El nodo principal se crea de forma predeterminada, por lo que el valor predeterminado es 0. Temperatura de ListNode = nuevo ListNode(0); ListNode nodo = temp; // Recuerda el primer nodo int carry = 0;//carry, es decir, si la suma es mayor que 10, sumamos 1 al siguiente dígito, el valor predeterminado es 0 Stack<Integer> stackL1 = new Stack<>() // Usa la pila para recibir datos de la lista enlazada l1. Stack<Integer> stackL2 = new Stack<>();//Utiliza la pila para recibir datos de la lista enlazada l2 // Recorre las dos listas enlazadas y agrégalas a la pila mientras (l1 != nulo || l2 != nulo) { // Determina si la lista enlazada l1 está vacía. De lo contrario, apunta al siguiente nodo. si (l1! = nulo) { pilaL1.push(l1.val); l1 = l1.siguiente; } // Determina si la lista enlazada l2 está vacía. De lo contrario, señala el siguiente nodo. si (l2! = nulo) { pilaL2.push(l2.val); l2 = l2.siguiente; } } // recorre ambas pilas mientras (!stackL1.empty() || !stackL2.empty()) { int x = (!stackL1.empty()) ? stackL1.pop() : 0;// Si la pila stackL1 no es nula, extrae la pila; de lo contrario, es 0 int y = (!stackL2.empty()) ? stackL2.pop() : 0;// Si la pila stackL2 no es nula, extraiga la pila; de lo contrario, es 0 int sum = x y carry;//Debido a que se agregan dígitos individuales, el índice máximo es igual a 19 y se debe agregar el acarreo // Almacena el resultado en la lista vinculada, pero debe ser módulo 10. Siempre que sea mayor o igual a 10, obtendrá un solo dígito. temp.next = nuevo ListNode(suma % 10); temp = temp.next;//apunta al siguiente nodo // Encuentra el acarreo. Si es mayor que 10, puedes encontrar que el acarreo es 1; de lo contrario, es 0. llevar = suma / 10; } // Puede haber una situación en la que el cálculo del valor final de las dos listas vinculadas sea mayor o igual a 10 y ambas listas vinculadas estén vacías, por lo que el acarreo debe colocarse en el último nodo si (llevar > 0) { temp.next = nuevo ListNode(llevar); temp = temp.siguiente; } // Debido a que el nodo principal se crea de forma predeterminada, tenemos que comenzar desde el siguiente en la lista enlazada temporal. devolver nodo.siguiente; }
En lugar de almacenar en orden inverso, debe encontrar una manera de cambiarlo al orden inverso. Puede usar el método de orden inverso que viene con la lista vinculada, o puede usar la pila para lograr el orden inverso. el resultado.
4. Cuerda
0.frecuencia
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
3. La subcadena más larga sin caracteres repetidos.
1. Ventana corredera
1. Premisa ideológica
Supongamos que seleccionamos el k-ésimo carácter en la cadena como posición inicial y obtenemos la posición final de la subcadena más larga que no contiene caracteres repetidos como r_k. Luego, cuando seleccionamos el k 1º carácter como posición inicial, los caracteres de k 1 a r_k obviamente no se repiten, y dado que falta el k-ésimo carácter original, podemos intentar continuar aumentando r_k hasta la derecha hasta que aparezcan caracteres repetidos en el lado.
2. Pensamientos concretos
De esta forma, podemos utilizar la "ventana deslizante" para solucionar este problema: Usamos dos punteros para representar una subcadena (los límites izquierdo y derecho) de una cadena. El puntero izquierdo representa la "posición inicial de la subcadena enumerada" mencionada anteriormente, y el puntero derecho es r_k mencionado anteriormente; En cada paso de la operación, moveremos el puntero izquierdo un espacio hacia la derecha, lo que indica que comenzamos a enumerar el siguiente carácter como posición inicial, y luego podemos continuar moviendo el puntero derecho hacia la derecha, pero necesitamos asegúrese de que los dos punteros correspondan. No hay caracteres repetidos en la subcadena. Una vez completado el movimiento, esta subcadena corresponde a la subcadena más larga que comienza desde el puntero izquierdo y no contiene caracteres repetidos. Registramos la longitud de esta subcadena; Al final de la enumeración, la longitud de la subcadena más larga que encontramos es la respuesta.
3. Tabla hash para determinar caracteres repetidos
También necesitamos usar una estructura de datos para determinar si hay caracteres repetidos. La estructura de datos comúnmente utilizada es un conjunto hash. Cuando el puntero izquierdo se mueve hacia la derecha, eliminamos un carácter del conjunto hash. a la derecha, al movernos, agregamos un carácter al conjunto de hash.
4.Código
solución de clase { public int lengthOfLongestSubstring(String s) { // Colección hash, registra si cada carácter aparece o no Set<Carácter> occ = new HashSet<Carácter>(); int n = s.longitud(); // El puntero derecho, el valor inicial es -1, lo que equivale a que estemos en el lado izquierdo del límite izquierdo de la cadena y aún no hayamos comenzado a movernos. int rk = -1, respuesta = 0; para (int i = 0; i < n; i) { si (yo! = 0) { //Mueve el puntero izquierdo un espacio hacia la derecha y elimina un carácter occ.remove(s.charAt(i - 1)); } mientras (rk 1 < n && !occ.contains(s.charAt(rk 1))) { //Sigue moviendo el puntero derecho occ.add(s.charAt(rk 1)); rk; } // Los caracteres i a rk son una subcadena de caracteres extremadamente larga y no repetitiva ans = Math.max(ans, rk - i 1); } volver y; } }
5. Complejidad
Tiempo: O (N), donde N es la longitud de la cadena, espacio: O (∣Σ∣), ∣Σ∣ representa el tamaño del conjunto de caracteres
5. La subcadena palíndromo más larga.
1. Programación dinámica dp
1. Pensamientos
Para una subcadena, si es una cadena palíndromo y la longitud es mayor que 2, después de eliminar la primera y las dos últimas letras, sigue siendo una cadena palíndromo.
2. Ecuación de transición de estado
P(i,j)=P(i 1,j−1)∧(Si == Sj)
3. Condiciones de contorno
P(i,i)=carácter único verdadero P(i,i 1)=(Si==Si 1) dos caracteres
4.Código
solución de clase { cadena pública palíndromo más largo (cadena s) { int n = s.longitud(); booleano[][] dp = nuevo booleano[n][n]; Cadena ans = ""; para (int k = 0; k < n; k) { para (int i = 0; i k < n; i) { int j = yo k; // Tratar primero dos situaciones críticas if (k == 0) { // Cuando k = 0, j = i, dp [i] [j] es equivalente a un carácter, que debe ser una cadena palíndromo dp[i][j] = verdadero; } else if (k == 1) { // Cuando k = 1, j = i 1, dp [i] [j] equivale a dos caracteres consecutivos. Cuando los dos caracteres son iguales, debe ser una cadena palíndromo. dp[i][j] = (s.charAt(i) == s.charAt(j)); } else { // Cuando k>1, la ecuación de transferencia de la programación dinámica debe satisfacerse: P(i,j)=P(i 1,j−1)∧(Si == Sj) dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i 1][j - 1]); } if (dp[i][j] && k 1 > ans.length()) { //Actualiza la longitud de la cadena palíndromo ans = s.substring(i, i k 1); //Preste atención al uso específico del método de subcadena } } } volver y; } }
5. Complejidad
Complejidad del tiempo: O (n ^ 2), donde n es la longitud de la cadena. El número total de estados en la programación dinámica es O (n ^ 2) y, para cada estado, el tiempo que necesitamos para transferir es O (1). Complejidad del espacio: O (n ^ 2), es decir, el espacio requerido para almacenar el estado de programación dinámica
2. Ampliación del centro
1. Requisitos previos
Según la ecuación de transición de estados, se puede encontrar que todos los estados tienen posibilidades únicas durante la transición. En otras palabras, podemos comenzar a "expandirnos" desde cada caso límite y también podemos obtener las respuestas correspondientes a todos los estados.
La subcadena correspondiente al "caso límite" es en realidad el "centro palíndromo" de la cadena palíndromo que "expandimos"
2.Esencia
Enumere todos los "centros de palíndromo" e intente "expandirlos" hasta que no se pueda expandir. La longitud de la cadena de palíndromo en este momento es la longitud de cadena de palíndromo más larga bajo este "centro de palíndromo".
3.Código
solución de clase { cadena pública palíndromo más largo (cadena s) { si (s == nulo || s.length() < 1){ devolver ""; } int start = 0;//Inicializa el punto inicial y el punto final de la subcadena palíndromo más grande extremo int = 0; // Recorre cada posición y úsala como posición central para (int i = 0; i < s.length(); i ) { // Obtener las longitudes de las subcadenas palíndromos pares e impares respectivamente int len_odd = expandCenter(s,i,i); int len_even = expandCenter(s,i,i 1); int len = Math.max(len_odd,len_even); // Compara la longitud máxima // Calcule el punto inicial y el punto final correspondientes a la subcadena palíndromo más grande y haga un dibujo para determinar si (len > fin - inicio){ // ¿Por qué se necesita len-1 aquí? Explique aquí, porque el bucle for comienza desde 0, // Si es un número impar de palíndromos, suponiendo que hay 3 palíndromos, entonces len = 3, en este momento el centro i es el subíndice 1 (comenzando desde 0), entonces Los resultados de (len-1)/2 y len/2 son ambos 1, porque los números enteros se redondean hacia abajo. // Pero si es un número par de palíndromos, suponiendo que hay 4 palíndromos, entonces len = 4, el centro en este momento es una línea de puntos, pero la posición de i Pero en 1, (porque S se recorre de izquierda a derecha, si es de derecha a izquierda, // La posición de i será 2.) En este momento, (len-1)/2=1, len/2=2 Obviamente, para asegurarnos de que el subíndice sea correcto, lo que necesitamos es. (len-1)/2. En realidad, la razón es que i está una posición a la izquierda de la línea central, //Así que redúcelo en 1. inicio = i - (len - 1)/2; fin = i len/2; } } return s.substring(start,end 1);// Nota: El final 1 aquí se debe a la función de cierre a la izquierda y apertura a la derecha de Java. } private int expandCenter(String s,int left,int right){ //Límites iniciales izquierdo y derecho // Cuando izquierda = derecha, el centro del palíndromo es un carácter y la longitud de la cadena del palíndromo es un número impar. // Cuando derecha = izquierda 1, el centro del palíndromo es un espacio y la longitud de la cadena del palíndromo es un número par. // ¡Al salir del bucle, s.charAt(left) está exactamente satisfecho! = s.charAt(derecha) mientras (izquierda >= 0 && derecha < s.length() && s.charAt(izquierda) == s.charAt(derecha)){ izquierda--; bien ; } return derecha - izquierda - 1 // La longitud de la cadena del palíndromo es derecha-izquierda 1-2 = derecha - izquierda - 1; } }
4. Complejidad
Complejidad del tiempo: O (n ^ 2), donde n es la longitud de la cadena. Hay n y n-1 centros palíndromos de longitud 1 y 2 respectivamente, y cada centro palíndromo se expandirá hacia afuera como máximo O (n) veces. Complejidad espacial: O (1)
3.Carro tirado por caballos de Manacher
5.Búsqueda binaria
0.frecuencia
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
4. Encuentra la mediana de dos matrices de orden positivo.
1. Pensamiento convencional
1. Utilice el método de combinación para fusionar dos matrices ordenadas y obtener una matriz ordenada grande. El elemento en medio de una gran matriz ordenada es la mediana.
2. No es necesario fusionar dos matrices ordenadas, simplemente encuentre la posición de la mediana. Dado que se conocen las longitudes de las dos matrices, también se conoce la suma de los subíndices de las dos matrices correspondientes a la mediana. Mantenga dos punteros, que inicialmente apuntan a la posición del subíndice 0 de las dos matrices. Cada vez, el puntero que apunta al valor más pequeño retrocede un bit (si un puntero ha llegado al final de la matriz, solo necesita mover el. puntero de la otra matriz), hasta alcanzar la posición mediana.
La complejidad temporal de la primera idea es O (m n) y la complejidad espacial es O (m n). Aunque la segunda idea puede reducir la complejidad espacial a O (1), la complejidad temporal sigue siendo O (m n). La complejidad temporal requerida por la pregunta es O (log (m n)), por lo que las dos ideas anteriores no cumplen con la complejidad temporal requerida por la pregunta.
2.Búsqueda binaria: késimo decimal
1. Tres situaciones
Si A[k/2-1] < B[k/2-1], entonces los números menores que A[k/2-1] son como máximo los primeros k/2−1 números de A y los primeros k/ de B 2-1 números, es decir, solo hay k-2 números menores que A[k/2-1] como máximo, por lo que A[k/2-1] no puede ser el k-ésimo número, A[0 ] a A[ k/2−1] también es imposible que sea el k-ésimo número, y todos pueden eliminarse. Si A[k/2-1] > B[k/2-1], entonces se puede excluir de B[0] a B[k/2-1]. Si A[k/2-1] = B[k/2-1], se puede clasificar en el primer caso.
2. Resultados del procesamiento
Después de comparar A[k/2−1] y B[k/2−1], se pueden eliminar k/2 números que no pueden ser el k-ésimo número más pequeño y el rango de búsqueda se reduce a la mitad. Al mismo tiempo, continuaremos realizando una búsqueda binaria en la nueva matriz después de la eliminación y reduciremos el valor de k de acuerdo con la cantidad de números que excluimos. Esto se debe a que los números que excluimos no son mayores que el k-ésimo número más pequeño.
3. Tres situaciones especiales
Si A[k/2−1] o B[k/2−1] está fuera de límites, entonces podemos seleccionar el último elemento de la matriz correspondiente. En este caso, debemos reducir el valor de k según el número de exclusiones, en lugar de restar directamente k/2 de k. Si una matriz está vacía, significa que todos los elementos de la matriz han sido excluidos y podemos devolver directamente el késimo elemento más pequeño de otra matriz. Si k = 1, solo necesitamos devolver el valor mínimo de los primeros elementos de las dos matrices.
4. Algoritmo
solución de clase { público doble findMedianSortedArrays(int[] números1, int[] números2) { int longitud1 = números1.longitud, longitud2 = números2.longitud; int longitud total = longitud1 longitud2; si (longitud total % 2 == 1) { int midIndex = longitud total / 2; doble mediana = getKthElement(nums1, nums2, midIndex 1); mediana de retorno; } demás { int midIndex1 = longitud total / 2 - 1, midIndex2 = longitud total / 2; doble mediana = (getKthElement(nums1, nums2, midIndex1 1) getKthElement(nums1, nums2, midIndex2 1)) / 2.0; mediana de retorno; } } público int getKthElement(int[] números1, int[] números2, int k) { /* Idea principal: Para encontrar el késimo (k>1) elemento más pequeño, compare pivot1 = nums1[k/2-1] y pivot2 = nums2[k/2-1] * El "/" aquí significa división entera * Hay elementos nums1[0 .. k/2-2] en nums1 que son menores o iguales que pivot1, un total de k/2-1 * Hay elementos nums2[0 .. k/2-2] en nums2 que son menores o iguales que pivot2, un total de k/2-1 * Tome pivot = min (pivot1, pivot2), el número total de elementos menores o iguales que pivot en las dos matrices no excederá (k/2-1) (k/2-1) <= k-2 * De esta manera, el pivote en sí solo puede ser el k-1º elemento más pequeño. * Si pivot = pivot1, entonces nums1[0 .. k/2-1] no puede ser el késimo elemento más pequeño. "Elimine" todos estos elementos y use el resto como una nueva matriz nums1 * Si pivot = pivot2, entonces nums2[0 .. k/2-1] no puede ser el késimo elemento más pequeño. "Elimine" todos estos elementos y use el resto como una nueva matriz nums2 * Dado que "eliminamos" algunos elementos (estos elementos son más pequeños que el k-ésimo elemento más pequeño), necesitamos modificar el valor de k, menos el número de elementos eliminados. */ int longitud1 = números1.longitud, longitud2 = números2.longitud; int índice1 = 0, índice2 = 0; int kthElemento = 0; mientras (verdadero) { // Casos límite si (índice1 == longitud1) { devolver números2[índice2 k - 1]; } si (índice2 == longitud2) { devolver números1[índice1 k - 1]; } si (k == 1) { return Math.min(nums1[índice1], números2[índice2]); } // En circunstancias normales, el índice1 se utiliza como punto de partida y se actualiza constantemente. int mitad = k/2; int newIndex1 = Math.min(index1 half, length1) - 1 //La longitud de la matriz puede ser menor que la anterior; int newIndex2 = Math.min(índice2 mitad, longitud2) - 1; int pivot1 = números1[nuevoIndex1], pivot2 = números2[nuevoIndex2]; if (pivot1 <= pivot2) { // Combina las dos situaciones k -= (newIndex1 - index1 1); //El índice1 a continuación también cambia al mismo tiempo, esta es la longitud que se resta índice1 = nuevoÍndice1 1; } demás { k -= (nuevoÍndice2 - índice2 1); índice2 = nuevoÍndice2 1; } } } }
5. Complejidad
Complejidad del tiempo: O (log (m n)), donde myn son las longitudes de las matrices nums1 y nums2 respectivamente. Inicialmente, hay k=(m n)/2 o k=(m n)/2 1. Cada ronda del bucle puede reducir el rango de búsqueda a la mitad, por lo que la complejidad del tiempo es O (log (m n)). Complejidad espacial: O(1).
3. Divida la matriz
1. El papel de la mediana
Dividir un conjunto en dos subconjuntos de igual longitud, de modo que los elementos de un subconjunto sean siempre más grandes que los elementos del otro subconjunto.
2. Dos situaciones
Cuando la longitud total de A y B es un número par, si se puede confirmar: len(parte_izquierda)=len(parte_derecha) max(parte_izquierda)≤min(parte_derecha) Entonces, todos los elementos en {A,B} se han dividido en dos partes de la misma longitud, y los elementos de la primera parte siempre son menores o iguales que los elementos de la última parte. La mediana es la media del valor máximo de la parte anterior y el valor mínimo de la siguiente parte.
Cuando la longitud total de A y B es un número impar, si se puede confirmar: len(parte_izquierda)=len(parte_derecha) 1 max(parte_izquierda)≤min(parte_derecha) Entonces, todos los elementos en {A,B} se han dividido en dos partes, la primera parte tiene un elemento más que la última, y los elementos de la primera parte siempre son menores o iguales que los elementos de la última parte. . La mediana es el valor máximo de la parte anterior.
3. Fusionar las dos situaciones.
Para garantizar estas dos condiciones, solo asegúrese de: i j=m−i n−j (cuando m n es un número par) o i j=m−i n−j 1 (cuando m n es un número impar). El lado izquierdo del signo igual es el número de elementos de la parte anterior y el lado derecho del signo igual es el número de elementos de la última parte. Mueve todos i y j al lado izquierdo del signo igual y obtenemos i j = (m n 1)/2. El resultado fraccionario aquí solo conserva la parte entera
Se estipula que la longitud de A es menor o igual que la longitud de B, es decir, m≤n. De esta manera, para cualquier i∈[0,m], hay j=(m n 1)/2−i∈[0,n]. Si la longitud de A es mayor, solo necesita intercambiar A y B. Si m> n, entonces el j resultante puede ser un número negativo.
B[j−1]≤A[i] y A[i−1]≤B[j], es decir, el valor máximo de la primera parte es menor o igual que el valor mínimo de la última parte
4. Simplifique el análisis
Supongamos que A[i−1],B[j−1],A[i],B[j] siempre existen. Para condiciones críticas como i=0, i=m, j=0, j=n, solo necesitamos estipular que A[−1]=B[−1]=−∞, A[m]=B[n ]= ∞ es suficiente. Esto también es relativamente intuitivo: cuando una matriz no aparece en la parte anterior, el valor correspondiente es infinito negativo, lo que no afectará el valor máximo de la parte anterior, cuando una matriz no aparece en la última parte; es infinito positivo no afectará el valor mínimo de la última parte.
5. trabajo
Encuentre i en [0,m] tal que B[j-1]≤A[i] y A[i-1]≤B[j], donde j=(m n 1)/2−i Es equivalente a encontrar i en [0,m] de modo que A[i-1]≤B[j]
6. Algoritmo
solución de clase { público doble findMedianSortedArrays(int[] números1, int[] números2) { if (números1.longitud > números2.longitud) { devolver findMedianSortedArrays(nums2, nums1); } int m = números1.longitud; int n = números2.longitud; int izquierda = 0, derecha = m, ansi = -1; //mediana1: el valor máximo de la parte anterior //mediana2: el valor mínimo de la última parte int mediana1 = 0, mediana2 = 0; mientras (izquierda <= derecha) { // La parte anterior contiene nums1[0 .. i-1] y nums2[0 .. j-1] // La última parte contiene nums1[i .. m-1] y nums2[j .. n-1] int i = (izquierda derecha) / 2; //dicotomía, i comienza desde la mitad del intervalo int j = (m n 1) / 2 - i // La operación de 1 combina el número total de números pares e impares en un solo caso. //nums_im1, nums_i, nums_jm1, nums_j representan respectivamente nums1[i-1], nums1[i], nums2[j-1], nums2[j] // Cuando una matriz no aparece en la parte anterior, el valor correspondiente es infinito negativo, lo que no afectará el valor máximo de la parte anterior. int nums_im1 = (i == 0? Integer.MIN_VALUE: nums1[i - 1]); // Cuando una matriz no aparece en la última parte, el valor correspondiente es infinito positivo, lo que no afectará el valor mínimo de la última parte. int nums_i = (i == m? Integer.MAX_VALUE: nums1[i]); int nums_jm1 = (j == 0? Integer.MIN_VALUE: nums2[j - 1]); int nums_j = (j == n? Integer.MAX_VALUE: nums2[j]); si (nums_im1 <= nums_j) { ansi = yo; mediana1 = Math.max(nums_im1, nums_jm1); mediana2 = Math.min(nums_i, nums_j); izquierda = yo 1; } demás { derecha = yo - 1; } } retorno (m n) % 2 == 0 ? (mediana1 mediana2) / 2.0: mediana1; } }
7. Complejidad
Complejidad del tiempo: O (log min (m, n)), donde myn son las longitudes de las matrices nums1 y nums2 respectivamente Complejidad espacial: O (1)
6.árbol
0.frecuencia
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
1. árbol binario
0. Recorrer y organizar plantillas comunes
144. Recorrido previo al pedido del árbol binario
1. Iteración
1. Pensamientos
Similar a la iteración del recorrido en orden, con ligeros cambios.
2.Código
solución de clase { Lista pública <Integer> preorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); // Stack se implementa en función de matrices dinámicas, mientras que LinkedList se implementa en función de listas enlazadas bidireccionales. LinkedList es más eficiente para agregar, eliminar y modificar que Stack. Deque<TreeNode> stk = new LinkedList<TreeNode>();//Cola de doble extremo //El nodo no está vacío o la pila no está vacía y el ciclo continúa mientras (raíz != nulo || !stk.isEmpty()) { mientras (raíz! = nulo) { res.add(root.val);// Se accede directamente al nodo raíz y luego se inserta en la pila stk.push(raíz); root = root.left;//Después de que finalice el nodo raíz, acceda a su hijo izquierdo y conviértase en el nuevo nodo raíz } root = stk.pop();// Al salir del bucle, se accede a todos los hijos de la izquierda y luego se accede al hijo derecho raíz = raíz.derecha; } devolver resolución; } }
2.1 Pitón
clase Solución: def preorderTraversal(self, raíz: TreeNode) -> Lista[int]: res = lista() si no es root: devolver resolución pila = [] nodo=raíz while pila o nodo:#El nodo no está vacío o la pila no está vacía y el ciclo continúa mientras nodo: res.append(node.val) #Se accede directamente al nodo raíz y luego se inserta en la pila pila.append(nodo) node = node.left #Después de que finalice el nodo raíz, acceda a su hijo izquierdo y conviértase en el nuevo nodo raíz node = stack.pop() #Al saltar del bucle, se accede a todos los hijos de la izquierda y luego se accede al hijo derecho nodo = nodo.derecha devolver resolución
3.Otro método
Comenzando desde el nodo raíz, cada iteración extrae el elemento superior de la pila actual y empuja su nodo hijo dentro de la pila, primero presionando el hijo derecho y luego el hijo izquierdo.
solución de clase { Lista pública <Integer> preorderTraversal (raíz de TreeNode) { LinkedList<TreeNode> pila = nueva LinkedList<>(); LinkedList<Integer> salida = nueva LinkedList<>(); si (raíz == nulo) { salida de retorno; } pila.add(raíz); mientras (!stack.isEmpty()) { TreeNode node = stack.pollLast();//Recuperar y eliminar el último elemento de esta lista salida.add(nodo.val); if (node.right != null) {// Solo inserte un hijo derecho e izquierdo del nodo actual en la pila, independientemente de los demás pila.add(nodo.derecha); } si (nodo.izquierda! = nulo) { pila.add(nodo.izquierda); } } salida de retorno; } }
4. Complejidad
Tiempo: O(n), Espacio: O(n)
2.Morris transversal
1. Pensamientos
1. Acceda a los nodos predecesores del recorrido de pedido anticipado desde el nodo actual hacia abajo, y cada nodo predecesor se visita exactamente dos veces.
2. Primero comience desde el nodo actual, camine un paso hacia el hijo izquierdo y luego visite todo el camino hacia abajo a lo largo del hijo derecho hasta llegar a un nodo hoja (el nodo predecesor transversal en orden del nodo actual), por lo que actualizamos el genera y crea un predecesor de pseudo borde. right = root actualiza el siguiente punto de este predecesor. Si visitamos el nodo predecesor por segunda vez, dado que ya apunta al nodo actual, eliminamos el pseudo borde y pasamos al siguiente vértice.
3. Si el movimiento hacia la izquierda en el primer paso no existe, actualice directamente la salida y muévase hacia la derecha.
2.Código
solución de clase { Lista pública <Integer> preorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); TreeNode predecesor = null;// El nodo predecesor del nodo actual, el nodo más a la derecha del subárbol izquierdo mientras (raíz! = nulo) { // Dependiendo de si hay un hijo izquierdo, determine si buscar su nodo predecesor o acceder directamente al hijo derecho si (raíz.izquierda! = nulo) { // Primero busque el predecesor, luego asigne un valor a su hijo derecho y luego recorra el hijo izquierdo del nodo actual // El nodo predecesor es el nodo raíz actual, da un paso hacia la izquierda y luego continúa caminando hacia la derecha hasta que ya no pueda avanzar. predecesor = raíz.izquierda; while (predecesor.derecho! = nulo && predecesor.derecho! = raíz) { predecesor = predecesor.derecho; } // Dependiendo de si el hijo derecho del predecesor está vacío, determine si acceder directamente al subárbol izquierdo y luego asignar el valor, o si se completa el acceso al subárbol izquierdo. // Deje que el puntero derecho del predecesor apunte a la raíz y continúe atravesando el subárbol izquierdo if (predecesor.derecho == nulo) { res.add(raíz.val); predecesor.derecho = raíz; raíz = raíz.izquierda; } // Indica que se ha visitado el subárbol izquierdo. Necesitamos desconectar el enlace y continuar visitando el subárbol derecho. demás { predecesor.derecho = nulo; raíz = raíz.derecha; } } // Si no queda ningún hijo, accede directamente al hijo derecho demás { res.add(raíz.val); raíz = raíz.derecha; } } devolver resolución; } }
2.1 Pitón
clase Solución: def preorderTraversal(self, raíz: TreeNode) -> Lista[int]: res = lista() si no es root: devolver resolución p1 = raíz mientras p1: p2 = p1.left #El nodo predecesor del nodo actual, el nodo más a la derecha del subárbol izquierdo si p2: # Según si hay un hijo izquierdo, determine si buscar su nodo predecesor o acceder directamente al hijo derecho mientras p2.right y p2.right != p1: p2 = p2.derecha # De acuerdo con si el hijo derecho de p2 está vacío, determine si acceder directamente al subárbol izquierdo y luego asignar el valor, o si se completa el acceso al subárbol izquierdo. si no p2.right:#Deje que el puntero derecho de p2 apunte a p1 y continúe atravesando el subárbol izquierdo res.añadir(p1.val) p2.derecha = p1 p1 = p1.izquierda continuar else: #Indica que se ha visitado el subárbol izquierdo, debemos desconectar el enlace y continuar visitando el subárbol derecho p2.right = Ninguno else: #Si no queda ningún hijo izquierdo, accede directamente al hijo derecho res.añadir(p1.val) p1 = p1.derecha devolver resolución
3. Complejidad
Tiempo: O(n), Espacio: O(1)
94. Recorrido en orden del árbol binario
1. recursividad
1. Pensamientos
Este árbol se recorre visitando el subárbol izquierdo - nodo raíz - subárbol derecho, y cuando visitamos el subárbol izquierdo o el subárbol derecho, atravesamos de la misma manera hasta atravesar el árbol completo. Por lo tanto, todo el proceso transversal es naturalmente recursivo. Podemos utilizar directamente funciones recursivas para simular este proceso.
2.Código
solución de clase { Lista pública <Integer> inorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); orden(raíz, res); devolver resolución; } orden vacía pública (raíz de TreeNode, Lista <Integer> res) { si (raíz == nulo) { devolver; } orden(raíz.izquierda, res); res.add(raíz.val); inorder(raíz.derecha, res); } }
2.1 Pitón
clase Solución: def preorderTraversal(self, raíz: TreeNode) -> Lista[int]: def dfs(cur): si no es curado: devolver # Recursión de pedidos anticipados res.append(cur.val) dfs(cur.izquierda) dfs (derecha actual) # # Recursión en orden #dfs(cur.izquierda) # res.append(cur.val) #dfs(derecha actual) # # Recursión posterior al pedido #dfs(cur.izquierda) #dfs(derecha actual) # res.append(cur.val) res = [] dfs(raíz) devolver resolución
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos del árbol binario. En el recorrido del árbol binario, cada nodo será visitado una vez y sólo una vez.
Complejidad espacial: O (n). La complejidad del espacio depende de la profundidad de la pila recursiva, y la profundidad de la pila alcanzará el nivel O (n) cuando el árbol binario sea una cadena.
2.Iteración de la pila
1.Ideas
También podemos implementar la función recursiva del método 1 de forma iterativa. Los dos métodos son equivalentes. La diferencia es que una pila se mantiene implícitamente durante la recursión y necesitamos simular explícitamente esta pila durante la iteración.
2.Código
solución de clase { Lista pública <Integer> inorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); // Stack se implementa en función de matrices dinámicas, mientras que LinkedList se implementa en función de listas enlazadas bidireccionales. LinkedList es más eficiente para agregar, eliminar y modificar que Stack. Deque<TreeNode> stk = new LinkedList<TreeNode>();//Cola de doble extremo //El nodo no está vacío o la pila no está vacía y el ciclo continúa mientras (raíz != nulo || !stk.isEmpty()) { //Recorre primero el hijo izquierdo hasta que esté vacío y finaliza el ciclo mientras (raíz! = nulo) { stk.push(raíz); raíz = raíz.izquierda; } // Al saltar del bucle, el hijo izquierdo está vacío. En este momento, el que salió de la pila es equivalente al nodo raíz, y luego es el turno del hijo derecho. raíz = stk.pop(); res.add(raíz.val); raíz = raíz.derecha; } devolver resolución; } }
2.1 Pitón
clase Solución: def inorderTraversal(self, raíz: TreeNode) -> Lista[int]: res = lista() si no es root: devolver resolución pila = [] nodo=raíz while pila o nodo:#El nodo no está vacío o la pila no está vacía y el ciclo continúa mientras nodo: stack.append(nodo) #Recorre primero el hijo izquierdo hasta que esté vacío y finaliza el ciclo nodo = nodo.izquierda node = stack.pop() #El hijo izquierdo está vacío al saltar del bucle. En este momento, el que salió de la pila es equivalente al nodo raíz, y luego es el turno del hijo derecho. res.append(nodo.val) nodo = nodo.derecha devolver resolución
3. Complejidad
Tiempo: O(n), Espacio: O(n)
3.Recorrido en orden de Morris
1. Pensamientos
1. Si x no tiene un hijo izquierdo, primero agregue el valor de x a la matriz de respuesta y luego acceda al hijo derecho de x, es decir, x = x.right
2. Si x tiene un hijo izquierdo, busque el nodo más a la derecha en el subárbol izquierdo de x (es decir, el último nodo del subárbol izquierdo en el recorrido en orden y el nodo predecesor de x en el recorrido en orden) , que registramos como predecesor. Dependiendo de si el hijo derecho del predecesor está vacío, realice las siguientes operaciones
3. Si el hijo derecho del predecesor está vacío, apunte su hijo derecho a x y luego acceda al hijo izquierdo de x, es decir, x = x.left
4. Si el hijo derecho del predecesor no está vacío, entonces su hijo derecho apunta a x en este momento, lo que indica que hemos atravesado el subárbol izquierdo de x. Dejamos el hijo derecho del predecesor vacío y sumamos el valor de x. a la matriz de respuestas y luego acceda al hijo derecho de x, es decir, x=x.right
5. Repita las operaciones anteriores hasta visitar el árbol completo.
6. De hecho, haremos un paso más en todo el proceso: suponiendo que el nodo actualmente atravesado es x, apunte el hijo derecho del nodo más a la derecha en el subárbol izquierdo de x hacia x, de modo que después del recorrido del subárbol izquierdo sea completado, usaremos este puntero Devoluciones
2.Código
solución de clase { Lista pública <Integer> inorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); TreeNode predecesor = null;// El nodo predecesor del nodo actual, el nodo más a la derecha del subárbol izquierdo mientras (raíz! = nulo) { // Dependiendo de si hay un hijo izquierdo, determine si buscar su nodo predecesor o acceder directamente al hijo derecho si (raíz.izquierda! = nulo) { // Primero busque el predecesor, luego asigne un valor a su hijo derecho y luego recorra el hijo izquierdo del nodo actual // El nodo predecesor es el nodo raíz actual, da un paso hacia la izquierda y luego continúa caminando hacia la derecha hasta que ya no pueda avanzar. predecesor = raíz.izquierda; while (predecesor.derecho! = nulo && predecesor.derecho! = raíz) { predecesor = predecesor.derecho; } //Determine si la asignación o el acceso al subárbol izquierdo finaliza en función de si el hijo derecho del predecesor está vacío. // Deje que el puntero derecho del predecesor apunte a la raíz y continúe atravesando el subárbol izquierdo if (predecesor.derecho == nulo) { predecesor.derecho = raíz; raíz = raíz.izquierda; } // Indica que se ha visitado el subárbol izquierdo. En este momento, visite el nodo raíz, luego desconecte el enlace y continúe visitando el subárbol derecho. demás { res.add(raíz.val); predecesor.derecho = nulo; raíz = raíz.derecha; } } // Si no queda ningún hijo, accede directamente al hijo derecho demás { res.add(raíz.val); raíz = raíz.derecha; } } devolver resolución; } }
2.1 Pitón
clase Solución: def inorderTraversal(self, raíz: TreeNode) -> Lista[int]: res = lista() si no es root: devolver resolución p1 = raíz mientras p1: p2 = p1.left #El nodo predecesor del nodo actual, el nodo más a la derecha del subárbol izquierdo si p2: # Según si hay un hijo izquierdo, determine si buscar su nodo predecesor o acceder directamente al hijo derecho mientras p2.right y p2.right != p1: p2 = p2.derecha # De acuerdo con si el hijo derecho de p2 está vacío, determine si acceder directamente al subárbol izquierdo y luego asignar el valor, o si se completa el acceso al subárbol izquierdo. si no p2.right:#Deje que el puntero derecho de p2 apunte a p1 y continúe atravesando el subárbol izquierdo p2.derecha = p1 p1 = p1.izquierda continuar de lo contrario: #Indica que se ha visitado el subárbol izquierdo. En este momento, visite el nodo raíz, luego desconecte el enlace y continúe visitando el subárbol derecho. res.añadir(p1.val) p2.right = Ninguno else: #Si no queda ningún hijo izquierdo, accede directamente al hijo derecho res.añadir(p1.val) p1 = p1.derecha devolver resolución
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol de búsqueda binario. Cada nodo en el recorrido de Morris será visitado dos veces, por lo que la complejidad del tiempo total es O (2n) = O (n)
Complejidad espacial: O (1)
4. Método de marcado de colores (universal)
1. Pensamientos
0. Tiene la eficiencia del método de iteración de pila y es tan conciso y fácil de entender como el método recursivo. Más importante aún, este método puede escribir código completamente consistente para el recorrido de pedido previo, medio y posterior.
1. Utilice colores para marcar el estado de los nodos. Los nodos nuevos son blancos y los nodos visitados son grises. Si el nodo encontrado es blanco, márquelo como gris y luego inserte su nodo secundario derecho, él mismo y el nodo secundario izquierdo en la pila en secuencia. Si el nodo encontrado es gris, se genera el valor del nodo
2. Si desea implementar un recorrido de pedido previo y posterior, solo necesita ajustar el orden de apilamiento de los nodos secundarios izquierdo y derecho.
2.Código
clase Solución: def inorderTraversal(self, raíz: TreeNode) -> Lista[int]: BLANCO, GRIS = 0, 1 #BLANCO no ha sido visitado, GRIS ha visitado res = [] pila = [(BLANCO, raíz)] mientras se apila: color, nodo = pila.pop() si el nodo es Ninguno: continuar si color == BLANCO: # El orden del orden medio es izquierda raíz derecha, el orden de inserción en la pila es derecha raíz izquierda, ajuste el orden para completar otros recorridos stack.append((BLANCO, nodo.derecha)) stack.append((GRIS, nodo)) #El nodo actual se vuelve gris y ha sido visitado pila.append((BLANCO, nodo.izquierda)) demás: res.append(nodo.val) devolver resolución
3. Complejidad
Tiempo: O(n), Espacio: O(n)
145.Recorrido posterior al pedido del árbol binario
1. Iteración
1.Ideas
Al regresar al nodo raíz, marque el nodo actual para determinar si regresar desde el subárbol izquierdo o desde el subárbol derecho. Cuando regrese desde el subárbol derecho, comience a atravesar el nodo.
2.Código
solución de clase { Lista pública <Integer> postorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); Pila<TreeNode> pila = nueva Pila<>(); // El nodo previo se utiliza para registrar el nodo visitado anteriormente, distinguiendo entre devolver el nodo raíz del subárbol izquierdo o del subárbol derecho. TreeNode pre = nulo; while(raíz!=nulo || !pila.empty()){ mientras(raíz!=nulo){ stack.push(root); //Empuja continuamente el nodo izquierdo en la pila raíz = raíz.izquierda; } root = stack.peek();//Regresar desde el subárbol izquierdo, solo obtener el nodo raíz y no mostrarlo, aún no ha sido visitado if(root.right==null || root.right==pre){ //Si el nodo derecho está vacío o se ha visitado el nodo derecho res.add(root.val); //Puedes acceder al nodo raíz en este momento pre = raíz; pila.pop(); root = null; // En este momento, no inserte el subárbol izquierdo en la pila en el siguiente ciclo. Ya ha sido insertado y determine directamente el elemento superior de la pila. }demás{ root = root.right; //No lo saques de la pila todavía, empuja su nodo derecho hacia la pila } } devolver resolución; } }
2.1 Pitón
clase Solución: def postorderTraversal(self, raíz: TreeNode) -> Lista[int]: si no es root: lista de retorno() res = lista() pila = lista() prev = Ninguno #El nodo anterior se utiliza para registrar el nodo visitado anteriormente, lo que distingue si se devuelve el nodo raíz del subárbol izquierdo o del subárbol derecho. mientras que root o pila: mientras que raíz: stack.append(root) # Empuje continuamente el nodo izquierdo hacia la pila raíz = raíz.izquierda #Esta aplicación se ve, pero no existe tal método en la lista. Solo puede aparecer primero y luego agregar. root = stack.pop() #Regresa desde el subárbol izquierdo, aún no ha sido visitado y se agregará a la pila más adelante. si no es root.right o root.right == prev:#Si el nodo correcto está vacío o se ha visitado el nodo correcto res.append(root.val) #Puedes acceder al nodo raíz en este momento prev = root #Registrar el nodo actual root = Ninguno # En este momento, no inserte el subárbol izquierdo en la pila en el siguiente ciclo. Ya ha sido insertado y determina directamente el elemento superior de la pila. demás: stack.append(root) #Si no se ha accedido antes, agréguelo a la pila raíz = raíz.derecha devolver resolución
3. Transformar el recorrido de reserva
//Modifica el código para escribir nodos en la lista vinculada de resultados en el código transversal del pedido previo: cambia la inserción al final de la cola por la inserción al principio de la cola // Modifica la lógica de verificar primero el nodo izquierdo y luego el nodo derecho en el código transversal de preorden: cambia para verificar primero el nodo derecho y luego el nodo izquierdo. // El recorrido de preorden son las raíces izquierda y derecha, el orden inverso es la raíz de derecha a izquierda y el recorrido de posorden son las raíces izquierda y derecha. solución de clase { Lista pública <Integer> postorderTraversal (raíz de TreeNode) { ListaEnlazada res = nueva ListaEnlazada(); Pila<TreeNode> pila = nueva Pila<>(); TreeNode pre = nulo; while(raíz!=nulo || !pila.empty()){ mientras(raíz!=nulo){ res.addFirst(root.val); //Inserta el primero de la cola pila.push(raíz); root = root.right; //Primero a la derecha y luego a la izquierda; } raíz = pila.pop(); raíz = raíz.izquierda; } devolver resolución; } }
4. Complejidad
Tiempo: O(n), Espacio: O(n)
2.Morris transversal
1.Ideas
1. La idea central del recorrido de Morris es utilizar una gran cantidad de punteros libres en el árbol para lograr la máxima reducción de la sobrecarga de espacio. Las reglas posteriores para el recorrido posterior al pedido se resumen a continuación:
2. Si el nodo secundario izquierdo del nodo actual está vacío, recorra el nodo secundario derecho del nodo actual.
3. Si el nodo secundario izquierdo del nodo actual no está vacío, busque el nodo predecesor del nodo actual en el recorrido en orden en el subárbol izquierdo del nodo actual.
4. Si el nodo secundario derecho del nodo predecesor está vacío, establezca el nodo secundario derecho del nodo predecesor en el nodo actual y actualice el nodo actual al nodo secundario izquierdo del nodo actual.
5. Si el nodo secundario derecho del nodo predecesor es el nodo actual, restablezca su nodo secundario derecho para que esté vacío. Genere todos los nodos en la ruta desde el nodo secundario izquierdo del nodo actual hasta el nodo predecesor en orden inverso. El nodo actual se actualiza al nodo secundario derecho del nodo actual
2.Código
solución de clase { Lista pública <Integer> postorderTraversal (raíz de TreeNode) { Lista<Integer> res = new ArrayList<Integer>(); si (raíz == nulo) { devolver resolución; } TreeNode p1 = raíz, p2 = nulo //p1 nodo actual, p2 nodo predecesor mientras (p1 != nulo) { p2 = p1.izquierda; si (p2! = nulo) { mientras (p2.derecha! = nulo && p2.derecha! = p1) { p2 = p2.derecha; } si (p2.derecha == nulo) { p2.derecha = p1; p1 = p1.izquierda; continuar; } demás { p2.derecha = nulo; addPath(res, p1.izquierda); } } p1 = p1.derecha; } addPath(res, raíz); devolver resolución; } public void addPath(List<Integer> res, nodo TreeNode) { recuento int = 0; mientras (nodo!= nulo) { contar; res.add(nodo.val); nodo = nodo.derecha; } //Invertir los nodos recién agregados int izquierda = res.size() - contar, derecha = res.size() - 1; mientras (izquierda <derecha) { int temp = res.get(izquierda); res.set(izquierda, res.get(derecha)); res.set(derecha, temperatura); izquierda ; bien--; } } }
2.1 Pitón
clase Solución: def postorderTraversal(self, raíz: TreeNode) -> Lista[int]: def addPath(nodo: TreeNode): contar = 0 mientras nodo: contar = 1 res.append(nodo.val) nodo = nodo.derecha #Invertir los nodos recién agregados i, j = len(res) - contar, len(res) - 1 mientras yo <j: res[i], res[j] = res[j], res[i] #No se requieren variables intermedias yo=1 j-= 1 si no es root: lista de retorno() res = lista() p1 = raíz mientras p1: p2 = p1.left #p2 nodo predecesor si p2: mientras p2.right y p2.right != p1: p2 = p2.derecha si no p2.derecha: p2.derecha = p1 p1 = p1.izquierda continuar demás: p2.right = Ninguno agregarRuta(p1.izquierda) p1 = p1.derecha agregar ruta (raíz) devolver resolución
3. Complejidad
Tiempo: O(n), Espacio: O(1)
102.Recorrido de orden de niveles del árbol binario
0.Título
Devuelve los valores de los nodos del recorrido de orden de nivel del árbol binario y los nodos de cada nivel se colocan juntos.
1.Ideas
1. El método más simple es usar una tupla (nodo, nivel) para representar el estado, que representa un nodo y el nivel en el que se encuentra. El valor de nivel de cada nodo recién ingresado es el valor de nivel del nodo principal más uno. . Finalmente, los puntos se clasifican según el nivel de cada punto. Al clasificar, podemos usar una tabla hash para mantener una matriz con el nivel como clave y el valor del nodo correspondiente como valor. presione el nivel de tecla para recuperar todos los valores, simplemente forme la respuesta y devuélvala
2. Considere cómo optimizar la sobrecarga de espacio: cómo implementar esta función sin usar mapeo hash y usar solo un nodo variable para representar el estado.
3. Modifique BFS de una manera inteligente: primero, agregue el elemento raíz a la cola. Cuando la cola no esté vacía, encuentre la longitud Si de la cola actual, luego tome los elementos Si de la cola para expandirlos y luego ingrese el. próxima iteración.
4. La diferencia entre este y BFS es que BFS solo toma un elemento a la vez para expandirse, mientras que aquí toma elementos Si cada vez. En la i-ésima iteración del proceso anterior, elementos Si del i-ésimo nivel de. Se obtiene el árbol binario.
2.Código
solución de clase { Lista pública <Lista <Entero>> orden de nivel (raíz de TreeNode) { Lista<Lista<Integer>> ret = new ArrayList<List<Integer>>(); si (raíz == nulo) { volver atrás; } Cola<TreeNode> cola = nueva LinkedList<TreeNode>(); queue.offer(root);//Agrega el elemento especificado al final de esta lista (el último elemento) mientras (!queue.isEmpty()) { Lista<Integer> nivel = new ArrayList<Integer>(); int currentLevelSize = cola.size(); //Quitar de la cola todos los elementos de la cola actual para (int i = 1; i <= currentLevelSize; i) { Nodo TreeNode = cola.poll(); nivel.add(nodo.val); si (nodo.izquierda! = nulo) { cola.oferta(nodo.izquierda); } si (nodo.derecho! = nulo) { cola.oferta(nodo.derecha); } } ret.add(nivel); } volver atrás; } }
2.1 Pitón
clase Solución: def ordendenivel(self, raíz: TreeNode) -> Lista[Lista[int]]: si no es root: return [] # En casos especiales, regresa directamente si root está vacío de colecciones importar deque # El siguiente es el contenido de la plantilla BFS. La clave de BFS es el uso de colas. capa = deque() Layer.append(root) # Empuja el nodo inicial res = [] # conjunto de resultados mientras capa: cur_layer = [] # Variable temporal, registra los nodos de la capa actual for _ in range(len(layer)): # Atraviesa los nodos de una determinada capa node = Layer.popleft() # Pop el nodo a procesar cur_layer.append(nodo.val) if node.left: # Si el nodo actual tiene nodos izquierdo y derecho, empújelos a la cola. Según el significado de la pregunta, preste atención a los nodos izquierdo y luego al derecho. capa.append(nodo.izquierda) si nodo.derecho: capa.append(nodo.derecha) res.append(cur_layer) # Después de procesar todos los nodos de una determinada capa, inserte los resultados de la capa actual en el conjunto de resultados devolver resolución
Amigos que usan otros idiomas, para estar seguros, el valor de len (capa) debe guardarse antes de atravesar, porque también se agregan elementos a la capa durante el recorrido, y el valor de la capa cambiará en este momento. Quizás Python sea un lenguaje especial; de hecho, cuando se llama a for _ in range(len(layer)), el número de bucles se ha determinado en función de la secuencia correspondiente generada y el cambio en la longitud de la capa en el bucle. no tendrá ningún impacto si se utiliza un bucle while. len(capa) se actualizará cada vez
3. Complejidad
Complejidad del tiempo: cada punto entra y sale de la cola una vez, por lo que la complejidad del tiempo asintótica es O (n)
Complejidad espacial: el número de elementos en la cola no excede n, por lo que la complejidad espacial asintótica es O (n)
104.La profundidad máxima de un árbol binario.
1. recursividad
1.Ideas
1. Si conocemos la profundidad máxima l y r del subárbol izquierdo y del subárbol derecho, entonces la profundidad máxima del árbol binario es max (l, r) 1
2. La profundidad máxima del subárbol izquierdo y del subárbol derecho se puede calcular de la misma manera. Por lo tanto, cuando calculamos la profundidad máxima del árbol binario actual, primero podemos calcular de forma recursiva la profundidad máxima de su subárbol izquierdo y su subárbol derecho, y luego calcular la profundidad máxima del árbol binario actual en tiempo O (1). La recursión sale cuando se alcanza un nodo vacío
2.Código
solución de clase { public int maxDepth (raíz de TreeNode) { if(root==null) return 0;//Cuando el último 1 es un nodo hoja, la altura volverá a 1 return Math.max(maxDepth(root.left),maxDepth(root.right)) 1; } }
2.1 Pitón
clase Solución: def maxDepth(self, raíz: TreeNode) -> int: si no es root: devuelve 0 lefth = self.maxDepth(root.left) #Debes usar self.self cuando te llames a ti mismo derecha = self.maxDepth(raíz.derecha) retorno máximo (izquierda, derecha) 1
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos del árbol binario. Cada nodo se atraviesa solo una vez en la recursividad.
Complejidad del espacio: O (altura), donde la altura representa la altura del árbol binario. Las funciones recursivas requieren espacio de pila, y el espacio de pila depende de la profundidad de la recursividad, por lo que la complejidad del espacio es equivalente a la altura del árbol binario.
2. Búsqueda en amplitud
1.Ideas
La cola de búsqueda en amplitud almacena "todos los nodos de la capa actual". Cada vez que expandimos la siguiente capa, a diferencia de la búsqueda en amplitud que solo elimina un nodo de la cola a la vez, necesitamos eliminar todos los nodos de la cola para la expansión, a fin de garantizar que la cola se expanda cada vez. time Se almacenan todos los nodos de la capa actual, es decir, expandimos capa por capa. Finalmente, usamos una variable ans para mantener el número de expansiones. La profundidad máxima del árbol binario es ans.
2. Algoritmo
solución de clase { public int maxDepth (raíz de TreeNode) { si (raíz == nulo) { devolver 0; } Cola<TreeNode> cola = nueva LinkedList<TreeNode>(); cola.oferta(raíz); int respuesta = 0; mientras (!queue.isEmpty()) { int tamaño = cola.tamaño(); while (tamaño > 0) { //operar una capa de elementos a la vez Nodo TreeNode = cola.poll(); si (nodo.izquierda! = nulo) { cola.oferta(nodo.izquierda); } si (nodo.derecho! = nulo) { cola.oferta(nodo.derecha); } tamaño--; } ans ;// Después de completar una capa de operación, el número de capas es 1 } volver y; } }
2.1 Pitón
importar colecciones clase Solución: def maxDepth(self, raíz: TreeNode) -> int: si no es root: devuelve 0 cola = colecciones.deque() cola.append(raíz) respuesta = 0 mientras cola: respuesta = 1 for _ in range(len(queue)): #Recorre los elementos de un nivel cada vez nodo = cola.popleft() si nodo.izquierda: cola.append(nodo.izquierda) si nodo.derecho: cola.append(nodo.derecha) regresar y
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol binario. El mismo análisis que el método 1, cada nodo solo será visitado una vez
Complejidad del espacio: el consumo de espacio de este método depende de la cantidad de elementos almacenados en la cola, que alcanzará O (n) en el peor de los casos.
226.Invertir un árbol binario
1. recursividad
1. Pensamientos
Comenzamos desde el nodo raíz, atravesamos el árbol de forma recursiva y comenzamos a voltear desde los nodos hoja primero. Si los subárboles izquierdo y derecho de la raíz del nodo actualmente atravesado se han invertido, entonces solo necesitamos intercambiar las posiciones de los dos subárboles para completar la inversión de todo el subárbol con raíz como nodo raíz.
2.Código
solución de clase { árbol público TreeNode invertTree (raíz de TreeNode) { si (raíz == nulo) { devolver nulo; } //Atraviesa el árbol de forma recursiva y comienza a voltear desde los nodos de hoja primero TreeNode izquierda = invertTree(root.left); TreeNode derecha = invertTree(root.right); raíz.izquierda = derecha; raíz.derecha = izquierda; devolver raíz; } }
2.1 pitón
clase Solución: def invertTree(self, raíz: TreeNode) -> TreeNode: si no es root: devolver la raíz #Atraviesa el árbol de forma recursiva y comienza a voltear primero desde los nodos de las hojas. izquierda = self.invertTree(raíz.izquierda) derecha = self.invertTree(raíz.derecha) raíz.izquierda, raíz.derecha = derecha, izquierda devolver la raíz
3. Complejidad
Complejidad del tiempo: O (N), donde N es el número de nodos del árbol binario. Atravesaremos cada nodo del árbol binario y, para cada nodo, intercambiamos sus dos subárboles en tiempo constante.
Complejidad espacial: O (N). El espacio utilizado está determinado por la profundidad de la pila de recursividad, que es igual a la altura del nodo actual en el árbol binario. En circunstancias normales, la altura de un árbol binario tiene una relación logarítmica con el número de nodos, es decir, O (logN). En el peor de los casos, el árbol forma una cadena y la complejidad espacial es O (N)
96. Número de árboles de búsqueda binarios diferentes.
1. Programación dinámica
0.Título
Dado un número entero n, ¿cuántos árboles de búsqueda binarios hay con 1...n como nodos?
1.Ideas
1. Recorra cada número i, use el número como raíz del árbol, use la secuencia 1⋯(i−1) como subárbol izquierdo y use la secuencia (i 1)⋯n como subárbol derecho. Entonces podemos construir recursivamente el subárbol izquierdo y el subárbol derecho de la misma manera.
2. Dado que los valores raíz son diferentes, podemos asegurarnos de que cada árbol de búsqueda binario sea único.
3. El problema original se puede descomponer en dos subproblemas más pequeños y las soluciones a los subproblemas se pueden reutilizar. La programación dinámica se puede utilizar para resolver este problema.
4. Podemos definir dos funciones: G (n): el número de árboles de búsqueda binarios diferentes que pueden formarse mediante una secuencia de longitud n. F (i, n): el número de árboles de búsqueda binarios diferentes con i como raíz y longitud de secuencia n (1≤i≤n). Se puede ver que G(n) es la función que necesitamos resolver
5. El número total de diferentes árboles de búsqueda binarios G (n) es la suma de F (i, n) que atraviesa todos los i (1≤i≤n)
6. Para casos límite, cuando la longitud de la secuencia es 1 (solo la raíz) o 0 (árbol vacío), solo hay una situación, a saber: G (0) = 1, G (1) = 1
7. Seleccione el número i como raíz, luego el conjunto de todos los árboles de búsqueda binarios con la raíz i es el producto cartesiano del conjunto de subárboles izquierdo y el conjunto de subárboles derecho.
8. Combina las dos fórmulas:
2.Código
solución de clase { público int numTrees(int n) { int[] G = nuevo int[n 1]; G[0] = 1; GRAMO[1] = 1; para (int i = 2; i <= n; i) { para (int j = 1; j <= i; j) { G[i] = G[j - 1] * G[i - j]; } } devolver G[n]; } }
2.1 Pitón
clase Solución: def numTrees(self, n): G = [0]*(n 1) #Lista de longitud n 1 G[0], G[1] = 1, 1 para i en el rango (2, n 1): #Atravesar desde G[2] hasta n para j en el rango (1, i 1): #Fórmula de suma correspondiente de 1 a n G[i] = G[j-1] * G[i-j] devolver G[n]
3. Complejidad
Complejidad del tiempo: O (n ^ 2), donde n representa el número de nodos en el árbol de búsqueda binario. La función G (n) tiene un total de n valores que deben resolverse. Cada solución requiere una complejidad de tiempo O (n), por lo que la complejidad de tiempo total es O (n ^ 2).
Complejidad espacial: O (n). Necesitamos espacio O(n) para almacenar la matriz G
2. Número catalán
1.Ideas
1. El valor de la función G(n) obtenido en el método 1 se denomina matemáticamente número catalán Cn
2.Código
solución de clase { público int numTrees(int n) { // Consejo: aquí debemos utilizar el tipo largo para evitar el desbordamiento durante el cálculo. largo C = 1; para (int i = 0; i < n; i) { C = C * 2 * (2 * yo 1) / (yo 2); } devolver (int) C; } }
2.1 Pitón
clase Solución (objeto): def numTrees(self, n): C=1 para i en el rango (0, n): C = C * 2*(2*yo 1)/(yo 2) devolver entero(C)
3. Complejidad
Complejidad del tiempo: O (n), donde n representa el número de nodos en el árbol de búsqueda binario. Sólo necesitamos recorrerlo una vez.
Complejidad espacial: O(1). Solo necesitamos espacio constante para almacenar varias variables.
95. Diferentes combinaciones de árboles de búsqueda binaria.
1. recursividad
0.Título
Dado un número entero n, genere todos los árboles de búsqueda binarios que constan de los nodos 1...n
1.Ideas
1. Supongamos que la longitud de la secuencia actual es n, y si enumeramos el valor del nodo raíz como i, entonces, de acuerdo con las propiedades del árbol de búsqueda binario, podemos saber que el conjunto de valores de nodo del El subárbol izquierdo es [1...i−1] y el conjunto de valores de nodo del subárbol derecho es [1...i−1]. yo 1…n]. En comparación con el problema original, la generación del subárbol izquierdo y el subárbol derecho es un subproblema con una longitud de secuencia reducida, por lo que podemos pensar en utilizar métodos recursivos para resolver este problema.
2. Defina la función generateTrees(inicio, fin) para indicar que el valor actual establecido es [inicio, fin] y devuelva todos los árboles de búsqueda binarios factibles generados por la secuencia [inicio, fin]. De acuerdo con la idea anterior, consideramos que el valor ii en la enumeración [inicio, fin] es la raíz del árbol de búsqueda binario actual, luego la secuencia se divide en dos partes: [inicio, i−1] y [i 1 , fin]. Llamamos a estas dos partes de forma recursiva, es decir, generateTrees (inicio, i - 1) y generateTrees (i 1, end), para obtener todos los subárboles izquierdos factibles y los subárboles derechos factibles. Luego, en el último paso, solo necesitamos recopilar los subárboles izquierdos factibles. subárboles de los subárboles izquierdos factibles Seleccione uno, luego seleccione uno del conjunto de subárboles derechos factibles y empalme con el nodo raíz, y coloque el árbol de búsqueda binario generado en la matriz de respuesta.
3. El punto de entrada de la recursividad es generateTrees (1, n), y el punto de salida es cuando \textit{start}>\textit{end}start>end, el árbol de búsqueda binario actual está vacío, simplemente devuelve un nodo vacío.
2.Código
clase Solución: def generarTrees(self, n: int) -> Lista[TreeNode]: def generarárboles(inicio, fin): si inicio > fin: devolver [Ninguno,] todos los árboles = [] for i in range(start, end 1): # Enumerar nodos raíz factibles #Primero complete la construcción recursiva de los subárboles izquierdo y derecho, y luego use el conjunto completo de subárboles izquierdo y derecho para construir un árbol completo leftTrees = generateTrees(start, i - 1) # Obtiene el conjunto de todos los subárboles izquierdos factibles rightTrees = generateTrees(i 1, end) # Obtener el conjunto de todos los subárboles derechos factibles # Seleccione un subárbol izquierdo del conjunto de subárboles izquierdo, seleccione un subárbol derecho del conjunto de subárboles derecho y empalme con el nodo raíz para l en leftTrees: #Relación del producto de Descartes para r en árboles derechos: currTree = TreeNode(i) #nodo raíz árbolcurr.izquierda = l árbolcurr.derecha = r allTrees.append(currTree) devolver todos los árboles devolver generarTrees(1, n) si n más []
3. Complejidad
Complejidad temporal: la complejidad temporal de todo el algoritmo depende del "número de árboles de búsqueda binarios factibles", y el número de árboles de búsqueda binarios generados para n puntos es equivalente al enésimo "número de Cattleya" en matemáticas, utilizando G_n significa. Se solicita a los lectores que verifiquen los detalles específicos del número de Cattleya por sí mismos. No entraré en detalles aquí y solo daré la conclusión. Generar un árbol de búsqueda binario requiere una complejidad de tiempo O (n). Hay G_n árboles de búsqueda binarios en total, que es O (nG_n).
Complejidad del espacio: hay Gn árboles de búsqueda binarios generados por n puntos, cada árbol tiene n nodos, por lo que el espacio de almacenamiento requiere O (nG_n)
617.Fusionar árboles binarios
1. Búsqueda en profundidad
0.Título
Dados dos árboles binarios, imagina que cuando superpones uno sobre el otro, algunos de los nodos de los dos árboles binarios se superponen. Debes fusionarlos en un nuevo árbol binario. La regla de la fusión es que si dos nodos se superponen, sus valores se agregan como el nuevo valor después de que se fusiona el nodo. De lo contrario, el nodo que no es NULL se utilizará directamente como el nodo del nuevo árbol binario.
1.Ideas
1. Atraviese dos árboles binarios simultáneamente desde el nodo raíz y combine los nodos correspondientes. Los nodos correspondientes de los dos árboles binarios pueden tener las siguientes tres situaciones. Utilice diferentes métodos de fusión para cada situación.
2. Si los nodos correspondientes de los dos árboles binarios están vacíos, los nodos correspondientes del árbol binario fusionado también estarán vacíos.
3. Si solo uno de los nodos correspondientes de los dos árboles binarios está vacío, el nodo correspondiente del árbol binario fusionado será el nodo no vacío.
4. Si los nodos correspondientes de los dos árboles binarios no están vacíos, el valor del nodo correspondiente del árbol binario fusionado es la suma de los valores de los nodos correspondientes de los dos árboles binarios. Los nodos deben fusionarse explícitamente.
5. Después de fusionar un nodo, los subárboles izquierdo y derecho del nodo deben fusionarse respectivamente. Este es un proceso recursivo.
2.Código
solución de clase { Nodo de árbol público mergeTrees (Nodo de árbol t1, Nodo de árbol t2) { si (t1 == nulo) { devolver t2; } si (t2 == nulo) { devolver t1; } // Primero fusiona el nodo actual y luego recurre a sus subárboles izquierdo y derecho TreeNode fusionado = nuevo TreeNode(t1.val t2.val); fusionado.izquierda = mergeTrees(t1.izquierda, t2.izquierda); fusionado.derecho = mergeTrees(t1.derecho, t2.derecho); volver fusionado; } }
2.1 Pitón
clase Solución: def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode: si no t1: regresar t2 si no t2: regresar t1 #Primero fusione el nodo actual y luego recurra a sus subárboles izquierdo y derecho fusionado = TreeNode(t1.val t2.val) fusionado.izquierda = self.mergeTrees(t1.izquierda, t2.izquierda) fusionado.derecho = self.mergeTrees(t1.derecho, t2.derecho) volver fusionado
3. Complejidad
Complejidad del tiempo: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. Realice una búsqueda en profundidad en dos árboles binarios al mismo tiempo. Solo cuando los nodos correspondientes en los dos árboles binarios no estén vacíos, el nodo se fusionará explícitamente. Por lo tanto, el número de nodos visitados no excederá el número de nodos. Árbol binario más pequeño. Número de nodos.
Complejidad espacial: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. La complejidad del espacio depende de la cantidad de niveles de llamadas recursivas. La cantidad de niveles de llamadas recursivas no excederá la altura máxima del árbol binario más pequeño. En el peor de los casos, la altura del árbol binario es igual a la cantidad de nodos. .
173.Iterador del árbol de búsqueda binaria
1. Aplanar el árbol de búsqueda binaria
0.Título
Implementar un iterador de árbol de búsqueda binario. Inicializará el iterador utilizando el nodo raíz del árbol de búsqueda binario. Llamar a next() devolverá el siguiente número más pequeño en el árbol de búsqueda binaria
1.Ideas
1. La forma más sencilla de implementar un iterador es en una interfaz de contenedor similar a una matriz. Si tenemos un array, sólo necesitamos un puntero o índice para implementar fácilmente las funciones next() y hasNext()
2. Utilice una matriz adicional y expanda el árbol de búsqueda binaria para almacenarla. Queremos que los elementos de la matriz se ordenen en orden ascendente, luego debemos realizar un recorrido en orden del árbol de búsqueda binario y luego construimos una función iteradora en la matriz.
3. Una vez que todos los nodos están en la matriz, solo necesitamos un puntero o índice para implementar las funciones next() y hasNext. Siempre que se llama a hasNext(), solo necesitamos verificar si el índice llega al final de la matriz. Siempre que se llama a next(), simplemente devolvemos el elemento señalado por el índice y avanzamos un paso para simular el progreso del iterador.
2. Algoritmo
clase BSTIterador { ArrayList<Integer> nodosSorted; índice entero; BSTIterator público (raíz de TreeNode) { this.nodesSorted = new ArrayList<Integer>(); este.índice = -1; this._inorder(raíz); } vacío privado _inorder (raíz de TreeNode) { si (raíz == nulo) retorno; this._inorder(root.left); this.nodesSorted.add(root.val); this._inorder(root.right); } público int siguiente() { devolver this.nodesSorted.get( this.index); } hasNext público booleano() { devolver this.index 1 < this.nodesSorted.size(); } }
2.1 Pitón
clase BSTIterador: self.nodes_sorted = [] auto.índice = -1 self._inorder(raíz) def __init__(self, raíz: TreeNode): self.nodes_sorted = [] auto.índice = -1 self._inorder(raíz) def _inorder(yo, raíz): si no es root: devolver self._inorder(raíz.izquierda) self.nodes_sorted.append(raíz.val) self._inorder(raíz.derecha) def siguiente(yo) -> int: auto.índice = 1 devolver self.nodes_sorted[self.index] def tieneNext(self) -> bool: devolver self.index 1 <len(self.nodes_sorted)
3. Complejidad
Complejidad del tiempo: el tiempo dedicado a construir el iterador es O (N). El planteamiento del problema solo requiere que analicemos la complejidad de las dos funciones, pero al implementar la clase, también debemos prestar atención al tiempo necesario para inicializar el objeto de clase. , en este caso A continuación, la complejidad del tiempo está relacionada linealmente con el número de nodos en el árbol de búsqueda binario: next(): O(1), hasNext(): O(1)
Complejidad espacial: O (N), dado que creamos una matriz para contener todos los valores de los nodos en el árbol de búsqueda binario, que no cumple con los requisitos en el enunciado del problema, la complejidad espacial máxima de cualquier función debe ser O (h) , donde h se refiere a la altura del árbol. Para un árbol de búsqueda binario equilibrado, la altura suele ser logN.
2. Recursión controlada
1.Ideas
1. El método utilizado anteriormente tiene una relación lineal en el espacio con el número de nodos en el árbol de búsqueda binario. Sin embargo, la razón por la que tenemos que utilizar este enfoque es que podemos iterar sobre la matriz y no podemos pausar la recursividad y luego iniciarla en algún momento. Sin embargo, si podemos simular la recursividad controlada con recorrido en orden, en realidad no necesitamos usar ningún otro espacio que el espacio en la pila utilizado para simular la recursividad.
2. Utilice un método iterativo para simular un recorrido en orden en lugar de un método recursivo. En el proceso de hacer esto, podemos implementar fácilmente las llamadas de estas dos funciones en lugar de utilizar otro espacio adicional;
3. Inicialice una pila vacía S, utilizada para simular el recorrido en orden de un árbol de búsqueda binario. Para el recorrido en orden usamos el mismo método que antes, excepto que ahora usamos nuestra propia pila en lugar de la pila del sistema. Dado que utilizamos una estructura de datos personalizada, la recursividad se puede pausar y reanudar en cualquier momento.
4. También se debe implementar una función auxiliar, que se llamará una y otra vez durante la implementación. Esta función se llama _inorder_left, que agrega todos los hijos izquierdos de un nodo determinado a la pila hasta que al nodo no le queden hijos.
5. Cuando se llama a la función next() por primera vez, se debe devolver el elemento mínimo del árbol binario de búsqueda, y luego simulamos la recursividad y debemos avanzar un paso, es decir, pasar al siguiente elemento mínimo de el árbol de búsqueda binaria. La parte superior de la pila siempre contiene el elemento devuelto por la función next(). hasNext() es fácil de implementar ya que solo necesitamos verificar si la pila está vacía
6. Primero, dado el nodo raíz del árbol de búsqueda binario, llamamos a la función _inorder_left, que garantiza que la parte superior de la pila siempre contenga el elemento devuelto por la función next()
7. Supongamos que llamamos a next(), necesitamos devolver el siguiente elemento más pequeño en el árbol de búsqueda binario, que es el elemento superior de la pila. Hay dos posibilidades: Una es que el nodo en la parte superior de la pila es un nodo hoja. Este es el mejor de los casos porque no tenemos que hacer nada más que sacar el nodo de la pila y devolver su valor. Entonces esta es una operación de tiempo constante. Otro caso es cuando el nodo en la parte superior de la pila posee el nodo correcto. No necesitamos verificar el nodo izquierdo porque ya se agregó a la pila. El elemento superior de la pila no tiene un nodo izquierdo o el nodo izquierdo ha sido procesado. Si hay un nodo derecho en la parte superior de la pila, entonces debemos llamar a la función auxiliar en el nodo derecho. La complejidad del tiempo depende de la estructura del árbol.
2. Algoritmo
clase BSTIterador { Pila<TreeNode> pila; BSTIterator público (raíz de TreeNode) { this.stack = new Stack<TreeNode>();//Usa la pila para simular la recursividad this._leftmostInorder(root);//El algoritmo comienza con la llamada de la función auxiliar } //Función auxiliar, agrega todos los nodos secundarios izquierdos a la pila vacío privado _leftmostInorder (raíz de TreeNode) { mientras (raíz! = nulo) { esta.pila.push(raíz); raíz = raíz.izquierda; } } público int siguiente() { TreeNode topmostNode = this.stack.pop();//El elemento superior de la pila es el elemento más pequeño //Mantenga el elemento superior de la pila como el elemento más pequeño. Si el nodo tiene un hijo derecho, llame a la función de ayuda. if (topmostNode.right! = nulo) { this._leftmostInorder(topmostNode.right); } devolver topmostNode.val; } hasNext público booleano() { devolver this.stack.size() > 0; } }
2.1 Pitón
clase BSTIterador: def __init__(self, raíz: TreeNode): self.stack = [] #Usa la pila para simular la recursividad self._leftmost_inorder(root) #El algoritmo comienza con la llamada de la función auxiliar #Función de ayuda, agregue todos los nodos secundarios izquierdos a la pila def _leftmost_inorder(self, raíz): mientras que raíz: self.stack.append(raíz) raíz = raíz.izquierda def siguiente(yo) -> int: topmost_node = self.stack.pop() #El elemento superior de la pila es el elemento más pequeño #Mantenga el elemento superior de la pila como el elemento más pequeño. Si el nodo tiene un hijo derecho, llame a la función auxiliar. si topmost_node.right: self._leftmost_inorder(topmost_node.right) devolver topmost_node.val def tieneNext(self) -> bool: devolver len(self.stack) > 0
3. Complejidad
Complejidad del tiempo: hasNext(): si todavía hay elementos en la pila, devuelve verdadero; de lo contrario, devuelve falso. Entonces esta es una operación O(1). next(): Contiene dos pasos principales. Una es sacar un elemento de la pila, que es el siguiente elemento más pequeño. Esta es una operación O(1). Sin embargo, luego tenemos que llamar a la función auxiliar _inorder_left, que debe ser recursiva, agregando el nodo izquierdo a la pila, que es una operación de tiempo lineal, O(N) en el peor de los casos. Pero solo lo llamamos en el nodo que contiene el nodo correcto y no siempre procesará N nodos. Sólo si tenemos un árbol sesgado, habrá N nodos. Por lo tanto, la complejidad temporal promedio de esta operación sigue siendo O (1), lo que está en línea con los requisitos del problema.
Complejidad del espacio: O (h), usando una pila para simular la recursividad
1130. Costo mínimo del árbol abarcador de valores de hojas ×
1. Programación dinámica
0.Título
Dada una matriz de números enteros positivos arr, considere todos los árboles binarios que cumplan las siguientes condiciones: Cada nodo tiene 0 o 2 nodos secundarios. Los valores en la matriz arr corresponden uno a uno al valor de cada nodo de hoja en el recorrido en orden del árbol. El valor de cada nodo no hoja es igual al producto del valor máximo del nodo hoja en su subárbol izquierdo y subárbol derecho. En todos estos árboles binarios, devuelve la suma más pequeña posible de los valores de cada nodo que no es hoja.
1.Ideas
El núcleo de esta pregunta es: Debe saber que el recorrido en orden determina que todos los elementos izquierdos (incluido él mismo) del k-ésimo elemento en la matriz arr (0...n-1) están en el subárbol izquierdo, y sus elementos derechos están todos en el subárbol derecho, y en este momento, el producto de los valores máximos seleccionados de los subárboles izquierdo y derecho es la raíz en este momento, que es el nodo no hoja mencionado en la pregunta, por lo que podemos suponer que desde. i a j, la suma mínima puede ser: k en este momento El subproblema producto del valor máximo de los elementos en los lados izquierdo y derecho de k es el valor mínimo del subproblema k en la izquierda (i, k), el valor mínimo del lado derecho de k dígitos (k 1, j), Es decir: dp[i][j]=min(dp[i][j], dp[i][k] dp[k 1][j] max[i][k]*max[k 1][ j ]) Esta pregunta es la misma que leetcode1039.
2.Código
solución de clase { público int mctFromLeafValues(int[] arreglo) { int n = longitud del arreglo; // Encuentra el valor máximo de los elementos en arr de i a j y guárdalo en max[i][j]. int[][] máx = nuevo int[n][n]; para (int j=0;j<n;j) { int maxValue = arreglo[j]; para (int i=j;i>=0;i--) { maxValue = Math.max(maxValue, arreglo[i]); máx[i][j] = valormáx; } } int[][] dp = nuevo int[n][n]; para (int j=0; j<n; j ) { para (int i=j; i>=0; i--) { //k es un valor entre i y j, i<=k<j int min = Entero.MAX_VALUE; para (int k=i; k 1<=j; k ) { min = Math.min(min,dp[i][k] dp[k 1][j] max[i][k]*max[k 1][j]); dp[i][j] = min;//Mantenga siempre la suma mínima entre i y j } } } devolver dp[0][n-1]; } }
2.1 Pitón
clase Solución: def mctFromLeafValues(self, arr: Lista[int]) -> int: n = longitud(arr) dp = [[float('inf') for _ in range(n)] for _ in range(n)] # Establece el valor inicial al máximo maxval = [[0 for _ in range(n)] for _ in range(n)] # Establece el valor máximo de la consulta de rango inicial en 0 para i en rango(n):# Encuentra el elemento más grande en el rango [i, j] para j en el rango (i, n): valormax[i][j] = max(arr[i:j 1]) para i en rango (n):# Los nodos hoja no participan en el cálculo dp[i][i] = 0 para l en rango (1, n): # Longitud del intervalo de enumeración para s en rango (n - l): # Enumerar el punto inicial del rango para k en rango(s, s l):# Enumere y divida dos subárboles, k es un valor entre s y l dp[s][s l] = min(dp[s][s l], dp[s][k] dp[k 1][s l] maxval[s][k] * maxval[k 1][s l]) devolver dp[0][n - 1]
3. Complejidad
Tiempo: O(n^3), Espacio: O(n^2)
2. Disminuir la pila
0.Enlace
1.Ideas
1. Si desea minimizar el valor mct, entonces los nodos de hoja con valores más pequeños deben colocarse en la parte inferior tanto como sea posible, y los nodos de hoja con valores más grandes deben colocarse lo más alto posible. Porque los nodos de hoja en la parte inferior se usan para multiplicar más veces. Esto determina que necesitamos encontrar un valor mínimo. Puede encontrar un valor mínimo manteniendo una pila monótonamente decreciente, porque dado que es una pila monótonamente decreciente, el nodo de la izquierda debe ser mayor que el nodo en la parte superior de la pila, y el nodo actual (a la derecha) es también mayor que el nodo en la parte superior de la pila (porque el nodo actual es más pequeño que la parte superior de la pila), se empuja directamente a la pila)
2. Después de encontrar este valor mínimo, debe mirar hacia la izquierda y hacia la derecha para ver qué valor es más pequeño a la izquierda o a la derecha. El propósito es colocar el valor más pequeño en la parte inferior tanto como sea posible.
2.Código
solución de clase { público int mctFromLeafValues(int[] arreglo) { Pila<Integer> st = nueva Pila(); st.push(Integer.MAX_VALUE); int mct = 0; // Comience a construir una pila decreciente. Si el elemento actual es mayor que el elemento en la parte superior de la pila, el elemento en la parte superior de la pila saldrá de la pila para encontrar el valor mínimo. para (int i = 0; i <arr.length; i) { mientras (arr[i] >= st.peek()) { //El elemento superior de la pila se saca de la pila y se combina con los elementos más pequeños en los lados izquierdo y derecho mct = st.pop() * Math.min(st.peek(), arreglo[i]); } st.push(arr[i]); } mientras (st.tamaño() > 2) { mct = st.pop() * st.peek(); } devolver mct; } }
2.1 Pitón
clase Solución: def mctFromLeafValues(self, A: Lista[int]) -> int: res, n = 0, len(A) stack = [float('inf')] #Pon el valor máximo primero en la pila # Comience a construir una pila decreciente. Si el elemento actual es mayor que el elemento superior de la pila, el elemento superior de la pila saldrá de la pila. Se puede encontrar el valor mínimo. para a en A: mientras que a >= pila[-1]: mid = stack.pop() #El elemento superior de la pila se saca de la pila y se combina con los elementos más pequeños en los lados izquierdo y derecho res = medio * min(pila[-1], a) pila.añadir(a) mientras len(pila) > 2: res = pila.pop() * pila[-1] devolver resolución
3. Complejidad
Tiempo: O(n), Espacio: O(n)
108. Convertir una matriz ordenada en un árbol de búsqueda binario equilibrado
1. Recursión en orden
0.Título
Convierta una matriz ordenada en orden ascendente en un árbol de búsqueda binario con altura equilibrada. Un árbol binario con altura equilibrada significa que el valor absoluto de la diferencia de altura entre los subárboles izquierdo y derecho de cada nodo de un árbol binario no excede 1.
1.Ideas
1. Seleccione el número del medio como nodo raíz del árbol de búsqueda binaria, de modo que el número de números asignados a los subárboles izquierdo y derecho sea el mismo o solo difiera en 1, lo que puede mantener el árbol equilibrado.
2. Después de determinar el nodo raíz del árbol de búsqueda binario equilibrado, los números restantes se ubican en el subárbol izquierdo y el subárbol derecho del árbol de búsqueda binario equilibrado, respectivamente. El subárbol izquierdo y el subárbol derecho también son árboles de búsqueda binarios equilibrados, respectivamente. Puede crear un árbol de búsqueda binario equilibrado de forma recursiva.
3. ¿Por qué se puede garantizar que tal logro sea "equilibrado"? Aquí puede consultar "1382. Equilibrar el árbol de búsqueda binaria"
4. La situación básica de la recursividad es que el árbol de búsqueda binario equilibrado no contiene ningún número. En este momento, el árbol de búsqueda binario equilibrado está vacío.
5. En el caso de una matriz de secuencia transversal en orden dada, los números en cada subárbol deben ser continuos en la matriz. Los números contenidos en el subárbol se pueden determinar mediante el rango de subíndices de la matriz. El rango de subíndice está marcado como [izquierda. , derecha], cuando izquierda>derecha, el árbol de búsqueda binario equilibrado está vacío
2.Código
clase Solución: def sortedArrayToBST(self, nums: Lista[int]) -> TreeNode: def ayudante (izquierda, derecha): si es izquierda > derecha: #Condición de terminación de dicotomía regresar Ninguno mid = (izquierda derecha) // 2 #Seleccione siempre el número a la izquierda de la posición media como nodo raíz root = TreeNode(nums[mid]) #Primero determine el nodo raíz, luego recurra a los subárboles izquierdo y derecho root.left = ayudante(izquierda, mitad - 1) root.right = ayudante(mitad 1, derecha) devolver la raíz ayudante de retorno(0, len(nums) - 1)
3. Complejidad
Complejidad del tiempo: O (n), donde n es la longitud de la matriz. Visita cada número solo una vez
Complejidad del espacio: O (logn), donde n es la longitud de la matriz. La complejidad del espacio no considera el valor de retorno, por lo que la complejidad del espacio depende principalmente de la profundidad de la pila recursiva. La profundidad de la pila recursiva es O (logn).
109. Conversión de lista enlazada ordenada a árbol de búsqueda binario equilibrado
1. Convierta una lista vinculada ordenada en una matriz ordenada
1. Complejidad
Tiempo: O(n), Espacio: O(n)
2. Los indicadores rápidos y lentos dividen y vencerán
1. Pensamientos
1. Establezca el intervalo de apertura izquierda y derecha
Supongamos que el extremo izquierdo de la lista vinculada actual es el izquierdo, el extremo derecho es el derecho y la relación de inclusión es "cerrada a la izquierda, abierta a la derecha". La lista vinculada dada es una lista vinculada unidireccional. elementos posteriores, pero no puede acceder directamente a los elementos predecesores. Por lo tanto, después de encontrar el nodo mediano en el medio de la lista vinculada, si establece la relación de "cerrado a la izquierda, abierto a la derecha", puede usar directamente (izquierda, medio) y (medio.siguiente, derecha) para representar la lista correspondiente a los subárboles izquierdo y derecho no necesitan mid.pre, y la lista inicial también se puede representar convenientemente mediante (head, null)
2. Utilice los punteros rápido y lento para encontrar la mediana.
Inicialmente, el puntero rápido y el puntero lento apuntan al punto final izquierdo de la lista vinculada. Mientras movemos el puntero rápido hacia la derecha dos veces, movemos el puntero lento hacia la derecha una vez hasta que el puntero rápido alcanza el límite (es decir, el puntero rápido alcanza el punto final derecho o el siguiente nodo del puntero rápido es el derecho punto final). En este momento, el elemento correspondiente al puntero lento es la mediana
3. Construya el árbol de forma recursiva.
Después de encontrar el nodo mediano, lo usamos como elemento del nodo raíz actual y construimos recursivamente el subárbol izquierdo correspondiente a la lista vinculada en el lado izquierdo y el subárbol derecho correspondiente a la lista vinculada en el lado derecho.
2. Algoritmo
clase Solución: def sortedListToBST(self, head: ListNode) -> TreeNode: def getMedian(izquierda: ListNode, derecha: ListNode) -> ListNode: rápido=lento=izquierda while fast != right and fast.next != right:#Defina el intervalo como cerrado por la izquierda y abierto por la derecha fast = fast.next.next#El puntero rápido se mueve dos veces y el puntero lento se mueve una vez lento = lento.siguiente volver lento #El puntero lento es la mediana en este momento def buildTree(izquierda: ListNode, derecha: ListNode) -> TreeNode: si izquierda == derecha: regresar Ninguno mid = getMedian(izquierda, derecha) #El intervalo se cierra por la izquierda y se abre por la derecha root = TreeNode(mid.val) #Primero determine el nodo raíz, luego construya los subárboles izquierdo y derecho root.left = buildTree(izquierda, mitad) #No es necesario mid.pre root.right = buildTree(medio.siguiente, derecha) devolver la raíz devolver buildTree(cabeza, Ninguno)
3. Complejidad
Complejidad del tiempo: O (nlogn), donde n es la longitud de la lista vinculada. Suponga que el tiempo para construir un árbol de búsqueda binario a partir de una lista vinculada de longitud n es T (n), y la fórmula de recurrencia es T (n) = 2⋅T (n/2) O (n). teorema, T(n)= O(nlogn)
Complejidad del espacio: O (logn), aquí solo se calcula el espacio distinto de la respuesta devuelta. La altura del árbol binario equilibrado es O (logn), que es la profundidad máxima de la pila durante el proceso recursivo, que es el espacio requerido.
3. Divide y conquistarás la optimización transversal en orden
1. Pensamientos
1. Suponga que el número del extremo izquierdo de la lista vinculada actual es el izquierdo, el número del extremo derecho es el derecho, la relación de inclusión es "doble cerrada", el número de nodos de la lista vinculada es [0, n) y el orden del recorrido en orden es "subárbol izquierdo - nodo raíz - subárbol derecho", luego, en el proceso de dividir y conquistar, no tenemos que apresurarnos a encontrar el nodo mediano de la lista vinculada, sino usar un nodo marcador de posición, y luego complete su valor cuando el nodo se recorra en orden
2. Realice un recorrido de orden medio calculando el rango de números: el número correspondiente al nodo mediano es mid = (izquierda derecha 1) / 2, y los rangos de números correspondientes a los subárboles izquierdo y derecho son [izquierda, mid-1] y [mid 1 respectivamente, derecha], si izquierda>derecha, entonces la posición recorrida corresponde a un nodo vacío; de lo contrario, corresponde a un nodo en el árbol de búsqueda binario.
3. Ya conocemos la estructura de este árbol de búsqueda binaria y la pregunta proporciona el resultado del recorrido en orden. Entonces solo necesitamos realizar un recorrido en orden para restaurar todo el árbol de búsqueda binaria.
4. Diagrama de proceso recursivo
2.Código
clase Solución: def sortedListToBST(self, head: ListNode) -> TreeNode: def getLength(cabeza: ListNode) -> int: retiro = 0 mientras cabeza: retirar=1 cabeza = cabeza.siguiente regresar def buildTree(izquierda: int, derecha: int) -> TreeNode: si izquierda > derecha: #Condición final del recorrido de orden medio regresar Ninguno medio = (izquierda derecha 1) // 2 raíz = NodoÁrbol() # Utilice recorrido en orden, primero construya el subárbol izquierdo, luego regrese al nodo raíz y luego construya el subárbol derecho, comenzando desde el primer elemento root.left = buildTree(izquierda, mitad - 1) encabezado no local #puede modificar el encabezado de la variable externa no global root.val = head.val #Comenzar a asignar valores desde el primer nodo cabeza = cabeza.siguiente root.right = buildTree(mediados de 1, derecha) devolver la raíz longitud = obtenerLongitud(cabeza) devolver buildTree(0, longitud - 1)
3. Complejidad
Complejidad del tiempo: O (n), donde n es la longitud de la lista vinculada. Supongamos que el tiempo para construir un árbol de búsqueda binario a partir de una lista vinculada de longitud n es T (n), y la fórmula de recurrencia es T (n) = 2⋅T (n/2) O (1). teorema, T(n)= O(n)
Complejidad del espacio: O (logn), aquí solo se calcula el espacio distinto de la respuesta devuelta. La altura del árbol binario equilibrado es O (logn), que es la profundidad máxima de la pila durante el proceso recursivo, que es el espacio requerido.
297. Serialización y deserialización de árboles binarios.
1. Recorrido de reserva
0.Título
La serialización es la operación de convertir una estructura de datos u objeto en bits continuos. Los datos convertidos pueden luego almacenarse en un archivo o memoria, y también pueden transmitirse a otro entorno informático a través de la red y reconstruirse de forma opuesta. datos. Diseñe un algoritmo para implementar la serialización y deserialización de árboles binarios. La lógica de ejecución de su algoritmo de secuencia/deserialización no está limitada aquí. Solo necesita asegurarse de que un árbol binario se pueda serializar en una cadena y deserializar la cadena en la estructura de árbol original.
1.Ideas
1. Serialización
1. Se elige el recorrido en orden anticipado porque el orden de impresión de raíz|izquierda|derecha facilita la localización del valor del nodo raíz durante la deserialización.
2. Cuando encuentra un nodo nulo, debe traducirlo al símbolo correspondiente. Solo cuando se deserializa se sabe que corresponde a nulo.
3. Gráfico recursivo
2.Deserialización
1. Para construir un árbol, primero cree el nodo raíz. La función auxiliar buildTree recibe la matriz de lista convertida a partir de la cadena serializada.
2. Abra el primer elemento de la lista por turno y construya el nodo raíz del subárbol actual. Siguiendo la matriz de la lista, se construirán primero el nodo raíz, el subárbol izquierdo y el subárbol derecho. Si el carácter que aparece es 'X', se devuelve un nodo nulo. Si el carácter que aparece no es 'X', cree un nodo, construya recursivamente sus subárboles izquierdo y derecho y devuelva el subárbol actual.
2.Código
Códec de clase: def serialize(self, root): #El recorrido de preorden implementa la serialización si raíz == Ninguno: devuelve 'X,' leftserilized = self.serialize(raíz.izquierda) derechoserilizados = self.serialize(root.right) return str(root.val) ',' leftserilized righterilized def deserializar(yo, datos): datos = datos.split(',') raíz = self.buildTree(datos) devolver la raíz def buildTree(yo, datos): val = data.pop(0) #pop el primer carácter if val == 'X': return Ninguno #Devuelve nodo vacío nodo = NodoÁrbol(val) nodo.izquierda = self.buildTree(datos) nodo.derecho = self.buildTree(datos) nodo de retorno
3. Complejidad
Tiempo: O(n), Espacio: O(n)
100. Árboles idénticos
1. Profundidad primero
0.Título
Dados los nodos raíz p y q de dos árboles binarios, escriba una función para verificar si los dos árboles son iguales
1.Ideas
1. Si ambos árboles binarios están vacíos, entonces los dos árboles binarios son iguales. Si uno y sólo uno de los dos árboles binarios está vacío, entonces los dos árboles binarios no deben ser iguales.
2. Si ambos árboles binarios no están vacíos, primero determine si los valores de sus nodos raíz son iguales. Si no son iguales, los dos árboles binarios deben ser diferentes. Si son iguales, determine si. los subárboles izquierdo y derecho de los dos árboles binarios son iguales. Este es un proceso recursivo, por lo que puede utilizar la búsqueda en profundidad para determinar de forma recursiva si dos árboles binarios son iguales.
2.Código
clase Solución: def esSameTree(self, p: TreeNode, q: TreeNode) -> bool: si no p y no q: #Ambos árboles están vacíos y son iguales devolver verdadero elif not p o not q: #Solo uno de los árboles está vacío, los dos árboles no son iguales falso retorno elif p.val != q.val: falso retorno demás: devuelve self.isSameTree(p.izquierda, q.izquierda) y self.isSameTree(p.derecha, q.derecha)
3. Complejidad
Complejidad del tiempo: O(\min(m,n))O(min(m,n)), donde mm y n son el número de nodos de los dos árboles binarios respectivamente. Realice una búsqueda en profundidad en dos árboles binarios al mismo tiempo. Se accederá al nodo solo cuando los nodos correspondientes en los dos árboles binarios no estén vacíos, por lo que la cantidad de nodos visitados no excederá la cantidad de nodos en el más pequeño. árbol binario.
Complejidad espacial: O(\min(m,n))O(min(m,n)), donde mm y n son el número de nodos de los dos árboles binarios respectivamente. La complejidad del espacio depende de la cantidad de niveles de llamadas recursivas. La cantidad de niveles de llamadas recursivas no excederá la altura máxima del árbol binario más pequeño. En el peor de los casos, la altura del árbol binario es igual a la cantidad de nodos. .
2.La amplitud primero
1.Ideas
1. Primero determine si los dos árboles binarios están vacíos. Si ambos árboles binarios no están vacíos, comience la búsqueda en amplitud desde los nodos raíz de los dos árboles binarios.
2. Utilice dos colas para almacenar los nodos de dos árboles binarios respectivamente. Inicialmente, los nodos raíz de los dos árboles binarios se agregan a las dos colas respectivamente. Cada vez, se saca un nodo de las dos colas y se realiza la siguiente operación de comparación. Compare los valores de dos nodos. Si los valores de los dos nodos no son iguales, los dos árboles binarios deben ser diferentes; Si los valores de dos nodos son iguales, determine si los nodos secundarios de los dos nodos están vacíos. Si solo el nodo secundario izquierdo de un nodo está vacío o el nodo secundario derecho de solo un nodo está vacío, las estructuras. de los dos árboles binarios son diferentes, por lo tanto los dos árboles binarios deben ser diferentes; Si las estructuras de los nodos secundarios de los dos nodos son las mismas, agregue los nodos secundarios no vacíos de los dos nodos a las dos colas respectivamente. Debe prestar atención al orden al agregar los nodos secundarios a la cola. los nodos secundarios izquierdo y derecho no están vacíos, agregue primero el nodo secundario izquierdo y luego agregue el nodo secundario derecho.
3. Si ambas colas están vacías al mismo tiempo al final de la búsqueda, los dos árboles binarios son iguales. Si solo una cola está vacía, la estructura de los dos árboles binarios es diferente, por lo que los dos árboles binarios son diferentes
2.Código
clase Solución: def esSameTree(self, p: TreeNode, q: TreeNode) -> bool: si no p y no q: #Ambos árboles están vacíos y son iguales devolver verdadero si no p o no q: #Solo uno de los árboles está vacío, los dos árboles no son iguales falso retorno cola1 = colecciones.deque([p]) cola2 = colecciones.deque([q]) mientras cola1 y cola2: nodo1 = cola1.popleft() nodo2 = cola2.popleft() si nodo1.val! = nodo2.val: falso retorno izquierda1, derecha1 = nodo1.izquierda, nodo1.derecha izquierda2, derecha2 = nodo2.izquierda, nodo2.derecha if (not left1) ^ (not left2): # ^ significa XOR, si los dos son diferentes, son 1, y si son iguales, son 0 falso retorno si (no está bien1) ^ (no está bien2): falso retorno si queda1: #Ingrese el nodo en la cola cuando no esté vacío cola1.append(izquierda1) si es correcto1: cola1.append(derecha1) si queda2: cola2.append(izquierda2) si es correcto2: cola2.append(derecha2) devolver no cola1 y no cola2
3. Complejidad
Complejidad del tiempo: O(\min(m,n))O(min(m,n)), donde mm y n son el número de nodos de los dos árboles binarios respectivamente. Realice una búsqueda en amplitud en dos árboles binarios al mismo tiempo. Se accederá al nodo solo cuando los nodos correspondientes en los dos árboles binarios no estén vacíos, por lo que la cantidad de nodos visitados no excederá la cantidad de nodos en el más pequeño. árbol binario.
Complejidad espacial: O(\min(m,n))O(min(m,n)), donde mm y n son el número de nodos de los dos árboles binarios respectivamente. La complejidad del espacio depende de la cantidad de elementos en la cola. La cantidad de elementos en la cola no excederá la cantidad de nodos del árbol binario más pequeño.
105. Construya un árbol binario a partir de secuencias transversales en preorden y en orden.
1. recursividad
1.Ideas
1. Para cualquier árbol, la forma de recorrido de preorden es siempre [Nodo raíz, [resultado del recorrido en preorden del subárbol izquierdo], [resultado del recorrido en preorden del subárbol derecho]] Es decir, el nodo raíz es siempre el primer nodo en el recorrido del pedido previo. La forma de recorrido en orden es siempre [[Resultado del recorrido en orden del subárbol izquierdo], nodo raíz, [Resultado del recorrido en orden del subárbol derecho]]
2. Siempre que ubiquemos el nodo raíz en el recorrido en orden, podremos conocer el número de nodos en el subárbol izquierdo y el subárbol derecho respectivamente. Dado que las longitudes del recorrido de preorden y del recorrido de orden del mismo subárbol son obviamente las mismas, podemos corresponder a los resultados del recorrido de preorden y ubicar todos los corchetes izquierdo y derecho en el formulario anterior.
3. De esta manera, conocemos los resultados del recorrido en preorden y en orden del subárbol izquierdo, y los resultados del recorrido en preorden y en orden del subárbol derecho. Podemos construir recursivamente el. subárbol izquierdo y el subárbol derecho, y luego conecte estos dos subárboles a las posiciones izquierda y derecha del nodo raíz.
4. Optimización: al ubicar el nodo raíz en el recorrido en orden, un método simple es escanear directamente todo el resultado del recorrido en orden y encontrar el nodo raíz list.index (x), pero la complejidad del tiempo para hacerlo es relativamente alta alta. Podemos considerar usar un HashMap para ayudarnos a localizar rápidamente el nodo raíz. Para cada par clave-valor en el mapa hash, la clave representa un elemento (el valor del nodo) y el valor representa su posición de aparición en el recorrido en orden. Antes de construir el árbol binario, podemos escanear la lista recorrida una vez para construir este mapa hash. En el proceso posterior de construcción del árbol binario, solo necesitamos O (1) tiempo para ubicar el nodo raíz.
2.Código
1. Optimizado oficialmente, pero hay muchos parámetros. Puede usar dos parámetros para ver la combinación de pedido posterior y pedido medio.
clase Solución: def buildTree(self, preorder: Lista[int], inorder: Lista[int]) -> TreeNode: def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): si preorden_izquierda > preorden_derecha: regresar Ninguno preorder_root = preorder_left # El primer nodo en el recorrido del pedido anticipado es el nodo raíz inorder_root = index[preorder[preorder_root]] # Ubique el subíndice del nodo raíz en el recorrido en orden root = TreeNode(preorder[preorder_root]) # Primero crea el nodo raíz size_left_subtree = inorder_root - inorder_left # Obtiene el número de nodos en el subárbol izquierdo # Construya recursivamente el subárbol izquierdo y conéctelo al nodo raíz # Los elementos "size_left_subtree comenzando desde el borde izquierdo 1" en el recorrido de preorden corresponden a los elementos "comenzando desde el borde izquierdo hasta la posición del nodo raíz -1" en el recorrido en orden. root.left = myBuildTree(preorder_left 1, preorder_left size_left_subtree, inorder_left, inorder_root - 1) # Construya recursivamente el subárbol derecho y conéctese al nodo raíz # En el recorrido de preorden, los elementos "desde el límite izquierdo 1 y el número de nodos del subárbol izquierdo hasta el límite derecho" corresponden a los elementos "desde la posición del nodo raíz 1 hasta el límite derecho" en el recorrido en orden . root.right = myBuildTree(preorder_left 1 size_left_subtree, preorder_right, inorder_root 1, inorder_right) devolver la raíz n = len(preorden) index = {elemento: i para i, elemento en enumerar (en orden)} #Construya un mapa hash, la clave es el elemento, el valor es el subíndice, ubique rápidamente el nodo raíz devolver myBuildTree(0, n - 1, 0, n - 1)
2. Versión sencilla
clase Solución: def buildTree(self, preorder: Lista[int], inorder: Lista[int]) -> TreeNode: def recur_func(en orden): x = preorder.pop(0) # Elimina el elemento más a la izquierda de la lista de pedidos anticipados cada vez nodo = TreeNode(x) # Utilice este elemento para generar un nodo idx = inorder.index(x) # Encuentra el índice del elemento en la lista en orden left_l = inorder[:idx] # Utilice este elemento para dividir la lista en orden right_l = orden[idx 1:] node.left = recur_func(left_l) si left_l else Ninguno node.right = recur_func(right_l) si right_l else Ninguno # Explore hasta la hoja más a la izquierda en la parte inferior y luego regrese capa por capa de abajo hacia arriba. nodo de retorno si no es un pedido anticipado o no un pedido: devolver Ninguno # Prueba vacía devolver recur_func(en orden)
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol
Complejidad del espacio: O (n), además del espacio O (n) requerido para devolver la respuesta, también necesitamos usar el espacio O (n) para almacenar el mapa hash y O (h) (donde h es la altura del árbol) el espacio representa el espacio de la pila durante la recursividad
2.Iteración
1.Ideas
1. Usamos una pila y un puntero para ayudar en la construcción del árbol binario. Inicialmente, el nodo raíz (el primer nodo del recorrido en orden anticipado) se almacena en la pila, y el puntero apunta al primer nodo del recorrido en orden "el nodo final alcanzado por el nodo actual al caminar continuamente hacia el izquierda."
1. La pila se utiliza para mantener "todos los nodos ancestros del nodo actual que aún no se han considerado el hijo correcto". La parte superior de la pila es el nodo actual. En otras palabras, sólo los nodos de la pila pueden conectarse a un nuevo hijo derecho.
2. Enumere cada nodo en el recorrido del pedido previo, excepto el primer nodo. Si el índice apunta al nodo superior de la pila, entonces continuamente sacamos el nodo superior de la pila y movemos el índice hacia la derecha, y usamos el nodo actual como el hijo derecho del último nodo extraído.
1. Para dos nodos consecutivos u y v en el recorrido de preorden, de acuerdo con el proceso de recorrido de preorden, podemos saber que u y v solo tienen dos relaciones posibles: v es el hijo izquierdo de u. Esto se debe a que después de atravesar hasta u, el siguiente nodo atravesado es el hijo izquierdo de u, que es v; u no tiene un hijo izquierdo y v es el hijo derecho de un nodo ancestro de u (o de u mismo). Si u no tiene un hijo izquierdo, entonces el siguiente nodo atravesado es el hijo derecho de u. Si u no tiene un hijo correcto, retrocederemos hacia arriba hasta encontrar el primer nodo ua que tiene un hijo correcto (y u no está en el subárbol de su hijo correcto), entonces v es el hijo correcto de ua
2. Cuando atravesamos 10, la situación es diferente. Encontramos que el índice solo apunta al nodo superior actual 4 de la pila, lo que significa que 4 no tiene un hijo izquierdo, por lo que 10 debe ser el hijo derecho de un nodo en la pila.
3. El orden de los nodos en la pila es consistente con el orden en que aparecen en el recorrido de preorden, y el hijo derecho de cada nodo aún no ha sido atravesado, entonces el orden de estos nodos es consistente con el orden en el que aparecen en el recorrido en orden. El orden debe invertirse.
4. Podemos seguir moviendo el índice hacia la derecha y compararlo con el nodo superior de la pila. Si el elemento correspondiente al índice es exactamente igual al nodo superior de la pila, significa que encontramos el nodo superior de la pila durante el recorrido en orden, por lo que aumentamos el índice en 1 y sacamos el nodo superior de la pila hasta que El elemento correspondiente al índice no es igual al nodo superior de la pila. De acuerdo con este proceso, el último nodo x que aparece es el nodo padre de 10. Esto se debe a que 10 aparece entre x y el recorrido en orden del siguiente nodo de x en la pila, por lo que 10 es el hijo correcto. de x
3. Si el índice es diferente del nodo superior de la pila, usamos el nodo actual como el hijo izquierdo del nodo superior de la pila.
1. Atravesamos 9. 9 debe ser el hijo izquierdo del nodo 3 en la parte superior de la pila. Usamos prueba por contradicción, asumiendo que 9 es el hijo derecho de 3, entonces 3 no tiene hijo izquierdo y el índice debería apuntar simplemente a 3, pero en realidad es 4, creando así una contradicción. Entonces tomamos 9 como el hijo izquierdo de 3 y empujamos 9 a la pila.
4. No importa cuál sea la situación, finalmente insertaremos el nodo actual en la pila.
2.Código
clase Solución: def buildTree(self, preorder: Lista[int], inorder: Lista[int]) -> TreeNode: si no preordenar: regresar Ninguno raíz = TreeNode(preorder[0]) pila = [raíz] inorderIndex = 0 # El nodo final alcanzado por el nodo actual se mueve continuamente hacia la izquierda, de acuerdo con el recorrido en orden para i en rango (1, len (preorden)): # Recorrido de preorden a partir del segundo nodo preordenVal = preorden[i] nodo = pila[-1] #El nodo superior de la pila if node.val != inorder[inorderIndex]: #Cuando el nodo superior de la pila no es igual, es el hijo izquierdo node.left = TreeNode(preorderVal) #Construye el hijo izquierdo directamente stack.append(node.left) #Empuja al hijo izquierdo hacia la pila else: #Cuando los nodos superiores de la pila son iguales, se ha atravesado hasta el punto más a la izquierda y es necesario sacarlo de la pila y luego atravesarlo hasta el hijo derecho de un determinado nodo. mientras que apilar y apilar[-1].val == inorder[inorderIndex]: nodo = stack.pop() #El elemento superior de la pila sale de la pila inorderIndex = 1 #Reemplazar el elemento más a la izquierda node.right = TreeNode(preorderVal) #Cuando los dos son diferentes, se encuentra el nodo padre del elemento actual stack.append(node.right) #También empuja el elemento actual a la pila devolver la raíz
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol
Complejidad del espacio: O (n) Además del espacio O (n) requerido para la respuesta devuelta, también necesitamos usar el espacio O (h) (donde h es la altura del árbol) para almacenar la pila.
106. Construya un árbol binario a partir de secuencias transversales en orden y postorden.
1. recursividad
1.Ideas
1. El último elemento de la matriz atravesado en orden posterior representa el nodo raíz y luego se divide en recorridos en orden.
2. Tenga en cuenta que existe una relación de dependencia en la que primero debe crear el subárbol derecho y luego el subárbol izquierdo. Se puede entender que en la matriz de recorrido posterior al orden, la matriz completa primero almacena los nodos del subárbol izquierdo, luego los nodos del subárbol derecho y finalmente el nodo raíz. Si selecciona "el último nodo del postorden". recorrido de orden" como raíz de cada nodo de tiempo, el primero construido debe ser el subárbol derecho
2.Código
1.Oficial: dos parámetros
clase Solución: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: def ayudante(en_izquierda, en_derecha): if in_left > in_right:# Si no hay nodos aquí para construir un árbol binario, terminará regresar Ninguno val = postorder.pop()# Seleccione el elemento en la posición post_idx como el nodo raíz del subárbol actual raíz = NodoÁrbol(val) index = idx_map[val] # Dividir en subárboles izquierdo y derecho según la ubicación de la raíz # Primero se debe construir el subárbol derecho, porque se accede al recorrido posterior al pedido desde el final, por lo que se debe acceder primero al subárbol derecho raíz.derecha = ayudante(índice 1, en_derecha) raíz.izquierda = ayudante(en_izquierda, índice - 1) devolver la raíz # Crear una tabla hash de pares clave-valor (elemento, subíndice) idx_map = {val:idx para idx, val en enumerar(en orden)} ayudante de retorno (0, len (en orden) - 1)
2. Versión sencilla
clase Solución: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: def recur_func(en orden): x = postorder.pop() # Elimina el elemento más a la izquierda de la lista de pedidos anterior cada vez nodo = TreeNode(x) # Utilice este elemento para generar un nodo idx = inorder.index(x) # Encuentra el índice del elemento en la lista en orden left_l = inorder[:idx] # Utilice este elemento para dividir la lista en orden right_l = orden[idx 1:] # Primero se debe construir el subárbol derecho, porque se accede al recorrido posterior al pedido desde el final, por lo que se debe acceder primero al subárbol derecho node.right = recur_func(right_l) si right_l else Ninguno node.left = recur_func(left_l) si left_l else Ninguno # Explore hasta la hoja más a la izquierda en la parte inferior y luego regrese capa por capa de abajo hacia arriba. nodo de retorno si no es postorder o no inorder: devuelve Ninguno # Prueba vacía devolver recur_func(en orden)
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol
Complejidad del espacio: O (n), además del espacio O (n) requerido para devolver la respuesta, también necesitamos usar el espacio O (n) para almacenar el mapa hash y O (h) (donde h es la altura del árbol) el espacio representa el espacio de la pila durante la recursividad
2.Iteración
1.Ideas
1. Si invierte el recorrido en orden, obtendrá un recorrido en orden inverso, es decir, atravesará al hijo derecho cada vez, luego atravesará el nodo raíz y finalmente atravesará al hijo izquierdo. Si invierte el recorrido posterior al orden, obtendrá el recorrido inverso del orden previo, es decir, atraviesa el nodo raíz cada vez, luego atraviesa el hijo derecho y finalmente atraviesa el hijo izquierdo.
2. La diferencia con la pregunta anterior es: todos los hijos de la izquierda se convierten en hijos de la derecha, todos los hijos de la derecha se convierten en hijos de la izquierda y el recorrido de orden directo se cambia a recorrido de orden inverso.
2.Código
clase Solución: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: si no postorder: regresar Ninguno raíz = TreeNode(postorder[-1]) pila = [raíz] inorderIndex = -1 #El nodo final alcanzado por el nodo actual se mueve continuamente hacia la derecha, de acuerdo con el recorrido en orden for i in range(len(postorder)-2, -1,-1):#Recorrer el recorrido posterior al orden comenzando desde el penúltimo nodo postordenVal = postorden[i] nodo = pila[-1] #El nodo superior de la pila if node.val != inorder[inorderIndex]: #Cuando el nodo superior de la pila no es igual, es el hijo correcto node.right = TreeNode(postorderVal) #Construye el hijo correcto directamente stack.append(node.right) #Empuja el elemento secundario correcto en la pila else: # Cuando los nodos superiores de la pila son iguales, se ha atravesado hasta el punto más a la derecha. Es necesario sacarlo de la pila y luego atravesarlo hasta el hijo izquierdo de un determinado nodo. mientras que apilar y apilar[-1].val == inorder[inorderIndex]: nodo = stack.pop() #El elemento superior de la pila sale de la pila inorderIndex -= 1 #Reemplazar el elemento más a la derecha node.left = TreeNode(postorderVal) #Cuando los dos son diferentes, se encuentra el nodo padre del elemento actual stack.append(node.left) #También empuja el elemento actual a la pila devolver la raíz
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol
Complejidad del espacio: O (n) Además del espacio O (n) requerido para la respuesta devuelta, también necesitamos usar el espacio O (h) (donde h es la altura del árbol) para almacenar la pila.
124. Suma máxima de ruta en árbol binario
1. recursividad
0.Título
Una ruta se define como una secuencia que comienza desde cualquier nodo del árbol, se conecta a lo largo del nodo principal y el nodo secundario y llega a cualquier nodo. El mismo nodo aparece como máximo una vez en una secuencia de ruta. La ruta contiene al menos un nodo y no necesariamente pasa por el nodo raíz. La suma de la ruta es la suma de los valores de cada nodo en la ruta. Proporcionarle la raíz del nodo raíz de un árbol binario y devolver la suma máxima de su ruta.
1.Ideas
1. Primero, considere implementar una función simplificada maxGain (nodo), que calcula el valor de contribución máximo de un nodo en el árbol binario. Específicamente, busca el nodo como punto de partida en el subárbol con el nodo como nodo raíz. una ruta que maximiza la suma de los valores de los nodos en la ruta
2. Específicamente, la función se calcula de la siguiente manera. El valor máximo de contribución de un nodo vacío es igual a 00. El valor de contribución máximo de un nodo no vacío es igual a la suma del valor del nodo y el valor de contribución máximo en sus nodos secundarios (para los nodos hoja, el valor de contribución máximo es igual al valor del nodo
3. El proceso de cálculo anterior es un proceso recursivo. Por lo tanto, llamando a la función maxGain en el nodo raíz, se puede obtener el valor de contribución máximo de cada nodo.
4. Después de obtener el valor de contribución máximo de cada nodo según la función maxGain, ¿cómo obtener la suma de ruta máxima del árbol binario? Para un nodo en un árbol binario, la suma de ruta máxima del nodo depende del valor del nodo y del valor de contribución máximo de los nodos secundarios izquierdo y derecho del nodo. Si el valor de contribución máximo del nodo secundario es positivo, está incluido en la suma de ruta máxima del nodo; de lo contrario, no se incluirá en la suma de ruta máxima del nodo. Mantenga una variable global maxSum para almacenar la suma de ruta máxima y actualice el valor de maxSum durante el proceso recursivo. El valor final de maxSum es la suma de ruta máxima en el árbol binario.
2.Código
clase Solución: def __init__(yo): self.maxSum = float("-inf") #El valor máximo se inicializa a infinito negativo def maxPathSum(self, raíz: TreeNode) -> int: def maxGain(nodo): #Obtener el valor máximo de contribución del nodo si no es nodo: regresar 0 # Calcular recursivamente el valor máximo de contribución de los nodos secundarios izquierdo y derecho # Solo cuando el valor máximo de contribución sea mayor que 0, se seleccionará el nodo hijo correspondiente ganancia izquierda = max(gananciamax(nodo.izquierda), 0) ganancia derecha = max(gananciamax(nodo.derecha), 0) # Primero obtenga el valor de ruta máximo con el nodo actual como nodo raíz y luego devuelva el valor de contribución máximo del nodo #La suma de ruta máxima de un nodo depende del valor del nodo y del valor de contribución máximo de los nodos secundarios izquierdo y derecho del nodo. precioNewpath = node.val ganancia izquierda ganancia derecha self.maxSum = max(self.maxSum, priceNewpath) # Actualizar respuesta # Devuelve el valor de contribución máximo del nodo, que está determinado por el mayor entre el valor del nodo y el subárbol izquierdo/subárbol derecho devolver node.val max (ganancia izquierda, ganancia derecha) ganancia máxima(raíz) devolver self.maxSum
3. Complejidad
Complejidad del tiempo: O (N) O (N), donde NN es el número de nodos en el árbol binario. No más de 2 visitas a cada nodo.
Complejidad espacial: O (N) O (N), donde NN es el número de nodos en el árbol binario. La complejidad del espacio depende principalmente de la cantidad de niveles de llamadas recursivas. El número máximo de niveles es igual a la altura del árbol binario. En el peor de los casos, la altura del árbol binario es igual a la cantidad de nodos en el árbol binario. .
654. Árbol binario máximo
1. recursividad
0.Título
Dada una matriz de números enteros sin elementos duplicados. Un árbol binario máximo construido directa y recursivamente a partir de esta matriz se define de la siguiente manera: La raíz del árbol binario es el elemento más grande de la matriz nums. El subárbol izquierdo es el árbol binario más grande construido recursivamente a través de la parte izquierda del valor máximo en la matriz. El subárbol derecho es el árbol binario más grande construido de forma recursiva a través de la parte derecha del valor máximo de la matriz. Devuelve el árbol binario más grande construido con los números de matriz dados.
1.Ideas
Sigue siendo una trilogía 1. Condición de terminación de la recursividad: cuando las matrices del subárbol izquierdo y del subárbol derecho están vacías 2. Qué hace esta recursividad: saque el valor máximo de la matriz, divida la matriz en matrices izquierda y derecha según el valor máximo y luego realice una asignación recursiva 3. Qué se devuelve: el nodo raíz del subárbol
2.Código
clase Solución: def constructMaximumBinaryTree(self, nums: Lista[int]) -> TreeNode: si no números: devolver Ninguno # Aplicable cuando hay elementos repetidos # max_v, m_i = flotador(-inf), 0 # para i, v en enumerar(nums): # si v>max_v: #max_v=v # m_i = yo max_v = max(núms) m_i = números.index(max_v) raíz = TreeNode(max_v) root.left = self.constructMaximumBinaryTree(nums[:m_i]) root.right = self.constructMaximumBinaryTree(nums[m_i 1:]) devolver la raíz
3. Complejidad
Complejidad del tiempo: O (n ^ 2), la construcción del método se llama n veces en total. Cada vez que busca recursivamente el nodo raíz, debe recorrer todos los elementos en el rango de índice actual para encontrar el valor máximo. En general, la complejidad de cada recorrido es O (logn) y la complejidad total es O (nlogn). En el peor de los casos, la matriz numsnums está ordenada y la complejidad total es O(n^2)
Complejidad espacial: O (n) O (n). La profundidad de la llamada recursiva es nn. En promedio, la profundidad de llamada recursiva para una matriz de longitud n es O(logn)
2. Pila monótonamente decreciente
1. Pensamientos
1. Construya una pila decreciente y empújela hacia la pila en secuencia. Si el elemento en la parte superior de la pila es más pequeño que el elemento actual, el elemento en la parte superior de la pila se saca de la pila.
2. Compare el tamaño del elemento superior después de sacarlo de la pila con el tamaño del elemento actual. Si es más grande que el elemento actual, el elemento actual se usa como nodo principal y el elemento sacado se usa como nodo. hijo izquierdo; si el elemento superior de la pila es más pequeño que el elemento actual, el elemento superior de la pila se usa como nodo padre y lo saca de la pila. El elemento previamente extraído se usa como el hijo derecho y continúa. hasta que el nodo actual sea empujado a la pila.
3. Hasta el final del último elemento, se forma la pila decreciente y la pila se extrae secuencialmente. Los elementos extraídos se utilizan como los elementos secundarios correctos. El último que se extrae de la pila es el nodo raíz. .
Ejemplo
Tome el caso de prueba como ejemplo, una secuencia de entrada: [3, 2, 1, 6, 0, 5]. Configure una pila auxiliar para almacenar desde grandes hasta pequeños. El proceso es el siguiente: Empuje 3 primero 2 es menor que 3, empujado a la pila 1 es más pequeño que 2, empujado a la pila 6 es mayor que 1, por lo que aparecerán 1 y 1. Elija el elemento más pequeño entre 2 y 6 como nodo principal, de modo que 2 esté seleccionado en el lado derecho de 2, lo que convierte a 1 en el nodo secundario derecho de. 2. Después de que aparezca 1, 6 sigue siendo mayor que 2. De manera similar, 2 debe elegir uno entre 3 y 6 como nodo principal. 3 es menor que 6, por lo que se selecciona 3. 2 está a la derecha de 3, por lo que 2 es el hijo derecho de 3. De la misma manera, aparece 3, sea 3 el nodo secundario izquierdo de 6 Empuje 6 Empuja 0 a la pila Cuando se inserta 5 en la pila, es mayor que 0. Para extraer 0, seleccione 5 como nodo principal y 0 es el hijo izquierdo de 5. Aparece 5, con 6 a la izquierda como nodo padre de 5 Finalmente aparece 6, que es el nodo raíz.
2. Código Java
public TreeNode constructMaximumBinaryTree(int[] números) { Raíz de TreeNode = nulo, nodo = nulo; LinkedList<TreeNode> pila = nueva LinkedList<>(); para (int i = 0; i < nums.length; i) { nodo = nuevo TreeNode(nums[i]); // Construya una pila decreciente. Cuando el elemento superior de la pila es más pequeño que el elemento actual, el elemento superior de la pila se extraerá de la pila. mientras (!stack.isEmpty() && stack.peek().val < nodo.val) { root = stack.pop();//El elemento superior de la pila actual, el valor más pequeño // Compara el tamaño de la parte superior de la pila después de abrirla con el tamaño del elemento actual. Si es más grande que el elemento actual, el elemento actual se utilizará como nodo principal. if (pila.isEmpty() || pila.peek().val > nodo.val) { nodo.izquierda = raíz; } else {//Si es menor que el elemento actual, el elemento superior de la pila se utilizará como nodo principal pila.peek().right = raíz; } } stack.push(node);//Empuja el elemento actual a la pila } // La pila decreciente está completamente formada y puede extraerse como el hijo correcto. mientras (!stack.isEmpty()) { raíz = pila.pop(); si (!stack.isEmpty()) { pila.peek().right = raíz; } } return root;// Lo último que sale de la pila es root, regresa a este nodo }
3. Complejidad
Complejidad del tiempo: O (n), Complejidad del espacio: O (n)
998.Árbol binario máximo II×
1. Inserción transversal
0.Título
Definición máxima de árbol: un árbol en el que el valor de cada nodo es mayor que cualquier otro valor en su subárbol. da la raíz del nodo raíz del árbol más grande. Al igual que la pregunta anterior, el árbol dado se construye recursivamente a partir de la lista A, no se nos proporciona A directamente, solo una raíz del nodo raíz, asumiendo que B es una copia de A con el valor val agregado al final. Los datos de la pregunta garantizan que los valores en B son diferentes. Construcción de retorno (B)
1. Pensamientos
Cada vez que se compara con el nodo principal, si es más pequeño se comprueba su nodo derecho. Actualización: 1) El nodo existente se convierte en el nodo izquierdo del nuevo nodo, 2) el nuevo nodo se convierte en el nodo derecho del nodo principal
2.Código
def insertIntoMaxTree(self, raíz: TreeNode, val: int) -> TreeNode: # variable ficticia, su hijo derecho apunta al nodo raíz ficticio = NodoÁrbol(0) tonto.derecha = raíz # buscar Si el valor del nodo raíz actual es mayor que val, continúe consultando su hijo derecho p, c = ficticio, ficticio.derecha mientras c y c.val > val: p, c = c, c.derecha # insert En este momento, val es mayor que el nodo raíz, val se usa como el hijo derecho del nodo raíz anterior y el nodo raíz actual se usa como el hijo izquierdo de val. n = NodoÁrbol(val) p.derecha = n n.izquierda = c return dummy.right #dummy no ha cambiado, su hijo derecho apunta al nodo raíz
3. Complejidad
2. recursividad
1. Pensamientos
El mismo procesamiento se realiza cuando es mayor que el nodo raíz. Cuando es menor que el nodo raíz, el hijo derecho del nodo raíz se procesa de forma recursiva.
2.Código
def insertIntoMaxTree(self, raíz: TreeNode, val: int) -> TreeNode: si root es Ninguno: # Condición de terminación recursiva returnTreeNode(val) if val > root.val: #Cómo manejar val mayor que el nodo raíz actual tmp = NodoÁrbol(val) tmp.izquierda = raíz regresar tmp # Procesar recursivamente el hijo derecho del nodo raíz raíz.derecha = self.insertIntoMaxTree(raíz.derecha, val) return root # Valor de retorno recursivo, nodo raíz
3. Complejidad
subtema
subtema
110. Determinar el árbol binario equilibrado.
1. De arriba hacia abajo
0.Título
Dado un árbol binario, determine si es un árbol binario de altura equilibrada. En esta pregunta, un árbol binario con altura equilibrada se define como: el valor absoluto de la diferencia de altura entre los subárboles izquierdo y derecho de cada nodo de un árbol binario no excede 1
1.Ideas
1. Defina la altura de la función, que se utiliza para calcular la altura de cualquier nodo p en el árbol binario.
2. Similar al recorrido de pedido previo de un árbol binario, es decir, para el nodo actualmente atravesado, primero calcule la altura de los subárboles izquierdo y derecho. Si la diferencia de altura entre los subárboles izquierdo y derecho no excede 1, entonces. atraviesa recursivamente los subnodos izquierdo y derecho respectivamente y determina si los subárboles izquierdo y derecho están equilibrados. Este es un proceso recursivo de arriba hacia abajo.
2.Código
solución de clase: #De arriba hacia abajo, similar al recorrido de pedido previo, calculará repetidamente la altura de los subárboles izquierdo y derecho def isBalanced(self, raíz: TreeNode) -> bool: def altura(raíz: TreeNode) -> int: si no es root: regresar 0 devolver max(altura(raíz.izquierda), altura(raíz.derecha)) 1 si no es root: devolver verdadero return abs(altura(raíz.izquierda) - altura(raíz.derecha)) <= 1 y self.isBalanced(root.left) y self.isBalanced(root.right)
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol binario. Utilizando la recursividad ascendente, la altura de cálculo de cada nodo y el juicio de si está equilibrado solo deben procesarse una vez. En el peor de los casos, es necesario atravesar todos los nodos del árbol binario, por lo que la complejidad del tiempo es O (. norte)
Complejidad espacial: O (n), donde n es el número de nodos en el árbol binario. La complejidad del espacio depende principalmente de la cantidad de niveles de llamadas recursivas. La cantidad de niveles de llamadas recursivas no excederá nn.
2. De abajo hacia arriba
1.Ideas
1. Dado que el método uno es recursivo de arriba hacia abajo, la función \texttt{height}height se llamará repetidamente para el mismo nodo, lo que generará una gran complejidad temporal. Si se utiliza el enfoque ascendente, la función \texttt{height}height solo se llamará una vez para cada nodo.
2. El método recursivo ascendente es similar al recorrido posterior al orden. Para el nodo actualmente atravesado, primero determine de forma recursiva si sus subárboles izquierdo y derecho están equilibrados y luego determine si el subárbol enraizado en el nodo actual está equilibrado. Si un subárbol está equilibrado, se devuelve su altura (la altura debe ser un número entero no negativo); de lo contrario, se devuelve -1−1. Si hay un subárbol desequilibrado, todo el árbol binario debe estar desequilibrado
2.Código
solución de clase: # De abajo hacia arriba, similar al recorrido posterior al orden, primero determine los subárboles izquierdo y derecho, y luego determine el nodo raíz, asegurándose de que la altura de cada nodo solo se calcule una vez def isBalanced(self, raíz: TreeNode) -> bool: def altura(raíz: TreeNode) -> int: si no es root: regresar 0 AlturaIzquierda = altura(raíz.izquierda) alturaderecha = altura(raíz.derecha) si AlturaIzquierda == -1 o AlturaDerecha == -1 o abs(AlturaIzquierda - AlturaDerecha) > 1: regresar -1 demás: devolver max(alturaizquierda, alturaderecha) 1 altura de retorno (raíz) >= 0
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol binario. Utilizando la recursividad ascendente, la altura de cálculo de cada nodo y el juicio de si está equilibrado solo deben procesarse una vez. En el peor de los casos, es necesario atravesar todos los nodos del árbol binario, por lo que la complejidad del tiempo es O (. norte)
Complejidad espacial: O (n), donde n es el número de nodos en el árbol binario. La complejidad del espacio depende principalmente de la cantidad de niveles de llamadas recursivas. La cantidad de niveles de llamadas recursivas no excederá nn.
111.Profundidad mínima del árbol binario.
1. Profundidad primero
0.Título
Dado un árbol binario, encuentre su profundidad mínima. La profundidad mínima es el número de nodos en el camino más corto desde el nodo raíz hasta el nodo hoja más cercano.
1.Ideas
1. La clave de esta pregunta es descubrir la condición final recursiva. La definición de nodo hoja es que cuando tanto el hijo izquierdo como el hijo derecho son nulos, se denomina nodo hoja. Cuando los hijos izquierdo y derecho del nodo raíz están vacíos, devuelve 1 Cuando uno de los hijos izquierdo y derecho del nodo raíz está vacío, devuelve la profundidad del nodo hijo que no está vacío. Cuando los hijos izquierdo y derecho del nodo raíz están vacíos, devuelve los valores de nodo de menor profundidad de los hijos izquierdo y derecho.
2.Código
clase Solución: def minDepth(self, raíz: TreeNode) -> int: si no es root: regresar 0 #Cuando los subárboles izquierdo y derecho están vacíos, es un nodo hoja y la distancia de retorno es 1 si no es root.left y no root.right: regresar 1 profundidad_min = 10**9 si root.left: #El subárbol izquierdo existe, compare la distancia mínima del subárbol izquierdo con la distancia mínima actual profundidad_min = min(self.profundidadmin(raíz.izquierda), profundidad_min) si root.right: profundidad_min = min(self.profundidadmin(raíz.derecha), profundidad_min) devolver min_profundidad 1
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol. Visita cada nodo una vez
Complejidad espacial: O (h), donde h es la altura del árbol. La complejidad del espacio depende principalmente de la sobrecarga del espacio de la pila durante la recursividad. En el peor de los casos, el árbol aparece en forma de cadena y la complejidad del espacio es O (n). En promedio, la altura del árbol está positivamente relacionada con el logaritmo del número de nodos y la complejidad del espacio es O (logN)
2.La amplitud primero
1.Ideas
Cuando encontramos un nodo hoja, devolvemos directamente la profundidad del nodo hoja. La naturaleza de la búsqueda en amplitud garantiza que la profundidad del nodo hoja buscado primero debe ser la más pequeña.
2.Código
clase Solución: def minDepth(self, raíz: TreeNode) -> int: si no es root: regresar 0 que = colecciones.deque([(raíz, 1)]) mientras que: nodo, profundidad = que.popleft() #El primer nodo hoja debe ser el nodo hoja más cercano si no es nodo.izquierda y nodo.derecha: profundidad de retorno si nodo.izquierda: que.append((nodo.izquierda, profundidad 1)) si nodo.derecho: que.append((nodo.derecha, profundidad 1)) regresar 0
3. Complejidad
Complejidad del tiempo: O (n), donde n es el número de nodos en el árbol. Visita cada nodo una vez
Complejidad del espacio: O (n), donde n es el número de nodos en el árbol. La complejidad del espacio depende principalmente de la sobrecarga de la cola. La cantidad de elementos en la cola no excederá la cantidad de nodos en el árbol.
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
7.La amplitud primero
0.frecuencia
200.279.301.199.101.127.102.407.133.107.103.126.773.994.207.111.847.417.529.130.542.690,,,743.210.913.512
8. La profundidad primero
0.frecuencia
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
9.Doble puntero
0.frecuencia
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
10. Clasificación
0.frecuencia
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
11.Método de retroceso
0.frecuencia
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
12. tabla hash
0.frecuencia
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
13.Pila
0.frecuencia
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
14. Programación dinámica
509. Números de Fibonacci
1. Programación dinámica
0.Título
Los números de Fibonacci, generalmente representados por F(n), forman una secuencia llamada secuencia de Fibonacci. La secuencia comienza con 0 y 1, y cada número posterior es la suma de los dos números anteriores.
1.Ideas
1. Determine el significado de la matriz dp y los subíndices.
La definición de dp[i] es: el valor de Fibonacci del i-ésimo número es dp[i]
2. Determinar la fórmula de recursividad.
Ecuación de transición de estado dp[i] = dp[i - 1] dp[i - 2]
3.Cómo inicializar la matriz dp
La pregunta también nos da directamente cómo inicializar dp[0] = 0;dp[1] = 1;
4. Determinar el orden transversal.
dp [i] depende de dp [i - 1] y dp [i - 2], por lo que el orden de recorrido debe ser de adelante hacia atrás.
5. Derivación de la matriz dp con ejemplos.
Cuando N es 10, la matriz dp debe tener la siguiente secuencia: 0 1 1 2 3 5 8 13 21 34 55 Si se escribe el código y se descubre que el resultado es incorrecto, imprima la matriz dp para ver si es coherente con la secuencia que derivamos.
2.Código
clase Solución: def fib(self, n: int) -> int: si norte < 2: regreso sustantivo, masculino— # Idea de matriz de desplazamiento, optimiza la complejidad del tiempo p, q, r = 0, 0, 1 # No es necesario mantener la secuencia completa, solo se pueden mantener 3 valores para i en el rango (2, n 1): pag, q = q, r r = p q volver r
3. Complejidad
Complejidad del tiempo: O (n) Complejidad espacial: O (1)
2. Exponenciación rápida de matrices
1.Ideas
1. La complejidad temporal del método 1 es O (n) O (n). El uso del método de exponenciación rápida matricial puede reducir la complejidad del tiempo
2. Relación de recurrencia
3. Siempre que podamos calcular rápidamente la enésima potencia de la matriz MM, podemos obtener el valor de F (n) F (n). Si M ^ n se calcula directamente, la complejidad del tiempo es O (n) O (n). Puede definir la multiplicación de matrices y luego usar un algoritmo de potencia rápida para acelerar el cálculo de M ^ n.
2.Código
clase Solución: def fib(self, n: int) -> int: si norte < 2: regreso sustantivo, masculino— q = [[1, 1], [1, 0]] res = self.matrix_pow(q, n - 1) devolver resolución[0][0] # Matriz elevada a la enésima potencia, resuelta rápidamente usando el método de bisección def Matrix_pow(self, a: Lista[Lista[int]], n: int) -> Lista[Lista[int]]: retiro = [[1, 0], [0, 1]] mientras norte > 0: si n & 1: # n/2 resto 1 ret = self.matrix_multiply(ret, a) n >>= 1 # n/2, encuentre la enésima potencia de la matriz por el método de bisección a = self.matriz_multiplicar(a, a) regresar #Multiplicación de matrices def matriz_multiplicar(self, a: Lista[Lista[int]], b: Lista[Lista[int]]) -> Lista[Lista[int]]: c = [[0, 0], [0, 0]] para i en el rango(2): para j en el rango(2): c[i][j] = a[i][0] * b[0][j] a[i][1] * b[1][j] volver c
3. Complejidad
Complejidad del tiempo: O (logn) Complejidad espacial: O (1)
3. Fórmula general
1.Ideas
1. Los números de Fibonacci F(n)F(n) son recursiones lineales homogéneas. Según la ecuación de recursividad F(n)=F(n−1) F(n−2), tales características se pueden escribir como ecuación: x^2. =x1
2.
3. Sustituyendo las condiciones iniciales F(0)=0, F(1)=1, obtenemos
4. Por tanto, la fórmula general de los números de Fibonacci es la siguiente:
2.Código
clase Solución: def fib(self, n: int) -> int: sqrt5 = 5**0.5 # raíz número 5 fibN = ((1 sqrt5) / 2) ** n - ((1 - sqrt5) / 2) ** n ronda de retorno (fibN/sqrt5)
3. Complejidad
La complejidad temporal y espacial de la función pow utilizada en el código está relacionada con el conjunto de instrucciones admitidas por la CPU. No la analizaremos en profundidad aquí.
70. Sube las escaleras
1. Programación dinámica
0.Título
Supongamos que estás subiendo escaleras. Se necesitan n pasos para llegar a la cima del edificio. Puedes subir 1 o 2 escalones a la vez. ¿De cuántas maneras diferentes puedes subir a la cima de un edificio?
1.Ideas
0.Guía
Hay una forma de subir las escaleras al primer piso y hay dos formas de subir las escaleras al segundo piso. Luego da dos escalones más en el primer tramo de escaleras para llegar al tercer piso, y un escalón más en el segundo piso de escaleras para llegar al tercer piso. Entonces, el estado de las escaleras al tercer piso se puede deducir del estado de las escaleras al segundo piso y del estado de las escaleras al primer piso, entonces se puede pensar en programación dinámica.
1. Determine el significado de la matriz dp y los subíndices.
dp[i]: Hay formas dp[i] de subir a la i-ésima escalera
2. Determinar la fórmula de recursividad.
De la definición de dp [i] se puede ver que dp [i] se puede derivar en dos direcciones. La primera es dp[i - 1]. Hay dp[i - 1] formas de subir las escaleras i-1. Luego, saltar un escalón a la vez es dp[i]. También hay dp[i - 2]. Hay dp[i - 2] formas de subir las escaleras i-2. Entonces saltar dos escalones en un solo paso es dp[i]. ¡Entonces dp[i] es la suma de dp[i - 1] y dp[i - 2]! Entonces dp[i] = dp[i - 1] dp[i - 2]
3.Cómo inicializar la matriz dp
Entonces, si i es 0, ¿cuál debería ser dp[i]? Hay muchas explicaciones para esto, pero básicamente se explican directamente hacia la respuesta.
Por ejemplo, hay una forma de obligarte a consolarte y subir al piso 0. No hacer nada también es una forma: dp [0] = 1, que equivale a pararse directamente en la cima del edificio.
Pensé que cuando corrí al piso 0, el método era 0. Solo podía caminar uno o dos pasos a la vez, sin embargo, el piso era 0 y simplemente me quedé en el techo. No había ningún método, así que dp [0. ] debería ser 0.
La mayoría de las razones por las que dp[0] debería ser 1 son en realidad porque si dp[0]=1, puedo solucionar el problema comenzando desde 2 durante el proceso de recursividad y luego confiar en el resultado para explicar dp[0] = 1
La pregunta dice que n es un número entero positivo, pero no dice que n sea 0 en absoluto. Por lo tanto, esta pregunta no debería discutir la inicialización de dp [0].
Mi principio es: no considere la inicialización de dp [0], solo inicialice dp [1] = 1, dp [2] = 2 y luego comience la recursividad desde i = 3, de modo que cumpla con la definición de dp [i ]
4. Determinar el orden transversal.
De la fórmula recursiva se puede ver dp[i] = dp[i - 1] dp[i - 2] que el orden transversal debe ser de adelante hacia atrás;
5. Derivación de la matriz dp con ejemplos.
Cuando N es 10, la matriz dp debe tener la siguiente secuencia: 0 1 1 2 3 5 8 13 21 34 55
2.Código
clase Solución: def subirEscaleras(self, n: int) -> int: # Idea de matriz de desplazamiento, optimiza la complejidad del tiempo si n <= 1: devuelve n dp = [0] * (n 1) # Cuando necesite usar subíndices de lista explícitamente, debe crear una lista dp[1], dp[2] = 1, 2 # No es necesario mantener la secuencia completa, solo mantenga 2 valores. Use pyq directamente sin crearlos. para i en el rango (3, n 1): suma = dp[1] dp[2] dp[1], dp[2] = dp[2], suma volver dp[2]
3. Complejidad
Complejidad del tiempo: O (n) Complejidad espacial: O (1)
2. Exponenciación rápida de matrices
Igual que 509
3. Fórmula general
Igual que 509
tema
Preguntas de entrevista clásicas
15,
Conceptos básicos de algoritmos
1. Complejidad del tiempo
1.Definición
La complejidad del tiempo es una función que describe cualitativamente el tiempo de ejecución del algoritmo.
2. ¿Qué es la Gran O?
Big O se usa para representar el límite superior. Cuando se usa como el límite superior del tiempo de ejecución en el peor de los casos del algoritmo, es el límite superior del tiempo de ejecución para cualquier entrada de datos. la complejidad temporal del algoritmo en general.
3. Diferencias en diferentes tamaños de datos.
Debido a que Big O es la complejidad del tiempo que se muestra cuando la magnitud de los datos atraviesa un punto y la magnitud de los datos es muy grande. Esta cantidad de datos es la cantidad de datos donde el coeficiente constante ya no juega un papel decisivo, por lo que llamamos complejidad del tiempo. Se omiten los coeficientes de términos constantes porque el tamaño de datos predeterminado es generalmente lo suficientemente grande. En base a este hecho, una clasificación de la complejidad temporal del algoritmo dada es la siguiente:
O(1) orden constante < O(logn) orden logarítmico < O(n) orden lineal < O(n^2) orden cuadrado < O(n^3) (orden cúbico) < O(2^n) (orden exponencial )
4. ¿Cuál es la base del registro en O (logn)?
Pero colectivamente decimos logn, es decir, ignorando la descripción de la base, el logaritmo de n con base 2 = el logaritmo de 10 con base 2 * el logaritmo de n con base 10, y el logaritmo de 10 con base 2 es un constante que puede ignorarse
Resumen de preguntas
0.interesante
Una pasada de codificación es tan feroz como un tigre, derrotando al 500% en sumisión.
Una serie de ideas florecieron con risas y la presentación superó el 80%.
1. No entiendo
1.árbol
145. Recorrido posterior al orden Recorrido de Morris
105. ¿Por qué no es posible utilizar el mapeo de índices en el método conciso?
2. Frecuencia de las entrevistas
1.Búsqueda binaria
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
2.La amplitud primero
200.279.301.199.101.127.102.407.133.107.103.126.773.994.207.111.847.417.529.130.542.690,,,743.210.913.512
3. tabla hash
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
4. Método de retroceso
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
5. Lista enlazada
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
6. Ordenar
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
7. La profundidad primero
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
8.árbol
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
9.matriz
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
10.Doble puntero
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
11.Pila
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
12 cuerdas
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
subtema
Preguntas similares
1.La suma de dos números
1,15,
Relacionado con el código
1. Estilo de código
1. Principios básicos
1.Sangría
Se prefiere utilizar 4 espacios. En la actualidad, casi todos los IDE convierten pestañas a 4 espacios de forma predeterminada, por lo que no hay gran problema.
2. Longitud máxima de línea
79 caracteres, sería mejor usar barras invertidas para los saltos de línea
3.Importar
1.Formato
La importación se realiza en la parte superior del archivo, después del comentario del archivo. La importación suele ser una importación de una sola línea o desde... importar.
2. Secuencia
1. Importación de biblioteca estándar 2. Importaciones de terceros relevantes 3. Importaciones de bibliotecas/aplicaciones locales específicas Coloque una línea en blanco entre cada grupo de importación. Se recomiendan las importaciones absolutas porque son más legibles; se pueden utilizar importaciones relativas explícitas en lugar de importaciones absolutas cuando se trata de diseños de paquetes complejos, que son demasiado detallados.
3. Atención
1. Según la experiencia práctica, se recomienda eliminar todas las importaciones innecesarias.
2. Importe esta parte, que se puede resolver perfectamente mediante la biblioteca Python isort (vscode usa isort por defecto)
3. Cuando from.. import... excede el límite de longitud de línea, comience una nueva línea: --sl/--force-single-line-imports
4. Forzar la clasificación por nombre de paquete: --fss/--force-sort-within-sections
Configurado en vscode como
4. Comentarios
¡Evite inconsistencias entre comentarios y código! !, lo cual es aún más exasperante que no tener ningún comentario. Cumplir principalmente con los siguientes puntos: 1. Al modificar el código, modifique primero los comentarios; 2. Los comentarios deben ser oraciones completas. Por lo tanto, la primera letra de la primera palabra debe estar en mayúscula, a menos que la primera palabra sea un identificador que comience con una letra minúscula; 3. Los comentarios breves no pueden terminar con punto, pero los comentarios con oraciones completas deben terminar con punto; 4. Cada línea de comentarios comienza con un # y un espacio; 5. Los comentarios de bloque deben utilizar el mismo nivel de sangría; 6. Los comentarios en línea deben estar separados del código por al menos dos espacios; 7. Intente hacer que su código "hable" y evite comentarios innecesarios.
5. Cadenas de documentos cadenas de documentos
Escriba cadenas de documentación para todos los módulos, funciones, clases y métodos públicos.
Para cadenas de documentos, CLion tiene un buen soporte y vscode puede implementar Python Docstring Generator a través de complementos.
6. Convención de nomenclatura
Las funciones, variables y propiedades se escriben en letras minúsculas, con guiones bajos entre las palabras, por ejemplo, lowercase_underscore. Las clases y excepciones deben nombrarse con la primera letra de cada palabra en mayúscula (mayúsculas altas), como CapitalizedWord. Las propiedades de la instancia protegida deben comenzar con un guión bajo. Las propiedades de instancia privada deben comenzar con dos guiones bajos. Las constantes a nivel de módulo deben escribirse con letras mayúsculas y con guiones bajos entre las palabras. El primer parámetro del método de instancia en la clase debe llamarse self, que representa el objeto mismo. El primer parámetro del método de clase (método cls) debe llamarse cls, lo que indica la clase en sí.
2. Presta atención a los detalles
0.Herramientas
Al utilizar la herramienta de formato de código Python yapf, puede resolver automáticamente algunos problemas de formato detallados. Combinado con isort, puede completar el formateo o la verificación de formato del código Python completamente a través de scripts. O configure el IDE para realizar automáticamente el formateo del código al editar y guardar. Esta es una buena práctica. Puedes instalar la biblioteca PEP8 y configurarla en pycharm, para que el IDE pueda ayudarte a organizar el código en el estilo PEP8.
1. Ajuste de código
Las nuevas líneas deben preceder a los operadores binarios
2. Línea en blanco
Hay dos líneas en blanco entre la función de nivel superior y la definición de clase. Hay una línea en blanco entre las definiciones de funciones dentro de la clase.
3. Comillas en cadena
Las comillas dobles y las comillas simples son lo mismo, pero es mejor seguir un estilo. Estoy acostumbrado a usar comillas simples porque: No es necesario mantener presionada la tecla Shift al escribir código para mejorar la eficiencia Algunas cadenas de lenguaje deben usar comillas dobles y Python no necesita agregar una barra invertida como escape al procesarlas.
4. Espacio
Utilice espacio para sangría en lugar de la tecla de tabulación. Utilice cuatro espacios para cada nivel de sangría relacionada con la sintaxis. Para expresiones largas que abarcan varias líneas, todas menos la primera línea deben tener una sangría de 4 espacios por encima del nivel habitual. Cuando utilice subíndices para recuperar elementos de una lista, llamar a funciones o asignar valores a argumentos de palabras clave, no agregue espacios alrededor de ellos. Al asignar un valor a una variable, se debe escribir un espacio en los lados izquierdo y derecho del símbolo de asignación, y solo uno
Inmediatamente dentro de paréntesis, corchetes o llaves Inmediatamente antes de una coma, punto y coma o dos puntos, debe haber un espacio después. Inmediatamente antes del paréntesis de apertura de una lista de parámetros de función Inmediatamente antes del paréntesis de apertura de una operación de índice o corte Coloque siempre 1 espacio a cada lado de los siguientes operadores binarios: asignación (=), asignación incremental (=, -=, etc.), comparación (==, <, >, !=, <>, <=, > = , en, no, en, es, no es), booleano (y, o, no) Pon espacios alrededor de los operadores matemáticos. Cuando se usa para especificar argumentos de palabras clave o valores de argumentos predeterminados, no use espacios alrededor = return magic(r=real, i=imag)
Agregue los espacios necesarios, pero evite los espacios innecesarios. Evite siempre los espacios en blanco finales, incluidos los caracteres invisibles. Por lo tanto, si su IDE admite la visualización de todos los caracteres invisibles, ¡actívelo! Al mismo tiempo, si el IDE admite la eliminación de contenido en blanco al final de la línea, ¡habilítelo también! yapf puede ayudarnos a resolver esta parte. Solo necesitamos formatear el código después de escribirlo.
5. Declaración compuesta
No se recomienda incluir varias declaraciones en una línea.
6. Coma al final
Cuando los elementos de la lista, los parámetros y los elementos de importación pueden seguir aumentando en el futuro, dejar una coma al final es una buena opción. El uso habitual es que cada elemento esté en su propia línea, con una coma al final y una etiqueta de cierre escrita en la línea siguiente después del último elemento. Si todos los elementos están en la misma línea, no es necesario hacer esto.
7. Solucionar problemas detectados por linter
Utilice flake8 para comprobar el código Python y modificar todos los errores y advertencias marcados, a menos que existan buenas razones.
2. Códigos de uso común
1. Diccionario
1. Cuente el número de ocurrencias
cuenta[palabra] = cuenta.get(palabra,0) 1
2. Lista
1. Listar la clasificación de palabras clave especificadas
items.sort(key=lambda x:x[1], reverse=True)# Ordena el segundo elemento en orden inverso
2. Convierta cada elemento de la lista de cadenas en una lista de números.
lista(mapa(eval,lista))
3. Invierta todos los elementos de la lista.
res[::-1]
4. Intercambia dos números de la lista.
res[i],res[j] = res[j],res[i] #No se requieren variables intermedias
5. Asigne una lista de longitud fija
G = [0]*(n 1) #Lista de longitud n 1
6. Encuentre el elemento más grande en el intervalo [i,j] en la lista de arr.
máx(arr[i:j 1])
7. Utilice este elemento para dividir la lista en orden.
left_l = orden[:idx] right_l = orden[idx 1:]
3. Bucle/juicio
1. Cuando solo necesita determinar el número de bucles y no necesita obtener el valor
para _ en rango (len (cola)):
2.Abreviatura de if...else
node.left = recur_func(left_l) si left_l else Ninguno
3. Ciclo inverso
para i en rango(len(postorder)-2, -1,-1)
4. Obtenga elementos y subíndices al mismo tiempo.
para i, v en enumerar (numeros):
5. Recorre varias listas al mismo tiempo y devuelve varios valores.
para net, opt, l_his en zip(redes, optimizadores, pérdidas_his):
Sin zip, solo se generará un valor cada vez
6. Recorre varias listas y devuelve un valor.
para etiqueta en ax.get_xticklabels() ax.get_yticklabels():
4. Mapeo
1. Convierta rápidamente estructuras transitables en asignaciones correspondientes a subíndices
índice = {elemento: i para i, elemento en enumerar (en orden)}
También se puede lograr usando list.index (x), pero la lista debe recorrerse cada vez y el tiempo es O (n). Lo anterior es O (1).
3. Métodos comúnmente utilizados
1. Viene con el sistema
0.Clasificación
1.Conversión de tipo
1.int(x)
1. Cuando el tipo de coma flotante se convierte a un tipo entero, la parte decimal se descarta directamente.
2.flotar(x)
3.cadena(x)
1.ordenado(núm)
1. Ordena los elementos especificados.
2.mapa (función, lista)
1. Aplicar la función del primer parámetro a cada elemento del segundo parámetro.
mapa(evaluación,lista)
3.len(i)
1. Obtén la longitud, que puede ser de cualquier tipo.
4.enumerar()
1. Combine un objeto de datos transitable (como una lista, tupla o cadena) en una secuencia de índice y enumere el subíndice de datos y los datos al mismo tiempo. Generalmente utilizado en bucles for: para i, elemento en enumerar (seq):
2.enumerar (secuencia, [inicio = 0]), el subíndice comienza desde 0 y devuelve el objeto de enumeración (enumeración)
5.tipo(x)
1. Determinar el tipo de variable x, aplicable a cualquier tipo de datos.
2. Si necesita utilizar el tipo de variable como condición en el juicio condicional, puede utilizar la función type() para realizar una comparación directa.
si tipo(n) == tipo(123):
2.Lista[]
1. Cuando se utiliza la pila
1. Empuje hacia la pila
pila.append()
2. Sal de la pila
pila.pop()
Pop el último elemento
3. Devuelve el elemento superior de la pila.
No hay ningún método de vista previa en la lista. Solo puede aparecer primero y luego agregar.
2. Cuando se usa en una cola
1. Únete al equipo
cola.append()
2.Dejar el equipo
cola.pop(0)
Quitar de cola el primer elemento
3. Obtener el subíndice del elemento
m_i = números.index(max_v)
3. Deque de cola
1. La cola de dos extremos se utiliza como cola.
1. Únete al equipo
cola.append()
2.Dejar el equipo
cola.popleft()
4. Errores/diferencias comunes
1. Formato de pregunta
1.IndentationError: se esperaba un bloque sangrado
Hay un problema con la sangría. El error más común es mezclar las teclas Tab y Espacio para lograr la sangría del código.
Otra razón común para el error anterior es que no hay sangría en la primera línea. Por ejemplo, al escribir una declaración if, agregue dos puntos después. Si cambia directamente la línea, muchos editores de código sangrarán automáticamente la primera línea. Sin embargo, es posible que algunos editores de código no tengan esta función. En este caso, es mejor desarrollar el hábito de sangrar manualmente. No presione la barra espaciadora varias veces seguidas. Se recomienda presionar simplemente la tecla Tab.
1.problema de pycharm
1. Cómo deshacerse del mensaje de que el certificado del servidor no es de confianza en pycharm
Haga clic en Archivo > Configuración > Herramientas > Certificados de servidor > Aceptar certificados no confiables automáticamente
1.habilidades de pycharm
1. Importar archivos Python al proyecto actual
Copie directamente el archivo correspondiente en el administrador de archivos al directorio correspondiente al proyecto actual y aparecerá directamente en pycharm. Si el problema de la versión no ocurre, contraiga usted mismo el directorio del proyecto actual y luego expándalo nuevamente.
2. Error de instalación de la línea de comando
0. Para ver el tipo de error, asegúrese de mirar el error en la línea encima de la última línea divisoria.
Otros errores
1."ningún módulo llamado XX"
A medida que el nivel de desarrollo de todos mejora y la complejidad del programa aumenta, se utilizarán cada vez más módulos y bibliotecas de terceros en el programa. El motivo de este error es que la biblioteca "XX" no está instalada, pip install ww.
1.error
1.Error: error en el comando con el estado de salida 1
Descargue el paquete de instalación de terceros correspondiente e ingrese al directorio de descarga para instalar el nombre completo del archivo descargado.
2.error: se requiere Microsoft Visual C 14.0
Instale Microsoft Visual C 14.0, el blog tiene la dirección de descarga visualcppbuildtools_full
3.error: comando no válido 'bdist_wheel'
rueda de instalación pip3
2.Error de atributo error de atributo
0. Solución general
Si le pregunta qué archivo de módulo tiene un error, busque este módulo, elimínelo y reemplácelo con el mismo archivo de un amigo que pueda ejecutarse.
1.AttributeError: el módulo 'xxx' no tiene el atributo 'xxx'
1. El nombre del archivo entra en conflicto con las palabras clave propias de Python y similares.
2. Dos archivos py se importan entre sí, lo que provoca que uno de ellos se elimine.
2.AttributeError: el módulo 'seis' no tiene el atributo 'principal'
El problema de la versión 10.0 de pip no tiene main(), Es necesario degradar la versión: python -m pip install –upgrade pip==9.0.1
Algunas API cambiaron después de Pip v10, lo que resultó en incompatibilidad entre las versiones antiguas y nuevas, lo que afecta nuestra instalación y actualización de paquetes. ¡Simplemente actualice el IDE y listo! Al actualizar, el IDE también nos dio indicaciones amigables. PyCharm -> Ayuda -> Buscar actualizaciones
No actualice la versión descifrada; de lo contrario, la información de activación dejará de ser válida.
3.AttributeError: el módulo 'async io' no tiene el atributo 'ejecutar'
Nombraste un archivo asyncio py Si la verificación no es la primera, debe verificar su versión de Python porque python3.7 y versiones posteriores solo admiten el método de ejecución. 1 Actualizar la versión de Python 2 ejecución se reescribe de la siguiente manera bucle = asyncio.get_event_loop() resultado = loop.run_until_complete(coro)
4.AttributeError: el módulo 'asyncio.constants' no tiene el atributo '_SendfileMode'
Reemplazar archivos de constantes en asyncio
3.Error de tipo
1. "TypeError: el objeto 'tupla' no se puede interpretar como un número entero"
t=('a','b','c') para i en el rango (t):
Problema típico de error de tipo En el código anterior, la función range() espera que el parámetro entrante sea un número entero (entero), pero el parámetro entrante es una tupla (tupla). La solución es cambiar el parámetro de entrada tupla t a The. El número de tuplas puede ser de tipo entero len(t). Por ejemplo, cambie rango(t) en el código anterior a rango(len(t)).
2. "TypeError: el objeto 'str' no admite la asignación de elementos"
Causado al intentar modificar el valor de una cadena, que es un tipo de datos inmutable
s[3] = 'a' se convierte en s = s[:3] 'a' s[4:]
3. "TypeError: no se puede convertir el objeto 'int' a str implícitamente"
Causado por intentar concatenar un valor que no es una cadena con una cadena, simplemente convierta el valor que no es una cadena en una cadena con str()
4. "TypeError: tipo no comparable: 'lista'
Cuando se utiliza la función set para construir un conjunto, los elementos de la lista de parámetros dada no pueden contener listas como parámetros.
5.TypeError: no se puede utilizar un patrón de cadena en un objeto similar a bytes
Al cambiar entre python2 y python3, inevitablemente encontrará algunos problemas. Las cadenas Unicode en python3 están en el formato predeterminado (tipo str) y las cadenas codificadas en ASCII (tipo bytes) contienen valores de bytes y en realidad no lo son. un carácter. String, python3 y tipo de matriz de bytes bytearray) debe estar precedido por el operador b o B, en python2, es lo contrario, la cadena codificada en ASCII es la predeterminada y la cadena Unicode debe estar precedida por el operador u o U;
import chardet #Necesita importar este módulo y detectar el formato de codificación tipo_codificación = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica en consecuencia y asígnalo al identificador original (variable)
4.IOError
1. "IOError: Archivo no abierto para escritura"
>>> f=abrir ("hola.py") >>> f.escribir ("prueba")
La causa del error es que el modo de parámetro del modo lectura-escritura no se agrega a los parámetros entrantes de open ("hello.py"), lo que significa que la forma predeterminada de abrir el archivo es de solo lectura.
La solución es cambiar el modo modo a permiso de modo de escritura w, f = open("hello. py", "w ")
5.Error de sintaxis Errores gramaticales
1.SyntaxError: sintaxis no válida”
Causado por olvidar agregar dos puntos al final de declaraciones como if, elif, else, for, while, class y def.
Usar incorrectamente "=" en lugar de "==" En los programas Python, "=" es un operador de asignación y "==" es una operación de comparación igual.
6.UnicodeDecodeError Error de interpretación de codificación
1. El códec 'gbk' no puede decodificar el byte
La mayoría de las veces esto se debe a que el archivo no está codificado en UTF8 (por ejemplo, puede estar codificado en GBK) y el sistema utiliza la decodificación UTF8 de forma predeterminada. La solución es cambiar al método de decodificación correspondiente: con open('acl-metadata.txt','rb') como datos:
abrir (ruta, '-modo-', codificación = 'UTF-8'), es decir, abrir (nombre del archivo de ruta, modo de lectura-escritura, codificación)
Modos de lectura y escritura más utilizados
7.Error de valor
1.demasiados valores para descomprimir
Al llamar a una función, no hay suficientes variables para aceptar el valor de retorno.
8.OSError
1.WinError 1455] El archivo de página es demasiado pequeño y la operación no se puede completar.
1. Reinicie pycharm (básicamente inútil)
2. Establezca num_works en 0 (quizás inútil)
3. Aumente el tamaño del archivo de página (resuelva completamente el problema)
9.Error de importación
1.Error al cargar la DLL: el archivo de página es demasiado pequeño y la operación no se puede completar.
1. No solo se está ejecutando un proyecto, sino que también se está ejecutando el programa Python de otro proyecto, simplemente apáguelo.
2. El sistema operativo Windows no admite la operación multiproceso de Python. La red neuronal utiliza múltiples procesos en la carga de conjuntos de datos, así que establezca el parámetro num_workers en DataLoader en 0.
1.Error al cargar la DLL: el sistema operativo no puede ejecutar %1
0. Recientemente, cuando estaba ejecutando un proyecto scrapy, el marco scrapy que se instaló de repente informó un error y me tomó por sorpresa.
1. Debido a que no es difícil instalar scrapy en Anaconda, no es tan simple y eficiente como reinstalar para encontrar una solución.
2. No puedo desinstalarlo por completo simplemente usando el comando conda remove scrapy. La razón puede ser que instalé scrapy dos veces usando pip y conda respectivamente. Los lectores pueden probar los comandos de desinstalación pip y conda.
pip desinstalar scrapy conda eliminar scrapy
Reinstale pip install scrapy
error de clase
1. Cuestiones patrimoniales de herencia múltiple de clases.
Solo modificamos A.x, ¿por qué también se modificó C.x? En los programas Python, las variables de clase se tratan internamente como diccionarios, que siguen el orden de resolución de métodos (MRO) comúnmente citado. Entonces, en el código anterior, dado que no se encuentra el atributo x en la clase C, buscará su clase base (aunque Python admite la herencia múltiple, solo hay A en el ejemplo anterior). En otras palabras, la clase C no tiene su propio atributo x, que es independiente de A. Por lo tanto, C.x es en realidad una referencia a A.x.
error de alcance
1. Variables globales y variables locales
1. La variable local x no tiene valor inicial y la variable externa X no se puede introducir internamente.
2. Diferentes operaciones en listas
Fool no asignó un valor a lst, pero Fool2 sí lo hizo. Ya sabes, lst = [5] es la abreviatura de lst = lst [5], y estamos intentando asignar un valor a lst (Python lo trata como una variable local). Además, nuestra asignación a lst se basa en el propio lst (que Python vuelve a tratarlo como una variable local), pero aún no se ha definido, por lo que se produce un error. Entonces aquí debemos distinguir entre el uso de variables locales y variables externas.
2.1 Error de actualización de Python2 a Python3
1.print se convierte en print()
1. En la versión Python 2, imprimir se usa como una declaración. En la versión Python 3, imprimir aparece como una función. En la versión Python 3, todo el contenido impreso debe estar entre paréntesis.
2.raw_Input se convierte en entrada
En la versión Python 2, la funcionalidad de entrada se implementa a través de raw_input. En la versión Python 3, se implementa mediante entrada
3. Problemas con números enteros y división
1. "TypeError: el objeto 'flotante* no se puede interpretar como un número entero"
2. En Python 3, int y long están unificados en el tipo int. Int representa un número entero de cualquier precisión. El tipo largo ha desaparecido en Python 3 y el sufijo L también ha quedado obsoleto cuando el uso de int excede el tamaño del entero local. , no provocará la excepción OverflowError
3. En versiones anteriores de Python 2, si el parámetro es int o long, se devolverá el resultado redondeado hacia abajo (piso) de la división, y si el parámetro es flotante o complejo, se devolverá el resultado equivalente, una buena aproximación. del resultado después de la división
4. "/" en Python 3 siempre devuelve un número de punto flotante, lo que siempre significa división hacia abajo. Simplemente cambie "/" a "//" para obtener el resultado de la división entera.
4. Actualización importante del manejo de excepciones.
1. En los programas Python 2, el formato para detectar excepciones es el siguiente: excepto excepción, identificador
excepto ValueError, e: # Python 2 maneja excepciones únicas excepto (ValueError, TypeError), e: # Python 2 maneja múltiples excepciones
2. En los programas Python 3, el formato para detectar excepciones es el siguiente: excepto excepción como identificador
excepto ValueError como e: # Python3 maneja una única excepción excepto (ValueError, TypeError) como e: # Python3 maneja múltiples excepciones
3. En los programas Python 2, el formato para generar excepciones es el siguiente: plantear excepción, argumentos
4. En los programas Python 3, el formato para generar excepciones es el siguiente: plantear excepción (args)
aumentar ValueError, e # método Python 2.x aumentar ValueError(e) # método Python 3.x
5.xrange() se convierte en rango()
"NameError: el nombre 'xrange' no está definido"
6. La recarga no se puede utilizar directamente
"el nombre 'recargar' no está definido y AttributeError: el módulo 'sys' no tiene att"
importar importarlib importarlib.recargar (sys)
7. No más tipos Unicode
"Python Unicode no está definido"
En Python 3, el tipo Unicode ya no existe y se reemplaza por el nuevo tipo str. El tipo str original en Python 2 fue reemplazado por bytes en Python 3
8.has_key ha sido abandonado
"AttributeError: el objeto 'dieta' no tiene el atributo 'has_key' "
has_key ha sido abandonado en Python 3. El método de modificación es utilizar en en lugar de has_key.
9.urllib2 ha sido reemplazado por urllib.request
"lmportError: No hay ningún módulo llamado urllib2"
La solución es modificar urllib2 a urllib.request
10. Problemas de codificación
TypeError: no se puede utilizar un patrón de cadena en un objeto similar a bytes
Al cambiar entre python2 y python3, inevitablemente encontrará algunos problemas. Las cadenas Unicode en python3 están en el formato predeterminado (tipo str) y las cadenas codificadas en ASCII (tipo bytes) contienen valores de bytes y en realidad no lo son. un carácter. String, python3 y tipo de matriz de bytes bytearray) debe estar precedido por el operador b o B, en python2, es lo contrario, la cadena codificada en ASCII es la predeterminada y la cadena Unicode debe estar precedida por el operador u o U;
import chardet #Necesita importar este módulo y detectar el formato de codificación tipo_codificación = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica en consecuencia y asígnalo al identificador original (variable)
2.1 Comando de línea del símbolo del sistema
1. Directorio de operaciones
1.cd cambia el subdirectorio actual, puede copiar directamente la ruta e ingresarla de una vez
2. El comando CD no puede cambiar el disco actual. CD... regresa al directorio anterior. CD\ significa regresar al directorio del disco actual. Cuando el CD no tiene parámetros, se muestra el nombre del directorio actual.
3.d: cambiar la ubicación del disco
2.Relacionado con Python
1.pip
0.Obtén ayuda
ayuda pipa
0. Cambiar fuente de pips
1. Uso temporal
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nombre del paquete
2. Establecer como predeterminado
conjunto de configuración de pip global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Después de configurarlo como predeterminado, las bibliotecas de instalación futuras se descargarán desde la fuente de Tsinghua y no es necesario agregar la URL de la fuente reflejada.
3. Dirección de origen espejo convencional
1. Instale la biblioteca especificada
nombre del paquete de instalación de pip
2. Instale la versión especificada de la biblioteca especificada.
instalación de pip nombre_paquete==1.1.2
3. Verifique qué bibliotecas están instaladas
lista de pipas
4. Ver la información específica de la biblioteca.
pip show -f nombre del paquete
5. ¿Dónde está instalado pip?
pipa -V
6. Biblioteca de instalación por lotes
instalación de pip -r d:\\requisitos.txt Escriba directamente el nombre del paquete == número de versión en el archivo
7. Instale la biblioteca usando el archivo wheel.
1. Busque el archivo .whl de la biblioteca correspondiente en el sitio web a continuación, busque con Ctrl F y preste atención a la versión correspondiente. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. En la carpeta donde se encuentra .whl, presione la tecla Shift y haga clic con el botón derecho del mouse para abrir la ventana CMD o PowerShell. (O ingrese a esta carpeta a través de la línea de comando)
3. Ingrese el comando: instalación de pip matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Desinstalar la biblioteca
pip desinstalación nombre_paquete
9.Actualizar biblioteca
instalación de pip --actualizar nombre_paquete
10. Guarde la lista de bibliotecas en el archivo especificado.
congelación de pip > nombre de archivo.txt
11. Verifique las bibliotecas que deben actualizarse
lista de pips -o
12. Verifique si hay problemas de compatibilidad
Verifique si la biblioteca instalada tiene dependencias compatibles pip check nombre-paquete
13. Descargue la biblioteca a local.
Descargue la biblioteca en un archivo local especificado y guárdela en formato whl pip descargar nombre_paquete -d "Ruta del archivo para guardar"
2. Empaquetar el programa en un archivo ejecutable.
pyinstaller -F nombre de archivo.py
3. Escritura de código
1. Sin formato
1. Sin i, sólo se puede escribir como i =1
2. Diferentes formatos
1. clase
1. Parámetros
0. Al definir una clase, el primer parámetro debe ser self y lo mismo se aplica a todos los métodos. Al llamar a miembros de esta clase, se deben usar self.
1. Para los parámetros de la clase, primero escriba el nombre de la variable, agregue dos puntos y luego escriba el nombre del tipo, separado por comas.
2. Una vez completada la definición de la clase, puede agregar -> escriba el nombre al final para indicar el tipo de valor de retorno.
2. Llamar
1. Para llamarse a sí mismo para recursividad en una clase, debe usar la función self.self (no necesita agregar self en los parámetros)
2. Cuando se usa una clase, no es necesario crear una nueva clase, solo úsela directamente root = TreeNode(max_num)
3.Método
1._Comience con un guión bajo para definir el método de protección
2.__Comience con dos guiones bajos para definir métodos privados
subtema
2. Números
1. Expresión de más y menos infinito
flotante("inf"), flotante("-inf")
3. Símbolos
1. En Python / significa división normal con resto, // significa división entera sin resto.
2. ¡No! en Python está representado por no
3.El cuadrado en Python es **
4. Declaración
1. Se deben agregar dos puntos después de todas las declaraciones relacionadas con palabras clave: excepto retorno
2. No es necesario agregar () a las condiciones en bucles, juicios y otras declaraciones similares, y no hay {} en los bloques de declaraciones. Utilice sangría para representar estrictamente el formato.
5. Comentarios
1. Comentario de una sola línea #
2. Comentarios de varias líneas ''' o """
4.Código
1. Lista
1. Cuando necesite usar subíndices de lista explícitamente, debe usar G = [0]*(n 1) # para crear una lista con una longitud de n 1; de lo contrario, el subíndice estará fuera de los límites.
2. Al crear una lista bidimensional de longitud fija, si *n falla, intente usar un bucle
dp = [[float('inf') para _ en rango(n)] para _ en rango(n)]
3. Si el valor de retorno de la función es una lista, pero solo se recibe una variable, agregue una coma
línea, = ax.plot(x, np.sin(x))
5.Programación en Python
1. Problema de archivo
1. Al importar otros proyectos que requieran un archivo, utilice la ruta absoluta del archivo.
2. Archivo de ejecución de línea de comando
Nombre de archivo Python.py
3. Recursos espejo de paquetes de terceros
Cuando utilice pycharm para descargar paquetes de terceros, agréguelo en Administrar repositorios http://mirrors.aliyun.com/pypi/simple/, elimine la URL original
6.Teclas de acceso directo de Pycharm
1. Comentar (añadir/eliminar)
Control /
Comentarios de una sola línea#
2.Código de desplazamiento a la derecha
Pestaña
3. Código de desplazamiento a la izquierda
Pestaña Mayús
4. Sangría automática
Ctrl-alt I
5. correr
Ctrl mayús F10
6.Formato estándar PEP8
Ctrl-alt L
También es la tecla de acceso directo de bloqueo de QQ. Cancela la configuración en QQ.
6.1 Solución rápida
alt enter y presione enter
7. Copie una línea/múltiples líneas/parte seleccionada
CtrlD
8. Eliminar una o varias líneas
Ctrl Y
9.Encontrar
CtrlF
9.1 Búsqueda global
Ctrl mayús F
10.Reemplazo
CtrlR
10.1 Reemplazo global
Ctrl mayús R
11.Mueva el cursor a la siguiente línea.
cambio Entrar
12.Clic del cursor de varias líneas
clic con el botón izquierdo del mouse
13. Saltar al siguiente punto de interrupción
Alt F9
14.Cancelación
Ctrl-Z
14.1 Anti-cancelación
Ctrl mayúscula Z
15. Copie el código de la clase principal.
Ctrl o
16. Seleccione bloque de palabra/código
Ctrl W
17. Ver documentos rápidamente (información del código)
Ctrl-Q
18. Inserta una línea hacia abajo en cualquier posición.
cambio entrar
19. Inserta una fila hacia arriba en cualquier posición.
Ctrl-alt-entrar
20. Ver vista del proyecto
alternativa 1
21. Ver la vista de estructura.
alternativa 7
22. Ingrese al código rápidamente
Ctrl clic izquierdo
23. Ver historial rápidamente
Tecla Alt izquierda (regreso)/derecha (adelante)
24. Ver rápidamente diferentes métodos
Alt arriba/abajo
25. Cambiar de vista
Pestaña Ctrl
26. Ver archivos de recursos
cambiar dos veces
27. Vea dónde se llama el método.
Ctrl alt H Doble clic para determinar la posición
28.Ver clase principal
Ctrl-U
29. Ver relación de herencia
Ctrl-H
7.página pycharm
0.Barra de menú
1.Ver ventana
1.Barra de navegación barra de navegación
2.Refactorización Refactorización
3.Herramientas
subtema
4.Control de versiones VCS
1. Depurar el código
1. Haga clic delante del código para insertar un punto de interrupción, haga clic en el rastreador para depurarlo y haga clic en el cuadro al lado del rastreador para finalizar la depuración.
2. Haga clic en el icono ↘ para saltar al siguiente punto de interrupción y podrá observar continuamente el valor de la variable.
2.configuración de configuración
1.apariencia y comportamiento interfaz y comportamiento
1.apariencia estilo general
1.Tema temático
1.Darcula tema negro
2. Tema de alto contraste y alto contraste.
3.Tema brillante Intellij
2.fuente personalizada fuente personalizada
2.configuración del sistemaconfiguración del sistema
1.actualizar
La detección automática de actualizaciones se puede desactivar
2.teclas de acceso directo del mapa de teclas
1. Según la vista del sistema, puede buscar directamente (nota de comentario)
3.editor solo edita el área
1. Fuente del código de fuente (tamaño ajustable)
2.Esquema de color Esquema de color del área del código
3. Estilo de código estilo de código
0.Python puede realizar cambios en cada detalle.
1.Python
1.Número de sangría
2.Configuración del espacio espacial
3.Envoltura y tirantes
1. Ajuste duro con el número máximo de códigos en una línea
4.Líneas en blanco líneas en blanco
4.Inspecciones
1.PEP 8 es un código estándar, no un error gramatical. Intente mantenerlo lo más estándar posible.
2. Puede establecer qué contenido se verifica y también puede establecer el rigor de la verificación en Gravedad.
5.Plantillas de archivos y códigos Plantillas de archivos y códigos
1. Agregue información en Python.Script que se mostrará cada vez que se cree un nuevo archivo. Descubra qué información puede agregar en línea
6.Codificación de archivos de codificaciones de archivos
1.UTF-8 predeterminado
7.Plantillas dinámicas Live Templates (fáciles de usar y dominar)
0. Puede hacer clic en el signo más para agregar la plantilla usted mismo y asegurarse de configurar la ubicación de uso.
1.para bucle
Escribe iter para seleccionar un bucle y sigue presionando Enter para escribir código
2. Utilice un bucle for para escribir una lista
Solo escribe completo
4.Complementos
5.Proyecto
1.Intérprete del proyecto Project Interpret
1. Puede administrar y agregar bibliotecas de terceros (haga clic en el signo más y busque)
2. Se pueden configurar diferentes intérpretes para el proyecto actual.
3.Gestión de proyectos de proyectos.
1. Haga clic derecho en el archivo y seleccione Mostrar en el Explorador para abrir directamente la ubicación del archivo.
2.Nuevo
1.Archivo
Pueden ser varios otros archivos, no necesariamente archivos Python.
2.Nuevo archivo borrador
Crear archivos temporales, equivalentes a papel borrador, utilizados para probar parte del código.
3.Directorio directorio
Crear una nueva carpeta de nivel inferior
4.Archivo Python
En comparación con las carpetas normales, hay más archivos de inicialización de Python vacíos.
5. Al crear un nuevo archivo HTML, puede hacer clic derecho en él y abrirlo en un navegador para ver el efecto de visualización.
4. Página de resultados del código
1.terminal terminal
1. Es lo mismo que el CMD del sistema. Puede instalar el paquete directamente aquí y usar el comando dos.
2.Consola de consola Python
Puede escribir código Python directamente, ejecutarlo de forma interactiva y editarlo línea por línea.
3.TODO
Es equivalente a una nota. Escriba TODO ('') en el punto del código, para que pueda encontrarlo rápidamente y continuar trabajando. Puede usarse para la cooperación mutua.
4. El avatar de la extrema derecha.
El rigor de la verificación del código se puede ajustar. El modo de ahorro de energía equivale a colocar la flecha en el extremo izquierdo. No se verificarán todos los errores gramaticales.
5.Entorno virtual
1.
2. El entorno virtual se crea porque en el desarrollo real es necesario utilizar diferentes versiones del intérprete de Python y diferentes versiones de la misma biblioteca al mismo tiempo. Por lo tanto, es necesario crear un entorno virtual para aislar el entorno del proyecto de otros entornos (entorno del sistema, otros entornos virtuales)
3. Hay tres formas de crear un entorno virtual en PyCharm: virtualen, conda y pipen.
4. Se puede imaginar que Virtualen crea una copia aislada del entorno del sistema actual. El intérprete utilizado es el mismo que instaló (copia).
5. Conda selecciona una versión de Python específica según sus necesidades, luego descarga la versión relevante de Internet y crea un nuevo entorno que es diferente del entorno del sistema. El intérprete utilizado también es diferente del que instaló.
6. Pipen es similar a virtualen. También crea una copia basada en el entorno del sistema existente, pero pipen usa Pipfile en lugar del request.txt de virtualen para la gestión de dependencias, lo cual es más conveniente.
6.Interrelación
7.SVN
1. Al descargar desde el sitio web oficial, seleccione la versión 1.10 en inglés, que es diferente de la versión china.
2. Al instalar TortoiseSVN.msi, asegúrese de verificar las herramientas de línea de comando
3. Aparecerá una serie de archivos svn.exe en el directorio bin instalado.
4. Configure en pycharm y busque el archivo svn.exe en el directorio bin en configuración/control de versiones/subversión.
5. Haga clic derecho en el archivo modificado en la carpeta para ver las modificaciones.
6. Haga clic derecho en el proyecto y aparecerá un nuevo acceso directo de subversión. Puede enviar confirmaciones y deshacer todos los cambios revertidos.
8. Complementos fáciles de usar
1.Promotor clave X
Enseñarle, ¿qué operación abreviada debería utilizar para mejorar la eficiencia en esta operación? Le recuerdo que no ha configurado una tecla de acceso directo para esta operación en este momento. ¿Qué tal si configura una rápidamente?
2.Probador de expresiones regulares
Puede probar expresiones regulares. Haga clic en el pequeño botón rectangular en la parte inferior izquierda de la interfaz de PyCharm para encontrar la opción Regex Tester.
3.PEP8 automático
pip instale autopep8 y luego importe esta herramienta en PyCharm (Configuración-Herramientas-Herramientas externas). Las configuraciones específicas son las siguientes.
4.CodeGlance
Barra de desplazamiento para la función de vista previa del código
5.Perfil en PyCharm
Haga clic en Ejecutar -> Perfil 'Programa' para realizar un análisis de rendimiento del código Haga clic en la interfaz Call Graph para mostrar intuitivamente la relación de llamada directa, el tiempo de ejecución y el porcentaje de tiempo de cada función.
6.Analizador Json
A menudo verifico si una cadena JSON es legal. En el pasado, mi método era abrir el sitio web en línea https://tool.lu/json/ y embellecerlo directamente para verificar que solo el formato JSON es correcto y legal. , hay un complemento en PyCharm específicamente para hacer esto
7.Par de soportes resaltados
Color de soporte resaltado en el editor
8.Colorer de soportes anidados
Mostrar colores para diferentes pares de soportes
9.Inspeccionar el código en PyCharm
Haga clic en la carpeta del proyecto, luego haga clic derecho y seleccione Inspeccionar código para activar la inspección estática.
5. Fragmentos de código comunes
1.Entrada
1. Obtener entrada del usuario de longitud variable
def getNum(): #Obtener entrada del usuario de longitud variable números = [] iNumStr = input("Ingrese un número (presione Enter para salir): ") mientras iNumStr != "": números.append(eval(iNumStr)) iNumStr = input("Ingrese un número (presione Enter para salir): ") devolver números
2.Texto
1. Normalización y eliminación de ruido del texto en inglés.
def obtenerTexto(): txt = abrir("hamlet.txt", "r").read() txt = txt.lower() #Convertir todo a letras minúsculas para ch en '!"#$%&()* ,-./:;<=>?@[\\]^_'{|}~': txt = txt.replace(ch, " ") #Reemplazar caracteres especiales en el texto con espacios devolver texto
2. Cuente la frecuencia de las palabras en el texto en inglés.
hamletTxt = obtenerTexto() palabras = hamletTxt.split() #Separar texto con espacios y convertirlo en una lista counts = {} #Crear un nuevo diccionario para palabra en palabras: #Cuente la frecuencia de cada palabra, el valor predeterminado es 0 cuenta[palabra] = cuenta.get(palabra,0) 1 items = list(counts.items()) #Convertir diccionario en lista items.sort(key=lambda x:x[1], reverse=True)# Ordena el segundo elemento en orden inverso para i en el rango (20): palabra, recuento = elementos[i] imprimir ("{0:<10}{1:>5}".formato(palabra, recuento))
3. Estadísticas de frecuencia de palabras en textos chinos
importar jieba txt = open("tresreinos.txt", "r", codificación='utf-8').read() palabras = jieba.lcut(txt) cuenta = {} para palabra en palabras: si len(palabra) == 1: continuar demás: cuenta[palabra] = cuenta.get(palabra,0) 1 items = list(counts.items()) #Convertir a lista para ordenar items.sort(clave=lambda x:x[1], reverso=Verdadero) para i en el rango(15): palabra, recuento = elementos[i] imprimir ("{0:<10}{1:>5}".formato(palabra, recuento))
3.matriz
1. Encuentra el valor máximo en la matriz.
1. Los elementos se repiten.
max_v, m_i = flotante(-inf), 0 #Combinar un objeto de datos transitable (como una lista, tupla o cadena) en una secuencia de índice, Enumerar subíndices de datos y datos al mismo tiempo. para i, v en enumerar (numeros): si v > max_v: máx_v = v m_i = yo
2. Sin elementos repetidos
max_v = max(núms) m_i = números.index(max_v)
2. Dicotomía
clase Solución: def searchInsert(self, nums: Lista[int], destino: int) -> int: izquierda, derecha = 0, len(nums) #Utilice el intervalo cerrado por la izquierda y abierto por la derecha [izquierda, derecha) while left < right: # Abierto a la derecha, por lo que no puede haber =, el intervalo no existe mid = izquierda (derecha - izquierda)//2 # Evita el desbordamiento, //Indica división de enteros if nums[mid] < target: # El punto medio es menor que el valor objetivo. En el lado derecho, se pueden obtener posiciones iguales. left = mid 1 # Izquierda cerrada, entonces 1 demás: right = mid # Abierto a la derecha, el punto final derecho real es mid-1 return left # Cuando finaliza este algoritmo, se garantiza que izquierda = derecha, y el retorno será el mismo para todos.
4.Matplotlib
1. Mueva las coordenadas a (0,0)
hacha = plt.gca() ax.spines['derecha'].set_color('ninguno') ax.spines['arriba'].set_color('ninguno') ax.xaxis.set_ticks_position('abajo') ax.spines['bottom'].set_position(('datos', 0)) ax.yaxis.set_ticks_position('izquierda') ax.spines['izquierda'].set_position(('datos', 0))
matemáticas
1. Calcula el promedio
def media(números): #Calcular el promedio s = 0,0 para num en números: s = s número devolver s/len(números)
2. Calcula la varianza
def dev(números, media): #Calcular varianza desv = 0,0 para num en números: desv = desv (núm - media)**2 devolver pow(sdev / (len(números)-1), 0,5)
3. Calcula la mediana
def mediana(números): #Calcular la mediana ordenados (números) tamaño = longitud (números) si tamaño % 2 == 0: med = (números[tamaño//2-1] números[tamaño//2])/2 demás: med = números[tamaño//2] devolver medicina
6. El código se ejecuta localmente
De hecho, simplemente defina una función principal, construya un caso de uso de entrada, luego defina una variable de solución y llame a la función minCostClimbingStairs.
0. Habilidades de escritura de códigos
1.Formato
1. Quiere escribir varias líneas en una sola.
Agrega un punto y coma al final de cada línea;
2. Una línea es demasiado larga y quiero incluirla en una nueva línea.
Simplemente agregue una barra diagonal derecha al final de esta línea
Relacionado con el algoritmo
pensamiento clásico
0.Problemas encontrados con frecuencia
1. Prevenir el desbordamiento
1. Al calcular el producto de muchos números, para evitar el desbordamiento, puedes tomar el logaritmo del producto y cambiarlo a la forma de suma.
1.Estructura de datos
1.matriz
0.Básico
1. Siempre que vea que la matriz proporcionada en la pregunta de la entrevista es una matriz ordenada, puede pensar si puede utilizar el método de dicotomía.
2. Los elementos de la matriz son continuos en la dirección de memoria. Un elemento de la matriz no se puede eliminar individualmente, solo se puede sobrescribir.
1. El mismo elemento de la matriz no se puede atravesar dos veces.
para (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) {
2. Problema de matriz circular
Cuando existe la restricción de que la cabeza y la cola no pueden estar al mismo tiempo, descomponga la matriz circular en varios problemas de matriz ordinarios y encuentre el valor máximo
3. Problema del intervalo de dicotomía
1. Cerrado a la izquierda y cerrado a la derecha [izquierda, derecha]
int middle = left ((right - left) / 2);// evita el desbordamiento, equivalente a (left right)/2
while (izquierda <= derecha) { // Cuando izquierda == derecha, el intervalo [izquierda, derecha] sigue siendo válido
if (numeros[medio] > objetivo) { derecha = medio - 1; // el objetivo está en el rango izquierdo, entonces [izquierda, medio - 1]
} else if (nums[medio] <objetivo) { izquierda = medio 1; // el objetivo está en el rango derecho, entonces [medio 1, derecha]
2. Cerrar a la izquierda y abrir a la derecha [izquierda, derecha)
while (izquierda < derecha) { // Porque cuando izquierda == derecha, [izquierda, derecha) es un espacio no válido
if (numeros[medio] > objetivo) { derecha = medio; // el objetivo está en el intervalo izquierdo, en [izquierda, medio)
} else if (nums[medio] <objetivo) { izquierda = medio 1; // el objetivo está en el intervalo derecho, en [medio 1, derecha)
2. Tabla hash
0. Siempre que implique contar el número de apariciones de un determinado número/valor, utilice una tabla hash
1. La tabla hash contiene el número correspondiente a un determinado número y no es en sí misma.
para (int i = 0; i < nums.length; i ) { int complemento = objetivo - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i)
3. Lista enlazada
1. Suma los números en dos listas enlazadas.
1. En caso de inversión del orden: se deberá considerar por separado el posible problema de carry de la última incorporación.
2. En secuencia directa: invierta la lista vinculada/use la estructura de datos de la pila para lograr la inversión
2. Encuentre la mediana de una lista enlazada individualmente ordenada (cerrada a la izquierda y abierta a la derecha)
Supongamos que el extremo izquierdo de la lista vinculada actual es el izquierdo, el extremo derecho es el derecho y la relación de inclusión es "cerrada a la izquierda, abierta a la derecha". La lista vinculada dada es una lista vinculada unidireccional. elementos posteriores, pero no puede acceder directamente a los elementos predecesores. Por lo tanto, después de encontrar el nodo mediano en el medio de la lista vinculada, si establece la relación de "cerrado a la izquierda, abierto a la derecha", puede usar directamente (izquierda, medio) y (medio.siguiente, derecha) para representar la lista correspondiente a los subárboles izquierdo y derecho no necesitan mid.pre, y la lista inicial también se puede representar convenientemente mediante (head, null)
4.Personajes
1. Registre si aparece cada personaje.
Conjunto de hash: Set<Character> occ = new HashSet<Character>();
5. Números
1. Llevar adquisición para sumar dos números de un solo dígito
int suma = llevar x y; int llevar = suma/10;
2. Inversión de enteros
Para "sacar" y "empujar" números sin la ayuda de una pila/matriz auxiliar, podemos usar las matemáticas, sacar primero el último dígito, luego dividir por 10 para eliminar el último dígito, invertir el número y seguir multiplicándose después de 10. , agregue el último dígito extraído y primero determine si se desbordará.
6.árbol
1. Encuentra nodos precursores
Da un paso hacia la izquierda y luego sigue caminando hacia la derecha hasta que no puedas avanzar más.
predecesor = raíz.izquierda; while (predecesor.derecho! = nulo && predecesor.derecho! = raíz) { predecesor = predecesor.derecho; }
7. Colección
1. Eliminar elementos duplicados de la lista.
s = conjunto(ls); lt = lista(s)
8. tupla
1. Si no desea que el programa cambie los datos, conviértalos a un tipo de tupla
lt = tupla(ls)
2. Algoritmo clásico
1.Doble puntero
0. Comúnmente utilizado en matrices y listas vinculadas.
1. Cuando necesite enumerar dos elementos en una matriz, si descubre que a medida que el primer elemento aumenta, el segundo elemento disminuye, puede usar el método de doble puntero para mover el segundo puntero desde el final de la matriz. Comience a recorrer mientras asegurando que el segundo puntero sea mayor que el primer puntero, reduciendo la complejidad temporal de la enumeración de O(N^2) a O(N)
2. Cuando el resultado de la búsqueda tiene un rango determinado, los punteros dobles se utilizan para cambiar continuamente, similar al mecanismo de ventana deslizante.
2. Método de puntero rápido y lento
Inicialmente, tanto el puntero rápido como el puntero lento apuntan al punto final izquierdo de la lista vinculada. Mientras movemos el puntero rápido hacia la derecha dos veces, movemos el puntero lento hacia la derecha una vez hasta que el puntero rápido alcanza el límite (es decir, el puntero rápido alcanza el punto final derecho o el siguiente nodo del puntero rápido es el derecho punto final). En este momento, el elemento correspondiente al puntero lento es la mediana
3. Programación dinámica
1. El número requerido se puede obtener mediante alguna operación a través del número requerido anterior, y se puede encontrar la ecuación de transferencia dinámica.
2.Condiciones
Si un problema tiene muchos subproblemas superpuestos, la programación dinámica es más efectiva.
3. Cinco pasos
1. Determinar el significado de la matriz dp (tabla dp) y los subíndices 2. Determinar la fórmula de recursividad. 3.Cómo inicializar la matriz dp 4. Determinar el orden transversal. 5. Derivación de la matriz dp con ejemplos.
¿Por qué determinar primero la fórmula de recursividad y luego considerar la inicialización? Porque en algunos casos la fórmula recursiva determina cómo inicializar la matriz dp
4.Cómo depurar
1. Imprima la matriz dp y vea si se deduce según sus propias ideas.
2. Antes de escribir código, asegúrese de simular la situación específica de la transferencia de estado en la matriz dp y asegúrese de que el resultado final sea el resultado deseado.
5.Matriz de desplazamiento
Cuando la ecuación recursiva solo está relacionada con unos pocos números adyacentes, se puede usar una matriz móvil para optimizar la complejidad del espacio a O (1)
4. recursividad
0. Trilogía recursiva
Condición de terminación de la recursividad, qué hace esta recursividad y qué devuelve
3. Técnicas comúnmente utilizadas
1. Uso inteligente de subíndices de matriz
1.Aplicación
El subíndice de una matriz es una matriz implícitamente útil, especialmente cuando se cuentan algunos números (tratando el valor de la matriz correspondiente como el subíndice temp[arr[i]] de la nueva matriz), o cuando se determina si aparecen algunos números enteros.
2.Ejemplos
1. Cuando le damos una cadena de letras y le pedimos que determine la cantidad de veces que aparecen estas letras, podemos usar estas letras como subíndices al atravesar, si se atraviesa la letra a, entonces arr [a] se puede aumentar en. 1. Es decir, arr[a]. Mediante este uso inteligente de subíndices, no necesitamos juzgar letra por letra.
2. Se le proporcionan n matrices de enteros int desordenados, y el rango de valores de estos números enteros está entre 0 y 20. Debe ordenar estos n números de pequeño a grande en una complejidad de tiempo O (n). orden y use el valor correspondiente como subíndice de la matriz. Si este número ha aparecido antes, agregue 1 a la matriz correspondiente.
2. Usa el resto con habilidad
1.Aplicación
Al atravesar la matriz, se realizará un juicio fuera de límites. Si el subíndice está casi fuera de los límites, lo estableceremos en 0 y lo recorreremos nuevamente. Especialmente en algunas matrices en forma de anillo, como colas implementadas con matrices pos = (pos 1) % N
3. Utilice los punteros dobles con habilidad
1.Aplicación
Para punteros dobles, es particularmente útil cuando se hacen preguntas sobre listas enlazadas individualmente.
2.Ejemplos
1. Determinar si una lista enlazada individualmente tiene un ciclo
Configure un puntero lento y un puntero rápido para recorrer la lista vinculada. El puntero lento mueve un nodo a la vez, mientras que el puntero rápido mueve dos nodos a la vez. Si la lista vinculada no tiene un ciclo, el puntero rápido atravesará la lista primero. Si hay un ciclo, el puntero rápido lo hará. encuentre el puntero lento durante el segundo recorrido.
2. Cómo encontrar el nodo medio de la lista vinculada en un recorrido
Lo mismo es configurar un puntero rápido y un puntero lento. El lento mueve un nodo a la vez, mientras que el rápido mueve dos. Al atravesar la lista vinculada, cuando se completa el recorrido rápido del puntero, el puntero lento apenas llega al punto medio
3. El k-ésimo nodo del último en una lista enlazada individualmente
Establezca dos punteros, uno de los cuales mueve k nodos primero. Después de eso, ambos punteros se mueven a la misma velocidad. Cuando el primer puntero movido completa el recorrido, el segundo puntero está exactamente en el k-ésimo nodo desde abajo.
subtema
4. Utilice las operaciones por turnos con habilidad
1.Aplicación
1. A veces, cuando realizamos operaciones de división o multiplicación, como n/2, n/4, n/8, podemos usar el método de cambio para realizar la operación. A través de la operación de cambio, la velocidad de ejecución será más rápida.
2. También existen algunas operaciones como & (y) y | (o), que también pueden acelerar la operación.
2.Ejemplos
1. Para determinar si un número es impar, será mucho más rápido utilizar la operación AND.
5. Establecer la posición centinela
1.Aplicación
1. En temas relacionados con listas vinculadas, a menudo configuramos un puntero principal, y este puntero principal no almacena ningún dato válido. Solo por conveniencia de operación, podemos llamar a este puntero principal bit centinela.
2. Al operar una matriz, también puede configurar un centinela, utilizando arr[0] como centinela.
2.Ejemplos
1. Cuando queremos eliminar el primer nodo, si no se establece un bit centinela, la operación será diferente a la operación de eliminar el segundo nodo. Pero hemos configurado un centinela, por lo que eliminar el primer nodo y eliminar el segundo nodo tienen la misma operación, sin realizar juicios adicionales. Por supuesto, lo mismo ocurre al insertar nodos.
2. Cuando desee juzgar si dos elementos adyacentes son iguales, establecer un centinela no le preocupará el problema transfronterizo. Puede directamente arr[i] == arr[i-1]. No tengas miedo de cruzar el límite cuando i = 0
6. Algunas optimizaciones relacionadas con la recursividad.
1. Considere la preservación del estado para problemas que pueden ser recursivos.
1. Cuando usamos la recursividad para resolver un problema, es fácil calcular repetidamente el mismo subproblema. En este momento, debemos considerar la preservación del estado para evitar cálculos repetidos.
2.Ejemplos
0. Una rana puede saltar 1 o 2 escalones a la vez. Descubre de cuántas maneras puede la rana saltar una escalera de n niveles.
1. Este problema se puede resolver fácilmente mediante recursividad. Supongamos que f(n) representa el número total de pasos para n pasos, entonces f(n) = f(n-1) f(n - 2)
2. La condición final de la recursividad es cuando 0 <= n <= 2, f (n) = n, es fácil escribir código recursivo
3. Sin embargo, para los problemas que se pueden resolver mediante recursividad, debemos considerar si hay muchos cálculos repetidos. Obviamente, para la recursividad de f(n) = f(n-1) f(n-2), hay muchos cálculos repetidos.
4. Esta vez tenemos que considerar la preservación del Estado. Por ejemplo, puede usar hashMap para guardar. Por supuesto, también puede usar una matriz. En este momento, puede usar subíndices de matriz como dijimos anteriormente. Cuando arr [n] = 0, significa que n no se ha calculado. Cuando arr [n]! = 0, significa que f (n) se ha calculado. En este momento, el valor calculado se puede devolver directamente.
5. De esta forma, se puede mejorar enormemente la eficiencia del algoritmo. Algunas personas también llaman a este tipo de conservación del estado método memo.
2. Piensa de abajo hacia arriba
1. Para problemas recursivos, generalmente recurrimos de arriba a abajo hasta que la recursividad llega al final y luego devolvemos el valor capa por capa.
2. Sin embargo, a veces, cuando n es relativamente grande, como cuando n = 10000, es necesario recurrir 10000 niveles hasta n <= 2 antes de devolver lentamente el resultado. Si n es demasiado grande, es posible que no haya espacio en la pila. suficiente
3. Para esta situación, podemos considerar un enfoque ascendente.
4. Este enfoque ascendente también se denomina recursividad.
3. Ventajas frente a otros idiomas
1.matriz
1. Los parámetros son matrices parciales.
En Python, los parámetros pueden devolver directamente parte de la matriz nums[:i]. No es necesario rediseñar un método para interceptar la matriz según el subíndice como en Java.
2. Obtenga elementos y subíndices al mismo tiempo.
para i, v en enumerar (numeros):
Estructuras/algoritmos de datos de uso común
1. Lista enlazada
2.Pila
1. Pila monótona
1.Definición
Una pila en la que los elementos de la pila aumentan o disminuyen monótonamente. Una pila monótona solo se puede operar en la parte superior de la pila.
2. Naturaleza
1. Los elementos de la pila monótona son monótonos.
2. Antes de agregar elementos a la pila, todos los elementos que destruyan la monotonicidad de la pila se eliminarán de la parte superior de la pila.
3. Utilice la pila monótona para encontrar el elemento y recorrer hacia la izquierda hasta el primer elemento que es más pequeño que él (pila incremental)/buscar el elemento y recorrer hacia la izquierda hasta el primer elemento que es más grande que él.
3. Cola
4.árbol
1.BST: árbol de búsqueda binaria árbol de clasificación binaria
1.Definición
1. Las claves de todos los nodos en el subárbol izquierdo son menores que las del nodo raíz.
2. Las palabras clave de todos los nodos en el subárbol derecho son mayores que las del nodo raíz.
3. Los subárboles izquierdo y derecho son cada uno de ellos un árbol de clasificación binario.
El recorrido en orden puede obtener una secuencia ordenada creciente
2.AVL: árbol binario equilibrado
1.Definición
1. Árbol binario equilibrado: el valor absoluto de la diferencia de altura entre los subárboles izquierdo y derecho de cualquier nodo no supera 1
2. Factor de equilibrio: la diferencia de altura entre los subárboles izquierdo y derecho del nodo -1,0,1
3.mct: árbol de expansión mínimo
1.Definición
Cualquier árbol que consta sólo de las aristas de G y contiene todos los vértices de G se llama árbol de expansión de G.
5. Figura
1. Terminología
1. Clúster (subgrafo completo)
Conjunto de puntos: Hay una arista que conecta dos puntos cualesquiera.
1.1 Conjunto independiente de puntos
Conjunto de puntos: no hay borde entre dos puntos.
2. Gráfico de Hamilton Hamilton
Un gráfico no dirigido va desde un punto inicial específico hasta un punto final específico, pasando por todos los demás nodos solo una vez. Un camino hamiltoniano cerrado se llama ciclo hamiltoniano y un camino que contiene todos los vértices del gráfico se llama camino hamiltoniano.
6. Algoritmo
1.BFS: búsqueda en amplitud
1.Definición
Un algoritmo transversal jerárquico similar a un árbol binario, que da prioridad al nodo descubierto más temprano.
2.DFS: búsqueda en profundidad primero
1.Definición
De manera similar al recorrido de un árbol en orden anticipado, se le da prioridad al último nodo descubierto.
sentido común
1. ¿Cuántas operaciones por segundo?
2. Complejidad temporal del algoritmo recursivo
Número de recursiones * Número de operaciones en cada recursión
3. Complejidad espacial del algoritmo recursivo.
Profundidad de recursividad * complejidad espacial de cada recursividad
Enfoque en la estructura de datos
Árbol
1.BST: árbol de búsqueda binaria
1. La búsqueda de nodos a partir del nodo raíz es en realidad un proceso de búsqueda binaria, por lo que BST también se denomina árbol de búsqueda binaria.
2. El recorrido en orden de BST puede obtener una secuencia ordenada
3. La posición de inserción debe ser la posición de la hoja, lo que no provocará que se ajuste la estructura del árbol.
4. CurrentNode=null no se puede utilizar para eliminar nodos en Java.
currentNode se pasa a través de parámetros del método, por lo que currentNode = null no provocará la destrucción del objeto, porque el objeto todavía está retenido por una referencia externa al método y debe destruirse a través del nodo principal.
5. Pregunte sobre la intención de BST en la entrevista.
En términos generales, el BST simple no se utiliza en la práctica de la ingeniería porque tiene un defecto fatal: la estructura del árbol se desequilibra fácilmente. Considere un caso extremo, si la secuencia de valores clave para construir el BST está ordenada, inserte los nodos uno por uno y luego. El BST resultante es en realidad una lista vinculada, y la complejidad del tiempo de operación de consulta de la lista vinculada no puede alcanzar o (logN), lo que viola la intención de diseño original de BST. Incluso si esta situación extrema no ocurre, BST La estructura también se convertirá gradualmente. El desequilibrio en el proceso de inserción y eliminación continua de nodos hará que la estructura del árbol se incline cada vez más. Este desequilibrio de la estructura será cada vez más desfavorable para las operaciones de consulta.
Solución: utilice BST con propiedades de autoequilibrio, como AVL y árbol rojo-negro
subtema
subtema
subtema
2. Árbol rojo-negro RBT
1.Definición
①Cada nodo tiene un color, negro o rojo. ②El nodo raíz es negro ③Cada nodo hoja (nodo vacío NIL) es negro ④Si un nodo es rojo, sus nodos secundarios deben ser negros ⑤Todas las rutas desde cualquier nodo a cada nodo hoja del nodo contienen la misma cantidad de nodos negros.
2.Ilustración
3. Propiedades del equilibrio
Propiedad ⑤, garantiza que entre todas las rutas que comienzan desde cualquier nodo hasta su nodo hoja, la longitud de la ruta más larga no excederá el doble de la longitud de la ruta más corta. Por lo tanto, el árbol rojo-negro es un árbol binario relativamente cercano al equilibrado.
Además, la propiedad ⑤ señala claramente que el número de niveles de nodos negros en los subárboles izquierdo y derecho de cada nodo es igual, por lo que los nodos negros del árbol rojo-negro están perfectamente equilibrados.
6. La diferencia entre inserción y eliminación
Para el nodo insertado, la posición insertada debe ser un nodo hoja, por lo que al ajustar, no habrá problemas en este nivel, así que ajuste desde la relación entre el nodo padre y el tío. Para eliminar nodos, la posición de ajuste no es necesariamente el nodo hoja, por lo que al ajustar, puede haber problemas en esta capa misma, por lo que la relación entre el nodo y sus hermanos se ajustará de ahora en adelante.
puntos de conocimiento labuladuo
0.Serie de lectura obligada
1. Pensamiento marco para aprender algoritmos y resolver preguntas.
1. Método de almacenamiento de la estructura de datos.
1. Solo hay dos tipos: matriz (almacenamiento secuencial) y lista vinculada (almacenamiento vinculado)
2. También hay varias estructuras de datos, como tablas hash, pilas, colas, montones, árboles, gráficos, etc., que pertenecen a la "superestructura", mientras que las matrices y las listas vinculadas son la "base estructural". esas diversas estructuras de datos Sus fuentes son operaciones especiales en listas o matrices vinculadas, y las API son simplemente diferentes.
3. Introducción a diversas estructuras.
1. Las dos estructuras de datos, "cola" y "pila", se pueden implementar mediante listas enlazadas o matrices. Si lo implementa con una matriz, debe lidiar con el problema de expansión y contracción; si lo implementa con una lista vinculada, no tiene este problema, pero necesita más espacio de memoria para almacenar punteros de nodo.
2. Dos métodos de representación de "gráfico", la lista de adyacencia es una lista vinculada y la matriz de adyacencia es una matriz bidimensional. La matriz de adyacencia determina la conectividad rápidamente y puede realizar operaciones matriciales para resolver algunos problemas, pero consume espacio si el gráfico es escaso. Las listas de adyacencia ahorran espacio, pero muchas operaciones definitivamente no son tan eficientes como las matrices de adyacencia.
3. La "tabla hash" asigna claves a una matriz grande mediante una función hash. Y como método para resolver conflictos de hash, el método de cremallera requiere características de lista vinculada, que es fácil de operar, pero requiere espacio adicional para almacenar punteros; el método de sondeo lineal requiere características de matriz para facilitar el direccionamiento continuo y no requiere espacio de almacenamiento para punteros; , pero la operación es un poco complicada algunos
4. "Árbol", implementado con una matriz es un "montón", porque el "montón" es un árbol binario completo para almacenar, no requiere punteros de nodo y la operación es relativamente simple para usar una lista vinculada; implementarlo es un "árbol" muy común, porque No es necesariamente un árbol binario completo, por lo que no es adecuado para el almacenamiento en matriz. Por esta razón, se han derivado varios diseños ingeniosos basados en esta estructura de "árbol" de lista enlazada, como árboles de búsqueda binaria, árboles AVL, árboles rojo-negro, árboles de intervalo, árboles B, etc., para abordar diferentes problemas.
4. Ventajas y desventajas
1. Dado que las matrices son compactas y de almacenamiento continuo, se puede acceder a ellas de forma aleatoria, los elementos correspondientes se pueden encontrar rápidamente a través de índices y se ahorra relativamente espacio de almacenamiento. Pero debido al almacenamiento continuo, el espacio de memoria debe asignarse al mismo tiempo. Por lo tanto, si la matriz desea expandirse, debe reasignar un espacio mayor y luego copiar todos los datos allí. La complejidad del tiempo es O (N); si desea Al insertar y eliminar en el medio de la matriz, todos los datos posteriores deben moverse cada vez para mantener la continuidad. La complejidad del tiempo es O (N).
2. Debido a que los elementos de una lista vinculada no son continuos, sino que dependen de punteros para señalar la ubicación del siguiente elemento, no hay problema de expansión de la matriz, si conoce el predecesor y el sucesor de un determinado elemento, puede eliminarlos; el elemento o inserte un nuevo elemento operando el puntero Complejidad de tiempo O (1). Sin embargo, debido a que el espacio de almacenamiento no es continuo, no se puede calcular la dirección del elemento correspondiente en función de un índice, por lo que el acceso aleatorio no es posible y debido a que cada elemento debe almacenar un puntero a la posición de los elementos anteriores, sí; consumirá relativamente más espacio de almacenamiento.
2. Operaciones básicas de estructuras de datos.
1. La operación básica no es más que acceso transversal. Para ser más específicos, es: agregar, eliminar, verificar y modificar.
2. Desde el nivel más alto, solo existen dos formas de recorrido y acceso a diversas estructuras de datos: lineal y no lineal.
3. Lineal se representa mediante iteración for/ while y no lineal se representa mediante recursividad.
4. Varios recorridos típicos.
1.matriz
recorrido vacío (int [] arr) { para (int i = 0; i <arr.length; i) { // Iterar sobre arr[i] } }
2. Lista enlazada
/* Nodo básico de lista enlazada individualmente */ clase Nodo de lista { valor int; ListNode siguiente; } recorrido vacío (cabeza de ListNode) { for (ListNode p = cabeza; p != nulo; p = p.siguiente) { //Accede iterativamente a p.val } } recorrido vacío (cabeza de ListNode) { // Accede recursivamente a head.val atravesar (cabeza.siguiente) }
3. árbol binario
/* Nodo de árbol binario básico */ claseNodoÁrbol { valor int; TreeNode izquierda, derecha; } recorrido vacío (raíz de TreeNode) { atravesar (raíz.izquierda) atravesar (raíz.derecha) }
4.árbol N-ario
/* Nodo de árbol N-ario básico */ claseNodoÁrbol { valor int; TreeNode[] hijos; } recorrido vacío (raíz de TreeNode) { para (niño TreeNode: root.children) atravesar(niño); }
5. El llamado marco es una rutina. Independientemente de agregar, eliminar, verificar o modificar, estos códigos son una estructura que nunca se puede separar. Puede usar esta estructura como un esquema y simplemente agregar códigos al marco de acuerdo con problemas específicos.
3. Guía de redacción de preguntas sobre algoritmos
1. ¡Cepille el árbol binario primero, cepille el árbol binario primero, cepille el árbol binario primero!
2. Los árboles binarios son el pensamiento marco más fácil de cultivar, y la mayoría de las técnicas de algoritmos son esencialmente problemas de recorrido de árboles.
3. No subestimes estas pocas líneas de código roto. Casi todos los problemas del árbol binario se pueden resolver utilizando este marco.
recorrido vacío (raíz de TreeNode) { // recorrido de reserva atravesar (raíz.izquierda) // recorrido en orden atravesar (raíz.derecha) // recorrido posterior al pedido }
4. Si no sabe cómo comenzar o tiene miedo de las preguntas, también puede comenzar con el árbol binario. Las primeras 10 preguntas pueden resultar un poco incómodas. Haga otras 20 preguntas según el marco. Tendrá cierta comprensión por su cuenta; termine todo el tema antes de hacerlo. Si mira hacia atrás en el tema de las reglas de división y conquista, encontrará que cualquier problema que involucre recursividad es un problema de árbol.
5. ¿Qué debo hacer si no puedo entender tantos códigos? Al extraer directamente el marco, puede ver la idea central: de hecho, muchos problemas de programación dinámica implican atravesar un árbol. Si está familiarizado con las operaciones de recorrido de árboles, al menos sabrá cómo convertir ideas en código y también lo sabrá. saber extraer otros La idea central de la solución.
2. Marco de rutina de resolución de problemas de programación dinámica
1. Conceptos básicos
1.Formulario
La forma general de un problema de programación dinámica es encontrar el valor óptimo. La programación dinámica es en realidad un método de optimización en la investigación de operaciones, pero se usa más comúnmente en problemas informáticos. Por ejemplo, le pide que encuentre la subsecuencia creciente más larga y la distancia mínima de edición.
2. Cuestiones fundamentales
El problema central es el agotamiento. Como requerimos el mejor valor, debemos enumerar exhaustivamente todas las respuestas posibles y luego encontrar el mejor valor entre ellas.
3.Tres elementos
1. Subproblemas superpuestos
0. Hay "subproblemas superpuestos" en este tipo de problemas. Si el problema es de fuerza bruta, la eficiencia será extremadamente ineficiente. Por lo tanto, se necesita una "nota" o "tabla DP" para optimizar el proceso exhaustivo y evitarlo. cálculos innecesarios.
2. Subestructura óptima
0. El problema de programación dinámica debe tener una "subestructura óptima", de modo que el valor óptimo del problema original pueda obtenerse a través del valor óptimo del subproblema.
3. Ecuación de transición de estado
0. Los problemas pueden cambiar constantemente y no es fácil enumerar exhaustivamente todas las soluciones factibles. Sólo enumerando la "ecuación de transición de estado" correcta podemos enumerarlas correctamente de forma exhaustiva.
1. Marco de pensamiento
Borrar caso base -> borrar "estado" -> borrar "selección" -> definir el significado de matriz/función dp
#Inicializar caso base dp[0][0][...] = base # Realizar transferencia de estado para el estado 1 en todos los valores del estado 1: para el estado 2 en todos los valores del estado 2: para... dp[estado 1][estado 2][...] = encontrar el valor máximo (seleccione 1, seleccione 2...)
2. Secuencia de Fibonacci
1. Recursión violenta
1.Código
int fib(int N) { si (N == 1 || N == 2) devuelve 1; retorno fib(N - 1) fib(N - 2); }
2. árbol recursivo
1. Siempre que encuentre un problema que requiera recursividad, es mejor dibujar un árbol de recursividad. Esto le será de gran ayuda para analizar la complejidad del algoritmo y encontrar las razones de la ineficiencia del algoritmo.
2.Fotos
3. Complejidad temporal del algoritmo recursivo.
0. Multiplicar el número de subproblemas por el tiempo necesario para resolver un subproblema.
1. Primero calcule el número de subproblemas, es decir, el número total de nodos en el árbol de recursividad. Obviamente, el número total de nodos del árbol binario es exponencial, por lo que el número de subproblemas es O (2 ^ n)
2. Luego calcule el tiempo para resolver un subproblema. En este algoritmo, no hay bucle, solo f (n - 1) f (n - 2) una operación de suma, el tiempo es O (1).
3. La complejidad temporal de este algoritmo es la multiplicación de dos, es decir, O (2 ^ n), nivel exponencial, explosión.
4. Al observar el árbol de recursividad, es obvio que se encuentra la razón de la ineficiencia del algoritmo: hay muchos cálculos repetidos.
5. Ésta es la primera propiedad de los problemas de programación dinámica: subproblemas superpuestos. A continuación intentaremos solucionar este problema.
2. Solución recursiva con memo.
1. Dado que el motivo que requiere mucho tiempo son los cálculos repetidos, podemos crear una "nota" antes de regresar después de calcular la respuesta a una determinada subpregunta. encuentre una subpregunta. Primero verifique el problema en "Nota". Si descubre que el problema se resolvió antes, simplemente saque la respuesta y úsela en lugar de perder tiempo calculando.
2. Generalmente, se utiliza una matriz como esta "nota". Por supuesto, también puede utilizar una tabla hash (diccionario).
3.Código
int fib(int N) { si (N < 1) devuelve 0; //La nota se inicializa a 0 vector<int> memo(N 1, 0); // Realiza recursividad con memo ayudante de devolución (nota, N); } int ayudante(vector<int>& memo, int n) { // caso base si (n == 1 || n == 2) devuelve 1; //Ya calculado if (memo[n]!= 0) devolver memo[n]; memorándum[n] = ayudante(memorándum, n - 1) ayudante(memorándum, n - 2); devolver nota[n]; }
4. árbol recursivo
De hecho, el algoritmo recursivo con "memo" transforma un árbol recursivo con gran redundancia en un gráfico recursivo sin redundancia mediante "poda", lo que reduce en gran medida el número de subproblemas (es decir, la recursión del número de nodos en el gráfico).
5. Complejidad
No hay cálculos redundantes en este algoritmo, el número de subproblemas es O (n) y la complejidad temporal de este algoritmo es O (n). Comparado con los algoritmos violentos, es un ataque de reducción de dimensiones.
6.Comparación con la programación dinámica.
La eficiencia de la solución recursiva con memo es la misma que la de la solución de programación dinámica iterativa. Sin embargo, este método se llama "de arriba hacia abajo" y la programación dinámica se llama "de abajo hacia arriba".
7. De arriba hacia abajo
El árbol de recursividad (o imagen) dibujado se extiende de arriba a abajo, comenzando desde un problema original más grande como f(20), y descompone gradualmente la escala hacia abajo hasta f(1) y f(2). Estos dos casos base luego devuelven el respuestas capa por capa.
8. De abajo hacia arriba
Simplemente comience desde abajo, el tamaño de problema más simple y más pequeño f (1) y f (2) y empuje hacia arriba hasta llegar a la respuesta que queremos f (20). y esta es la razón por la cual la planificación de programación dinámica generalmente rompe con la recursividad y, en cambio, completa los cálculos mediante iteración de bucle.
3.Solución iterativa de matriz dp.
1. Pensamientos
Con la inspiración del "memorándum" del paso anterior, podemos separar este "memorándum" en una tabla, llamémosla tabla DP. ¿No sería bueno completar cálculos "de abajo hacia arriba" en esta tabla?
2.Código
int fib(int N) { vector<int> dp(N 1, 0); // caso base dp[1] = dp[2] = 1; para (int i = 3; i <= N; i ) dp[yo] = dp[yo - 1] dp[yo - 2]; devolver dp[norte]; }
3. Ecuación de transición de estado
1. Es el núcleo de la resolución de problemas. Y es fácil encontrar que, de hecho, la ecuación de transición de estado representa directamente la solución de fuerza bruta
2. No menosprecies las soluciones violentas. Lo más difícil de los problemas de programación dinámica es escribir esta solución violenta, es decir, la ecuación de transición de estado. Siempre que escriba una solución de fuerza bruta, el método de optimización no es más que usar una nota o una tabla DP, no hay ningún misterio.
4. Compresión de estado
1. El estado actual solo está relacionado con los dos estados anteriores. De hecho, no necesita una tabla DP tan larga para almacenar todos los estados. Solo necesita encontrar una manera de almacenar los dos estados anteriores. Por lo tanto, se puede optimizar aún más para reducir la complejidad del espacio a O (1)
2.Código
int fib(int n) { si (n == 2 || n == 1) devolver 1; int anterior = 1, actual = 1; para (int i = 3; i <= n; i ) { int suma = curso anterior; anterior = actual; curr = suma; } devolver corriente; }
3. Esta técnica es la llamada "compresión de estado". Si encontramos que cada transferencia de estado solo requiere una parte de la tabla DP, entonces podemos intentar usar la compresión de estado para reducir el tamaño de la tabla DP y registrar solo la tabla DP. datos necesarios En términos generales, es comprimir una tabla DP bidimensional en una dimensión, es decir, comprimir la complejidad del espacio de O (n ^ 2) a O (n).
3. El problema de cobrar el cambio
0.Pregunta
Te dan k monedas con valores nominales de c1, c2...ck. La cantidad de cada moneda es ilimitada. Luego te dan una cantidad total de dinero. Te pregunto cuántas monedas necesitas al menos para compensar. esta cantidad Si es imposible compensar la cantidad, el algoritmo devuelve -1.
1. Recursión violenta
1. En primer lugar, este problema es un problema de programación dinámica porque tiene una "subestructura óptima". Para cumplir con la "subestructura óptima", los subproblemas deben ser independientes entre sí.
2. Volviendo al problema de cobrar el cambio, ¿por qué se dice que está en consonancia con la subestructura óptima? Por ejemplo, si desea encontrar la cantidad mínima de monedas cuando la cantidad = 11 (pregunta original), si conoce la cantidad mínima de monedas cuando la cantidad = 10 (subpregunta), solo necesita agregar una a la respuesta a la subpregunta (elija otra moneda de 1 valor nominal) es la respuesta a la pregunta original. Debido a que la cantidad de monedas es ilimitada, no hay control mutuo entre los subproblemas y son independientes entre sí.
3. Cuatro pasos principales
1. Determinar el caso base. Esto es muy simple. Obviamente, cuando la cantidad objetivo es 0, el algoritmo devuelve 0.
2. Determine el "estado", es decir, las variables que cambiarán en el problema y subproblemas originales. Dado que el número de monedas es infinito y la denominación de la moneda también viene dada por la pregunta, sólo la cantidad objetivo seguirá acercándose al caso base, por lo que el único "estado" es la cantidad objetivo.
3. Determinar la "elección", que es el comportamiento que hace que el "estado" cambie. ¿Por qué cambia la cantidad objetivo? Porque estás eligiendo monedas. Cada vez que eliges una moneda, equivale a reducir la cantidad objetivo. Entonces el valor nominal de todas las monedas es tu "elección"
4. Aclare la definición de función/matriz dp. De lo que estamos hablando aquí es de una solución de arriba hacia abajo, por lo que habrá una función dp recursiva. En términos generales, el parámetro de la función es la cantidad que cambiará durante la transición de estado, que es el "estado" mencionado anteriormente; El valor de retorno de la función es la cantidad que la pregunta requiere que calculemos. En lo que respecta a esta pregunta, solo hay un estado, que es "cantidad objetivo". La pregunta requiere que calculemos la cantidad mínima de monedas necesarias para alcanzar la cantidad objetivo. Entonces podemos definir la función dp así: La definición de dp (n): ingrese una cantidad objetivo n y devuelva la cantidad mínima de monedas para completar la cantidad objetivo n
4. Pseudocódigo
def coinChange(monedas: Lista[int], cantidad: int): # Definición: Para compensar la cantidad n, se necesitan al menos dp(n) monedas definición dp(n): # Haga una elección y elija el resultado que requiera la menor cantidad de monedas. para moneda en monedas: res = min(res, 1 dp(n - moneda)) devolver resolución #El resultado final requerido por la pregunta es dp(cantidad) devolver dp(monto)
5.Código
def coinChange(monedas: Lista[int], cantidad: int): definición dp(n): # caso base si n == 0: devuelve 0 si n < 0: devuelve -1 # Encuentra el valor mínimo, así que inicialízalo a infinito positivo. res = flotante('INF') para moneda en monedas: subproblema = dp(n - moneda) # El subproblema no tiene solución, sáltalo si subproblema == -1: continuar res = min(res, 1 subproblema) devolver res si res! = float('INF') más -1 devolver dp(monto)
6. Ecuación de transición de estado
7. árbol recursivo
8. Complejidad
El número total de subproblemas es el número de nodos del árbol recursivo. Esto es difícil de ver. En resumen, es exponencial. Cada subproblema contiene un bucle for con una complejidad de O(k). Entonces la complejidad del tiempo total es O(k * n^k), nivel exponencial
2. Recursividad con memo
1.Código
def coinChange(monedas: Lista[int], cantidad: int): # nota nota = dictar() definición dp(n): # Verifique la nota para evitar el doble conteo si n en la nota: devolver la nota [n] # caso base si n == 0: devuelve 0 si n < 0: devuelve -1 res = flotante('INF') para moneda en monedas: subproblema = dp(n - moneda) si subproblema == -1: continuar res = min(res, 1 subproblema) # Grabar en nota memo[n] = res si res != float('INF') else -1 devolver nota[n] devolver dp(monto)
2. Complejidad
Obviamente, el "memorándum" reduce en gran medida el número de subproblemas y elimina por completo la redundancia de subproblemas, por lo que el número total de subproblemas no excederá la cantidad de dinero n, es decir, el número de subproblemas. Está encendido). El tiempo para abordar un subproblema permanece sin cambios y sigue siendo O (k), por lo que la complejidad del tiempo total es O (kn)
3.Solución iterativa de matriz dp.
1.Definición de matriz dp
La función dp se refleja en los parámetros de la función, mientras que la matriz dp se refleja en el índice de la matriz: La definición de matriz dp: cuando la cantidad objetivo es i, se necesitan al menos monedas dp [i] para recolectarla
2.Código
int coinChange(vector<int>& monedas, cantidad int) { //El tamaño de la matriz es la cantidad 1 y el valor inicial también es la cantidad 1 vector<int> dp(cantidad 1, cantidad 1); // caso base dp[0] = 0; // El bucle for externo atraviesa todos los valores de todos los estados para (int i = 0; i < dp.size(); i ) { //El bucle for interno busca el valor mínimo de todas las opciones para (int moneda: monedas) { // El subproblema no tiene solución, omítelo si (i - moneda < 0) continúa; dp[i] = min(dp[i], 1 dp[i - moneda]); } } return (dp[monto] == monto 1) -1: dp[monto]; }
3. Proceso
4.Detalles
¿Por qué la matriz dp se inicializa en la cantidad 1? Porque la cantidad de monedas que componen la cantidad solo puede ser igual a la cantidad como máximo (se utilizan todas las monedas con un valor nominal de 1 yuan), por lo que inicializarla en la cantidad 1 es equivalente a inicializarlo a infinito positivo, lo que facilita el valor mínimo posterior
4. Resumen
1. En realidad, no existen trucos de magia para que las computadoras resuelvan problemas. Su única solución es agotar exhaustivamente todas las posibilidades. El diseño de algoritmos no es más que pensar primero en "cómo hacerlo de manera exhaustiva" y luego continuar con "cómo hacerlo de manera exhaustiva y exhaustiva".
2. Enumerar la ecuación de transferencia dinámica es resolver el problema de "cómo hacerlo exhaustivamente". La razón por la que es difícil es que, en primer lugar, muchos cálculos exhaustivos deben implementarse de forma recursiva y, en segundo lugar, porque el espacio de solución de algunos problemas es complejo y no es fácil completar el cálculo exhaustivo.
3. Los memorandos y los cuadros del PD tratan de buscar “cómo hacerlo de forma exhaustiva y exhaustiva”. La idea de intercambiar espacio por tiempo es la única forma de reducir la complejidad del tiempo. Además, déjame preguntarte, ¿qué otros trucos puedes hacer?