Galleria mappe mentale Compilazione di codici di idee chiave per le domande sull'algoritmo di deduzione più potenti
Ho compilato le idee e i codici principali delle domande dell'algoritmo sopra. Questa mappa mentale continuerà ad essere aggiornata. Gli amici che l'hanno acquistata possono aggiungermi come amici tramite il blog nella mia introduzione personale. Continuerò a fornire aggiornamenti e tu puoi discuti anche con me il problema algoritmico.
Modificato alle 2021-06-29 20:35:23Questa è una mappa mentale su una breve storia del tempo. "Una breve storia del tempo" è un'opera scientifica popolare con un'influenza di vasta portata. Non solo introduce i concetti di base della cosmologia e della relatività, ma discute anche dei buchi neri e dell'espansione dell'universo. questioni scientifiche all’avanguardia come l’inflazione e la teoria delle stringhe.
Dopo aver letto "Il coraggio di essere antipatico", "Il coraggio di essere antipatico" è un libro filosofico che vale la pena leggere. Può aiutare le persone a comprendere meglio se stesse, a comprendere gli altri e a trovare modi per ottenere la vera felicità.
"Il coraggio di essere antipatico" non solo analizza le cause profonde di vari problemi nella vita, ma fornisce anche contromisure corrispondenti per aiutare i lettori a comprendere meglio se stessi e le relazioni interpersonali e come applicare la teoria psicologica di Adler nella vita quotidiana.
Questa è una mappa mentale su una breve storia del tempo. "Una breve storia del tempo" è un'opera scientifica popolare con un'influenza di vasta portata. Non solo introduce i concetti di base della cosmologia e della relatività, ma discute anche dei buchi neri e dell'espansione dell'universo. questioni scientifiche all’avanguardia come l’inflazione e la teoria delle stringhe.
Dopo aver letto "Il coraggio di essere antipatico", "Il coraggio di essere antipatico" è un libro filosofico che vale la pena leggere. Può aiutare le persone a comprendere meglio se stesse, a comprendere gli altri e a trovare modi per ottenere la vera felicità.
"Il coraggio di essere antipatico" non solo analizza le cause profonde di vari problemi nella vita, ma fornisce anche contromisure corrispondenti per aiutare i lettori a comprendere meglio se stessi e le relazioni interpersonali e come applicare la teoria psicologica di Adler nella vita quotidiana.
Sommario
Algoritmo Likou
0. Pensiero inverso
6.Trasformazione a zigzag
1. Ordina per riga
0.Titolo
Disporre una determinata stringa a forma di Z dall'alto verso il basso e da sinistra a destra in base al numero di righe specificato. Successivamente, l'output deve essere letto riga per riga da sinistra a destra per generare una nuova stringa.
1. Idee
Scorri su ss da sinistra a destra, aggiungendo ogni carattere alla riga appropriata. La riga appropriata può essere tracciata utilizzando la riga corrente e le variabili di direzione corrente. La direzione corrente cambierà solo quando ci spostiamo verso l'alto o verso il basso verso la riga inferiore.
2.Codice
soluzione di classe { public String convert(String s, int numRighe) { if (numRighe == 1) restituisce s; List<StringBuilder> rows = new ArrayList<>(); //Memorizza i caratteri di ogni riga dopo la trasformazione for (int i = 0; i < Math.min(numRighe, s.lunghezza()); i ) righe.add(nuovo StringBuilder()); int curRow = 0; booleano goingDown = false; for (char c : s.toCharArray()) { //Attraversa s direttamente, convertilo in un array di caratteri e assegnalo a c uno per uno righe.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown; curRow = andandoGiù 1: -1; } StringBuilder ret = new StringBuilder(); for (StringBuilder riga: righe) ret.append(riga); return ret.toString(); } }
3. Complessità
Tempo O(n), dove n=len(s), spazio O(n)
1. Algoritmo puro
7. Inversione di numeri interi
1. Domanda
Dato un intero con segno a 32 bit, è necessario invertire ogni bit dell'intero. Supponiamo che il nostro ambiente possa memorizzare solo interi con segno a 32 bit. Se l'intero va in overflow dopo l'inversione, verrà restituito 0.
2. Idee
Per "estrarre" e "spingere" i numeri senza l'aiuto di uno stack/array ausiliario, possiamo usare la matematica, eliminare prima l'ultima cifra, quindi dividere per 10 per eliminare l'ultima cifra, invertire il numero e continuare a moltiplicarsi Dopo 10, aggiungi l'ultima cifra eliminata e determina prima se traboccherà.
3.Codice
soluzione di classe { public int reverse(int x) { int giro = 0; mentre (x!= 0) { int pop = x % 10; //Elimina l'ultima cifra di x x /= 10; //Rimuove l'ultima cifra di x //Quando int occupa 32 bit, l'intervallo di valori è -2147483648~2147483647, quindi pop>7 o pop<-8 if (rev > Intero.MAX_VALUE/10 || (rev == Intero.MAX_VALUE / 10 && pop > Intero.MAX_VALUE % 10)) return 0; if (rev < Intero.MIN_VALUE/10 || (rev == Intero.MIN_VALUE / 10 && pop < Intero.MIN_VALUE % 10)) return 0; rev = rev * 10 pop; //Dopo che il tutto avanza di una cifra, aggiungi l'ultima cifra } ritorno giro; } }
4. Complessità
Tempo: O(log(x)), spazio O(1)
2.Serie
3. Elenco collegato
4. Corda
5.Ricerca binaria
6.Albero
1. Albero binario
7.Prima di tutto l'ampiezza
8. Prima la profondità
9.Doppio puntatore
10. Ordinamento
11.Metodo di backtracking
12. Tabella hash
13.Impila
14. Programmazione dinamica
sottoargomento
Domande classiche da intervista
15,
Nozioni di base sugli algoritmi
1. Complessità temporale
1.Definizione
La complessità temporale è una funzione che descrive qualitativamente il tempo di esecuzione dell'algoritmo
2. Cos'è Big O?
Big O viene utilizzato per rappresentare il limite superiore. Quando viene utilizzato come limite superiore del tempo di esecuzione nel caso peggiore dell'algoritmo, è il limite superiore del tempo di esecuzione per qualsiasi input di dati a cui fa riferimento la complessità temporale dell'algoritmo in generale
3. Differenze nelle diverse dimensioni dei dati
Poiché Big O è la complessità temporale mostrata quando la grandezza dei dati attraversa un punto e la grandezza dei dati è molto grande. Questa quantità di dati è la quantità di dati in cui il coefficiente costante non gioca più un ruolo decisivo, quindi chiamiamo complessità temporale The i coefficienti a termine costante vengono omessi perché la dimensione predefinita dei dati è generalmente sufficientemente grande. Sulla base di questo fatto, una classificazione della complessità temporale dell'algoritmo fornita è la seguente:
O(1) ordine costante < O(logn) ordine logaritmico < O(n) ordine lineare < O(n^2) ordine quadrato < O(n^3) (ordine cubico) < O(2^n) (ordine esponenziale )
4.Qual è la base di log in O(logn)?
Ma collettivamente diciamo logn, cioè, trascurando la descrizione della base, il logaritmo di n in base 2 = il logaritmo di 10 in base 2 * il logaritmo di n in base 10, e il logaritmo di 10 in base 2 è un costante che può essere ignorata
Riepilogo delle domande
0.Interessante
Un passaggio di codifica è feroce come una tigre, sconfiggendo il 500% in sottomissione
Una serie di idee è sbocciata dalle risate e le candidature hanno superato l'80%
1. Non capisco
1.Albero
145. Attraversamento postordine Attraversamento di Morris
105. Perché non è possibile utilizzare la mappatura degli indici nel metodo conciso?
2. Frequenza delle interviste
1.Ricerca 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.Prima di tutto l'ampiezza
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. Tabella 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. Metodo del backtracking
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. Elenco collegato
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. Ordina
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. Prima la profondità
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.Albero
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.Array
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.Doppi puntatori
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.Impila
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 corde
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,
sottoargomento
Domande simili
1.La somma di due numeri
1,15,
Relativo al codice
1. Stile del codice
1. Principi fondamentali
1.Rientro
È preferibile utilizzare 4 spazi. Al momento, quasi tutti gli IDE convertono le schede in 4 spazi per impostazione predefinita, quindi non ci sono grossi problemi.
2. Lunghezza massima della linea
79 caratteri, sarebbe meglio usare le barre rovesciate per le interruzioni di riga
3.Importa
1.Formatta
L'importazione si trova all'inizio del file, dopo il commento del file. L'importazione è solitamente un'importazione a riga singola o da ... importa
2. Sequenza
1. Importazione della libreria standard 2. Importazioni rilevanti da terzi 3. Importazioni di applicazioni/librerie locali specifiche Inserire una riga vuota tra ciascun gruppo di importazione. Le importazioni assolute sono consigliate perché sono più leggibili; è possibile utilizzare importazioni relative esplicite invece di importazioni assolute quando si ha a che fare con layout di pacchetti complessi, che sono troppo dettagliati
3. Attenzione
4. Commenti
5. Stringhe di documenti docstrings
6. Convenzione di denominazione
Funzioni, variabili e proprietà sono scritte in lettere minuscole, con trattini bassi tra le parole, ad esempio lowercase_underscore. Le classi e le eccezioni dovrebbero essere denominate con la prima lettera di ogni parola in maiuscolo (camello alto), come CapitalizedWord. Gli attributi dell'istanza protetta dovrebbero iniziare con un singolo carattere di sottolineatura. Le proprietà dell'istanza privata dovrebbero iniziare con due caratteri di sottolineatura. Le costanti a livello di modulo devono essere scritte in maiuscolo, con trattini bassi tra le parole. Il primo parametro del metodo di istanza nella classe dovrebbe essere denominato self, che rappresenta l'oggetto stesso. Il primo parametro del metodo della classe (metodo cls) dovrebbe essere denominato cls, indicando la classe stessa
2. Presta attenzione ai dettagli
0.Strumenti
1. Avvolgimento del codice
I ritorni a capo dovrebbero precedere gli operatori binari
2. Riga vuota
Ci sono due righe vuote tra la funzione di livello superiore e la definizione della classe. C'è una riga vuota tra le definizioni di funzione all'interno della classe
3. Virgolette
Le virgolette doppie e le virgolette singole sono la stessa cosa, ma è meglio seguire uno stile a cui sono abituato a usare le virgolette singole perché: Non è necessario tenere premuto il tasto Maiusc durante la scrittura del codice per migliorare l'efficienza Alcune stringhe del linguaggio devono utilizzare virgolette doppie e Python non ha bisogno di aggiungere la barra rovesciata durante l'elaborazione.
4. Spazio
Utilizza lo spazio per il rientro invece del tasto tab. Utilizza quattro spazi per ogni livello di rientro relativo alla sintassi. Per le espressioni lunghe che si estendono su più righe, tutte tranne la prima riga dovrebbero essere rientrate di 4 spazi sopra il livello normale. Quando si utilizzano pedici per recuperare elementi di elenco, chiamare funzioni o assegnare valori ad argomenti di parole chiave, non aggiungere spazi attorno ad essi. Quando si assegna un valore a una variabile, è necessario scrivere uno spazio a sinistra e a destra del simbolo di assegnazione e solo uno
Immediatamente all'interno di parentesi quadre, parentesi quadre o parentesi graffe Immediatamente prima di una virgola, punto e virgola o due punti, deve esserci uno spazio dopo. Immediatamente prima della parentesi di apertura di un elenco di parametri di funzione Immediatamente prima della parentesi di apertura di un'operazione di indice o di suddivisione in porzioni Inserisci sempre 1 spazio su entrambi i lati dei seguenti operatori binari: assegnazione (=), assegnazione incrementale (=, -=, ecc.), confronto (==, <, >, !=, <>, <=, > = , in, non, in, è, non è), Booleano (e, o, non) Inserisci degli spazi attorno agli operatori matematici Se utilizzato per specificare argomenti di parole chiave o valori di argomenti predefiniti, non utilizzare spazi attorno a = return magic(r=real, i=imag)
Aggiungi gli spazi necessari, ma evita gli spazi non necessari. Evita sempre gli spazi bianchi finali, inclusi eventuali caratteri invisibili. Pertanto, se il tuo IDE supporta la visualizzazione di tutti i caratteri invisibili, attivalo! Allo stesso tempo, se l'IDE supporta l'eliminazione del contenuto vuoto alla fine della riga, abilitalo anche tu! yapf può aiutarci a risolvere questa parte. Dobbiamo solo formattare il codice dopo averlo scritto.
5. Dichiarazione composta
Non è consigliabile includere più istruzioni in una riga
6. Virgola finale
Quando gli elementi dell'elenco, i parametri e gli elementi importati potrebbero continuare ad aumentare in futuro, lasciare una virgola finale è una buona scelta. L'uso comune è che ogni elemento sia su una propria riga, con una virgola alla fine, e un tag di chiusura sia scritto sulla riga successiva dopo l'ultimo elemento. Se tutti gli elementi sono sulla stessa riga, non è necessario farlo
7. Risolvere i problemi rilevati dal linter
Usa flake8 per controllare il codice Python e modificare tutti gli errori e gli avvisi controllati a meno che non ci siano buone ragioni.
2. Codici comunemente usati
1. Dizionario
1. Contare il numero di occorrenze
conta[parola] = conta.get(parola,0) 1
2. Elenco
1. Elenca l'ordinamento delle parole chiave specificate
items.sort(key=lambda x:x[1], reverse=True)# Ordina il secondo elemento in ordine inverso
2. Converti ciascun elemento nell'elenco di stringhe in un elenco di numeri
lista(mappa(eval,lista))
3. Invertire tutti gli elementi nell'elenco
ris[::-1]
4. Scambia due numeri nell'elenco
res[i],res[j] = res[j],res[i] #Non sono richieste variabili intermedie
5. Assegnare un elenco di lunghezza fissa
G = [0]*(n 1) #Lista di lunghezza n 1
6. Trova l'elemento più grande nell'intervallo [i, j] nell'elenco arr
max(arr[i:j 1])
7. Utilizzare questo elemento per dividere l'elenco in ordine
sinistra_l = ordine[:idx] destra_l = ordine[idx 1:]
3. Ciclo/giudizio
1. Quando è necessario determinare solo il numero di loop e non è necessario ottenere il valore
for _ in range(len(coda)):
2.Abbreviazione di if...else
node.left = recur_func(left_l) se left_l altrimenti Nessuno
3. Ciclo inverso
for i in range(len(postorder)-2, -1,-1)
4. Ottieni elementi e pedici allo stesso tempo
for i, v in enumerate(nums):
5. Attraversa più elenchi contemporaneamente e restituisce più valori
for net, opt, l_his in zip(nets, Optimizers, Loss_ his):
Senza zip, verrà emesso un solo valore ogni volta
6. Attraversa più elenchi e restituisce un valore
per l'etichetta in ax.get_xticklabels() ax.get_yticklabels():
4. Mappatura
1. Converti rapidamente le strutture attraversabili in mappature corrispondenti ai pedici
indice = {elemento: i per i, elemento in enumerate (in ordine)}
Può anche essere ottenuto utilizzando list.index(x), ma la lista deve essere attraversata ogni volta e il tempo è O(n). Quanto sopra è O(1).
3. Metodi comunemente utilizzati
1. Viene fornito con il sistema
0.Classificazione
1.Conversione del tipo
1.int(x)
1. Quando il tipo a virgola mobile viene convertito in un tipo intero, la parte decimale viene scartata direttamente.
2.float(x)
3.str(x)
1.ordinato(num)
1. Ordina gli elementi specificati
2.map(funzione,elenco)
1. Applicare la funzione del primo parametro a ciascun elemento del secondo parametro
mappa(valutazione,elenco)
3.len(i)
1. Ottenere la lunghezza, che può essere di qualsiasi tipo
4.enumerare()
1. Combina un oggetto dati attraversabile (come una lista, una tupla o una stringa) in una sequenza di indici ed elenca contemporaneamente il pedice dei dati e i dati. Generalmente utilizzato nei cicli for: for i, elemento in enumerate(seq) :.
2.enumerate(sequence, [start=0]), il pedice inizia da 0, restituisce l'oggetto enumerate (enumerazione)
5.tipo(x)
1. Determinare il tipo di variabile x, applicabile a qualsiasi tipo di dati
2. Se è necessario utilizzare la variabile tipo come condizione nel giudizio condizionale, è possibile utilizzare la funzione type() per il confronto diretto.
se tipo(n) == tipo(123):
2.Elenco[]
1. Quando viene utilizzato lo stack
1. Spingere nella pila
stack.append()
2. Esci dallo stack
stack.pop()
Pop l'ultimo elemento
3. Restituisce l'elemento superiore dello stack
Non esiste un metodo di visualizzazione nell'elenco. Puoi solo prima visualizzarlo e poi aggiungerlo.
2. Quando utilizzato in coda
1. Unisciti alla squadra
coda.append()
2.Lascia la squadra
coda.pop(0)
Il primo elemento da rimuovere dalla coda
3. Ottieni l'indice dell'elemento
m_i = numeri.indice(max_v)
3. Decoda della coda
1. La coda a doppia estremità viene utilizzata come coda
1. Unisciti alla squadra
coda.append()
2.Lascia la squadra
coda.popleft()
4. Errori/differenze comuni
1. Formato della domanda
1.IndentationError: previsto un blocco rientrato
Si è verificato un problema con il rientro. L'errore più comune è mescolare i tasti Tab e Spazio per ottenere il rientro del codice.
Un altro motivo comune per l'errore sopra riportato è che non è presente il rientro della prima riga. Ad esempio, quando si scrive un'istruzione if, aggiungere due punti dopo di essa. Se si modifica direttamente la riga, molti editor di codice rientreranno automaticamente la prima riga. Tuttavia, alcuni editor di codice potrebbero non avere questa funzione. In questo caso, è meglio sviluppare un'abitudine. Si prega di non premere la barra spaziatrice più volte di seguito. Si consiglia di premere semplicemente il tasto Tab.
problema 1.pycharm
1. Come eliminare la richiesta che il certificato del server non è attendibile in pycharm
Fare clic su File > Impostazioni > Strumenti > Certificati server > Accetta automaticamente certificati non attendibili
1.abilità di pycharm
1. Importa file Python nel progetto corrente
Copia direttamente il file corrispondente nel file manager nella directory corrispondente al progetto corrente e apparirà direttamente in pycharm. Se il problema della versione non si verifica, comprimi tu stesso la directory del progetto corrente e poi espandila di nuovo.
2. Errore operativo
0. Per esaminare il tipo di errore, assicurati di guardare l'errore sulla riga sopra l'ultima linea di divisione.
Altri errori
1."nessun modulo denominato XX"
Man mano che il livello di sviluppo di tutti migliora e la complessità del programma aumenta, nel programma verranno utilizzati sempre più moduli e librerie di terze parti. Il motivo di questo errore è che la libreria "XX" non è installata, pip install ww
1.errore
1.Errore: comando errato con stato di uscita 1
Scarica il pacchetto di installazione di terze parti corrispondente e accedi alla directory di download per installare il nome completo del file scaricato.
2.errore: è richiesto Microsoft Visual C 14.0
Installa Microsoft Visual C 14.0, il blog ha l'indirizzo di download visualcppbuildtools_full
3.errore: comando non valido 'bdist_wheel'
rotella di installazione pip3
2.Errore attributo Errore di proprietà
0. Soluzione generale
Se viene richiesto quale file del modulo presenta un errore, trova questo modulo, eliminalo e sostituiscilo con lo stesso file di un amico che può essere eseguito.
1.AttributeError: il modulo 'xxx' non ha l'attributo 'xxx'
1. Il nome del file è in conflitto con le parole chiave di Python e simili.
2. Due file py si importano a vicenda, provocando l'eliminazione di uno di essi.
2.AttributeError: il modulo 'six' non ha l'attributo 'main'
Il problema della versione pip 10.0 non ha main(), È necessario eseguire il downgrade della versione: python -m pip install –upgrade pip==9.0.1
Alcune API sono cambiate dopo Pip v10, con conseguente incompatibilità tra la vecchia e la nuova versione, che influisce sulla nostra installazione e aggiornamento dei pacchetti. Basta aggiornare l'IDE e il gioco è fatto! Durante l'aggiornamento, l'IDE ci ha fornito anche suggerimenti amichevoli. PyCharm -> Aiuto -> Controlla aggiornamenti
Non aggiornare la versione crackata, altrimenti le informazioni di attivazione non saranno più valide.
3.AttributeError: il modulo 'async io' non ha l'attributo 'run'
Hai chiamato un file asyncio py Se il controllo non è il primo, devi controllare la tua versione di Python perché Python3.7 e versioni successive supportano solo il metodo run. 1 Aggiorna la versione di Python 2 run viene riscritta come segue loop = asyncio.get_event_loop() risultato = loop.run_until_complete(coro)
4.AttributeError: il modulo 'asyncio.constants' non ha l'attributo '_SendfileMode'
Sostituisci i file delle costanti in asyncio
3.Erroretipo
1. "TypeError: l'oggetto 'tuple' non può essere interpretato come un numero intero"
t=('a','b','c') per i nell'intervallo(t):
Tipico problema di errore di tipo. Nel codice precedente, la funzione range() prevede che il parametro in entrata sia un numero intero (intero), ma il parametro in entrata è una tupla (tupla). il numero di tuple può essere di tipo intero len(t) Ad esempio, modificare range(t) nel codice precedente in range(len(t))
2. "TypeError: l'oggetto 'str' non supporta l'assegnazione di elementi"
Causato dal tentativo di modificare il valore di una stringa, che è un tipo di dati immutabile
s[3] = 'a' diventa s = s[:3] 'a' s[4:]
3. "TypeError: impossibile convertire implicitamente l'oggetto 'int' in str"
Causato dal tentativo di concatenare un valore non stringa con una stringa, basta eseguire il cast del valore non stringa in una stringa con str()
4. "TypeError: tipo non hashable: 'list'
Quando si utilizza la funzione set per costruire un insieme, gli elementi nell'elenco di parametri fornito non possono contenere elenchi come parametri.
5.TypeError: non è possibile utilizzare un modello di stringa su un oggetto simile a byte
Quando si passa da python2 a python3, si incontreranno inevitabilmente alcuni problemi. Le stringhe Unicode in python3 sono nel formato predefinito (tipo str) e le stringhe con codifica ASCII (tipo byte) Il tipo bytes contiene valori byte e in realtà non lo è un carattere. String, tipo array di byte python3 e bytearray) deve essere preceduto dall'operatore b o B; in python2 è il contrario, la stringa codificata ASCII è l'impostazione predefinita e la stringa Unicode deve essere preceduta dall'operatore u o U.
import chardet #È necessario importare questo modulo e rilevare il formato di codifica codifica_tipo = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica di conseguenza e assegnalo all'identificatore originale (variabile)
4.IOErrore
1. "IOErrore: file non aperto per la scrittura"
>>> f=aperto ("ciao.py") >>> f.write ("prova")
La causa dell'errore è che il parametro della modalità di lettura-scrittura mode non viene aggiunto ai parametri in entrata di open("hello.py"), il che significa che il modo predefinito per aprire il file è di sola lettura.
La soluzione è cambiare la modalità modalità in modalità scrittura autorizzazione w, f = open("ciao. py", "w ")
5.Errore di sintassi Errori grammaticali
1.SyntaxError: sintassi non valida”
Causato dalla dimenticanza di aggiungere i due punti alla fine di istruzioni come if, elif, else, for, while, class e def
Utilizzo errato di "=" invece di "==". Nei programmi Python, "=" è un operatore di assegnazione e "==" è un'operazione di confronto uguale.
6.Errore di decodifica Unicode Errore di interpretazione della codifica
Il codec 1.'gbk' non può decodificare i byte
Nella maggior parte dei casi ciò accade perché il file non è codificato UTF8 (ad esempio, potrebbe essere codificato GBK) e il sistema utilizza la decodifica UTF8 per impostazione predefinita. La soluzione è passare al metodo di decodifica corrispondente: con open('acl-metadata.txt','rb') come dati:
open(percorso, '-modalità-', codifica='UTF-8') ovvero open(percorso nome file, modalità lettura-scrittura, codifica)
Modalità di lettura e scrittura comunemente utilizzate
7.ErroreValore
1.troppi valori da scompattare
Quando si chiama una funzione, non ci sono abbastanza variabili per accettare il valore restituito.
8.Errore
1.WinError 1455] Il file di paging è troppo piccolo e l'operazione non può essere completata.
1. Riavvia Pycharm (praticamente inutile)
2. Imposta num_works su 0 (forse inutile)
3. Aumenta la dimensione del file di paging (risolvi completamente il problema)
9.Errore di importazione
1.Caricamento DLL non riuscito: il file di paging è troppo piccolo e l'operazione non può essere completata.
1. Non solo è in esecuzione un progetto, ma è in esecuzione anche il programma Python di un altro progetto, basta spegnerlo.
2. Il sistema operativo Windows non supporta il funzionamento multiprocesso di Python. La rete neurale utilizza più processi nel caricamento del set di dati, quindi imposta il parametro num_workers in DataLoader su 0.
Caricamento 1.DLL non riuscito: il sistema operativo non può eseguire %1
0. Recentemente, mentre stavo eseguendo un progetto scrapy, il framework scrapy installato ha segnalato improvvisamente un errore e sono stato colto di sorpresa.
1. Poiché non è difficile installare Scrapy in Anaconda, non è così semplice ed efficiente come reinstallarlo per trovare una soluzione.
2. Non riesco a disinstallarlo completamente utilizzando semplicemente il comando conda rimuovi scrapy. Il motivo potrebbe essere che ho installato scrapy due volte utilizzando rispettivamente pip e conda. I lettori possono provare sia i comandi di disinstallazione pip che conda.
pip disinstalla scrapy conda rimuovi lo scrapy
Reinstallare pip install scrapy
errore di classe
1. Problemi di proprietà dell'ereditarietà multipla delle classi
Abbiamo modificato solo A.x, perché è stato modificato anche C.x? Nei programmi Python, le variabili di classe vengono trattate internamente come dizionari, che seguono l'ordine di risoluzione del metodo comunemente citato (MRO). Quindi nel codice sopra, poiché l'attributo x nella classe C non viene trovato, cercherà la sua classe base (sebbene Python supporti l'ereditarietà multipla, c'è solo A nell'esempio sopra). In altre parole, la classe C non ha un proprio attributo x, che è indipendente da A. Pertanto, C.x è in realtà un riferimento ad A.x
errore di ambito
1. Variabili globali e variabili locali
1. La variabile locale x non ha valore iniziale e la variabile esterna X non può essere introdotta internamente.
2. Diverse operazioni sulle liste
Fool non ha assegnato un valore a lst, ma pazzo2 lo ha fatto. Sai, lst = [5] è l'abbreviazione di lst = lst [5] e stiamo cercando di assegnare un valore a lst (Python lo tratta come una variabile locale). Inoltre, la nostra assegnazione a lst è basata su lst stesso (che ancora una volta viene trattato come una variabile locale da Python), ma non è stato ancora definito, quindi si verifica un errore! Quindi qui dobbiamo distinguere tra l’uso di variabili locali e variabili esterne.
2.1 Aggiornamento Python2 Errore Python3
1.print diventa print()
1. Nella versione Python 2, print viene utilizzato come istruzione. Nella versione Python 3, print appare come una funzione. Nella versione Python 3, tutto il contenuto print deve essere racchiuso tra parentesi.
2.raw_Input diventa input
Nella versione Python 2, la funzionalità di input è implementata tramite raw_input. Nella versione Python 3, viene implementato tramite input
3. Problemi con gli interi e divisione
1. "TypeError: l'oggetto 'float* non può essere interpretato come un numero intero"
2. In Python 3, int e long sono unificati nel tipo int. Int rappresenta un numero intero di qualsiasi precisione. Il tipo long è scomparso in Python 3 e anche il suffisso L è stato deprecato quando si utilizza int supera la dimensione dell'intero locale , non causerà quindi l'eccezione OverflowError
3. Nelle versioni precedenti di Python 2, se il parametro è int o long, verrà restituito il risultato della divisione arrotondato per difetto (floor) e se il parametro è float o complesso, verrà restituito il risultato equivalente con una buona approssimazione del risultato dopo la divisione
4. "/" in Python 3 restituisce sempre un numero in virgola mobile, che significa sempre una divisione verso il basso. Basta cambiare "/" in "//" per ottenere il risultato della divisione intera
4. Importante aggiornamento della gestione delle eccezioni
1. Nei programmi Python 2, il formato per rilevare le eccezioni è il seguente: tranne Eccezione, identificatore
tranne ValueError, e: # Python 2 gestisce singole eccezioni tranne (ValueError, TypeError), e: # Python 2 gestisce più eccezioni
2. Nei programmi Python 3, il formato per rilevare le eccezioni è il seguente: eccetto Exception come identificatore
tranne ValueError as e: # Python3 gestisce una singola eccezione tranne (ValueError, TypeError) as e: # Python3 gestisce più eccezioni
3. Nei programmi Python 2, il formato per lanciare eccezioni è il seguente: raise Exception, args
4. Nei programmi Python 3, il formato per lanciare eccezioni è il seguente: raise Exception(args)
raise ValueError, e # metodo Python 2.x raise ValueError(e) # Metodo Python 3.x
5.xrange() diventa range()
"NameError: il nome 'xrange' non è definitow"
6. La ricarica non può essere utilizzata direttamente
"il nome 'reload' non è definito e AttributeError: il modulo 'sys' non ha att"
importa importlib importlib.reload(sys)
7. Niente più tipi Unicode
"python unicode non è definito"
In Python 3, il tipo Unicode non esiste più ed è sostituito dal nuovo tipo str. Il tipo str originale in Python 2 è stato sostituito da byte in Python 3
8.has_key è stato abbandonato
"AttributeError: l'oggetto 'dieta' non ha l'attributo 'has_key' "
has_key è stato abbandonato in Python 3. Il metodo di modifica consiste nell'utilizzare in invece di has_key.
9.urllib2 è stato sostituito da urllib.request
"lmportError: nessun modulo denominato urllib2"
La soluzione è modificare urllib2 in urllib.request
10. Problemi di codifica
TypeError: non è possibile utilizzare un modello di stringa su un oggetto simile a byte
Quando si passa da python2 a python3, si incontreranno inevitabilmente alcuni problemi. Le stringhe Unicode in python3 sono nel formato predefinito (tipo str) e le stringhe con codifica ASCII (tipo byte) Il tipo bytes contiene valori byte e in realtà non lo è un carattere. String, tipo array di byte python3 e bytearray) deve essere preceduto dall'operatore b o B; in python2 è il contrario, la stringa codificata ASCII è l'impostazione predefinita e la stringa Unicode deve essere preceduta dall'operatore u o U.
import chardet #È necessario importare questo modulo e rilevare il formato di codifica codifica_tipo = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica di conseguenza e assegnalo all'identificatore originale (variabile)
2.1 Comandi della riga del prompt dei comandi
1. Directory delle operazioni
1.cd cambia la sottodirectory corrente, puoi copiare direttamente il percorso e inserirlo in una volta sola
2. Il comando CD non può modificare il disco corrente. CD.. ritorna alla directory precedente CD\ significa tornare alla directory del disco corrente. Quando CD non ha parametri, viene visualizzato il nome della directory corrente.
3.d: modifica la posizione del disco
2.Relativo a Python
1.pip
0.Chiedi aiuto
pip aiuto
0. Cambia la sorgente pip
1. Uso temporaneo
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nome del pacchetto
2. Imposta come predefinito
set di configurazione pip global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Dopo averlo impostato come predefinito, le future librerie di installazione verranno scaricate dalla sorgente Tsinghua e non sarà necessario aggiungere l'URL della sorgente mirror.
3. Indirizzo di origine del mirror principale
1. Installare la libreria specificata
nome del pacchetto di installazione pip
2. Installare la versione specificata della libreria specificata
pip installa nome_pacchetto==1.1.2
3. Controllare quali librerie sono installate
elenco pip
4. Visualizza le informazioni specifiche della biblioteca
pip mostra -f nome del pacchetto
5.Dove è installato pip?
pip -V
6. Libreria di installazione batch
pip install -r d:\\requirements.txt Scrivi direttamente nome pacchetto == numero di versione nel file
7. Installare la libreria utilizzando il file wheel
1. Trova il file .whl della libreria corrispondente sul sito web sottostante, cerca con Ctrl F e presta attenzione alla versione corrispondente. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. Nella cartella in cui si trova .whl, premere il tasto Maiusc e fare clic con il pulsante destro del mouse per aprire la finestra CMD o PowerShell (Oppure inserisci questa cartella tramite la riga di comando)
3. Immettere il comando: pip install matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Disinstallare la libreria
pip disinstalla nome_pacchetto
9.Aggiornamento della libreria
pip install --upgrade nome_pacchetto
10. Salvare l'elenco delle librerie nel file specificato
pip freeze > nomefile.txt
11. Controlla le librerie che devono essere aggiornate
elenco pip -o
12. Verifica eventuali problemi di compatibilità
Verifica se la libreria installata ha dipendenze compatibili pip check nome-pacchetto
13. Scarica la libreria in locale
Scarica la libreria in un file locale specificato e salvala in formato whl pip download nome_pacchetto -d "Percorso file da salvare"
2. Comprimere il programma in un file eseguibile
pyinstaller -F nomefile.py
3. Scrittura del codice
1. Nessun formato
1. Senza i, può essere scritto solo come i =1
2. Formati diversi
1. Classe
1. Parametri
0. Quando si definisce una classe, il primo parametro deve essere self e lo stesso vale per tutti i metodi Quando si chiamano i membri di questa classe, è necessario utilizzare i membri.
1. Per i parametri nella classe, scrivi prima il nome della variabile, aggiungi i due punti, quindi scrivi il nome del tipo, separato da virgole
2. Una volta completata la definizione della classe, è possibile aggiungere -> nome del tipo alla fine per indicare il tipo di valore restituito.
2. Chiama
1. Per richiamarsi per la ricorsione in una classe, è necessario utilizzare la funzione self.self (non è necessario aggiungere self nei parametri)
2. Quando si utilizza una classe, non è necessario creare una nuova classe, basta usarla direttamente root = TreeNode(max_num)
3.Metodo
1._Inizia con un carattere di sottolineatura per definire il metodo di protezione
2.__Inizia con due caratteri di sottolineatura per definire i metodi privati
sottoargomento
2. Numeri
1. Espressione di più e meno infinito
float("inf"), float("-inf")
3. Simboli
1. In Python / significa divisione normale con resto, // significa divisione intera senza resto.
2. Non! in Python è rappresentato da not
3.Il quadrato in Python è **
4. Dichiarazione
1. È necessario aggiungere i due punti dopo tutte le istruzioni relative alle parole chiave: tranne return
2. Non è necessario aggiungere () alle condizioni nei cicli, nei giudizi e in altre istruzioni simili e non ci sono {} nei blocchi di istruzioni. Utilizzare il rientro per rappresentare rigorosamente il formato.
5. Commenti
1. Commento a riga singola n.
2. Commenti su più righe ''' o """
4. Codici diversi
1. Elenco
1. Quando è necessario utilizzare esplicitamente gli indici degli elenchi, è necessario utilizzare G = [0]*(n 1) # per creare un elenco con una lunghezza di n 1, altrimenti l'indice sarà fuori limite.
2. Quando si crea un elenco bidimensionale a lunghezza fissa, se *n fallisce, provare a utilizzare un ciclo
dp = [[float('inf') for _ in range(n)] for _ in range(n)]
3. Se il valore restituito dalla funzione è un elenco, ma viene ricevuta solo una variabile, aggiungere una virgola
linea, = ax.plot(x, np.sin(x))
5.Programmazione Python
1. Problema relativo al file
1. Quando si importano altri progetti che richiedono un file, utilizzare il percorso assoluto del file.
2. File di esecuzione della riga di comando
Nome file Python.py
3. Risorse mirror del pacchetto di terze parti
Quando usi pycharm per scaricare pacchetti di terze parti, aggiungilo in Gestisci repository http://mirrors.aliyun.com/pypi/simple/, elimina l'URL originale
6.Tasti di scelta rapida Pycharm
1. Commento (aggiungi/elimina)
Ctrl/
Commenti a riga singola#
2.Codice spostamento a destra
Tab
3. Codice spostamento a sinistra
Scheda Maiusc
4. Rientro automatico
Ctrl alt I
5. Corri
CTRL MAIUSC F10
6.Formattazione standard PEP8
Ctrl-alt L
È anche il tasto di scelta rapida per il blocco QQ. Annulla l'impostazione in QQ.
6.1 Soluzione rapida
alt invio e premere invio
7. Copia una riga/più righe/parte selezionata
CTRL D
8. Cancella una/più righe
Ctrl Y
9.Trova
CTRLF
9.1 Ricerca globale
Ctrl Maiusc F
10.Sostituzione
CTRL R
10.1 Sostituzione globale
Ctrl Maiusc R
11.Spostare il cursore sulla riga successiva
turno Invio
12.Clic del cursore su più righe
alt clic con il pulsante sinistro del mouse
13. Passa al punto di interruzione successivo
alt F9
14.Cancellazione
CTRLZ
14.1 Anti-cancellazione
Ctrl-Shift Z
15. Copia il codice della classe genitore
Ctrl o
16. Selezionare il blocco parola/codice
Ctrl W
17. Visualizza rapidamente i documenti (informazioni sul codice)
CTRLQ
18. Inserire una linea verso il basso in qualsiasi posizione
entrare nel turno
19. Inserisci una riga verso l'alto in qualsiasi posizione
Ctrl-Alt Invio
20. Visualizza la vista del progetto
alt1
21. Visualizza la vista della struttura
alt7
22. Entra rapidamente nel codice
Ctrl clic sinistro
23. Visualizza rapidamente la cronologia
alt sinistra (ritorno)/tasto destro (avanti)
24. Visualizza rapidamente diversi metodi
alt su/giù
25. Cambia visualizzazione
Scheda Ctrl
26. Visualizza i file di risorse
cambiare due volte
27. Scopri dove viene chiamato il metodo
Ctrl alt H Fare doppio clic per determinare la posizione
28.Visualizza la classe genitore
Ctrl U
29. Visualizza la relazione di eredità
Ctrl H
Pagina 7.pycharm
0.Barra dei menu
1.Finestra di visualizzazione
1.Barra di navigazione della barra di navigazione
2.Refactoring Refactoring
3.Strumenti
Controllo della versione 4.VCS
1. Eseguire il debug del codice
1. Fai clic davanti al codice per inserire un punto di interruzione, fai clic sul crawler per eseguire il debug e fai clic sulla casella accanto al crawler per terminare il debug
2. Fare clic sull'icona ↘ per passare al punto di interruzione successivo e sarà possibile osservare continuamente il valore della variabile.
2.impostazioni delle impostazioni
1.aspetto e comportamento, interfaccia e comportamento
1.aspetto generale dello stile
1.Tema a tema
1.Tema nero Darcula
2.Tema ad alto contrasto ad alto contrasto
3.Tema intelligente intelligente
2.carattere personalizzato carattere personalizzato
2.impostazioni di sistemaimpostazioni di sistema
1.aggiornamento
Il rilevamento automatico degli aggiornamenti può essere disattivato
2.Tasti di scelta rapida della mappa dei tasti
1. È possibile effettuare la ricerca direttamente in base alla visualizzazione del sistema (nota di commento)
3.editor modifica solo l'area
1. Carattere del codice carattere (dimensione regolabile)
2. Combinazione colori dell'area codice combinazione colori
3. Stile codice stile codice
0.Python può apportare modifiche ad ogni dettaglio
1.Pitone
1.Numero di rientro
2. Impostazione dello spazio spaziale
3. Avvolgimento e bretelle
1. Avvolgimento rigido con il numero massimo di codici in una riga
4.Righe vuote righe vuote
4.Ispezioni
1.PEP 8 è un codice standard, non un errore grammaticale. Cerca di mantenerlo il più standard possibile.
2. È possibile impostare quale contenuto viene controllato e anche impostare la severità del controllo in Gravità.
5.Modelli di file e codice Modelli di file e codice
1. Aggiungi informazioni in Python.Script che verranno visualizzate ogni volta che viene creato un nuovo file, Scopri quali informazioni puoi aggiungere online
6.Codifica file codifica file
1.UTF-8 predefinito
7.Modelli dinamici di Live Templates (facili da usare e padroneggiare)
0. Puoi fare clic sul segno più per aggiungere tu stesso il modello e assicurarti di impostare la posizione di utilizzo.
1.per ciclo
Scrivi iter per selezionare un ciclo e continua a premere Invio per scrivere il codice
2. Scrivere una lista utilizzando un ciclo for
Basta scrivere compl
4.Plugin
5.Progetto
1.Project Interpret interprete di progetto
1. Puoi gestire e aggiungere librerie di terze parti (fai clic sul segno più e cerca)
2. È possibile impostare diversi interpreti per il progetto corrente
3.Gestione del progetto
1. Fare clic con il pulsante destro del mouse sul file e selezionare Mostra in Explorer per aprire direttamente il percorso del file.
2.Nuovo
1.Archivio
Possono essere vari altri file, non necessariamente file Python.
2.Nuovo file Scratch
Crea file temporanei, equivalenti a fogli di carta, utilizzati per testare parte del codice
3.Directory
Crea una nuova cartella di livello inferiore
4.File Python
Rispetto alle cartelle ordinarie, ci sono più file Python di inizializzazione vuoti.
5. Quando crei un nuovo file HTML, puoi fare clic con il pulsante destro del mouse su di esso e aprirlo in un browser per vedere l'effetto di visualizzazione.
4. Pagina dei risultati del codice
1. Terminale terminale
1. È uguale al CMD di sistema. Puoi installare il pacchetto direttamente qui e utilizzare il comando dos.
2.Console della console Python
Puoi scrivere direttamente il codice Python, eseguirlo in modo interattivo e modificarlo riga per riga.
3.DA FARE
È equivalente a un promemoria. Scrivi TODO ('') nel punto di codice, in modo da poterlo trovare rapidamente e continuare a lavorare. Può essere utilizzato per la cooperazione reciproca.
4. L'avatar all'estrema destra
È possibile regolare la severità del controllo del codice. La modalità di risparmio energetico equivale a posizionare la freccia all'estrema sinistra. Tutti gli errori grammaticali non verranno controllati.
5.Ambiente virtuale
1.
2. L'ambiente virtuale viene creato perché nello sviluppo effettivo è necessario utilizzare contemporaneamente diverse versioni di interpreti Python e diverse versioni della stessa libreria. Pertanto è necessario creare un ambiente virtuale per isolare l'ambiente di progetto da altri ambienti (ambiente di sistema, altri ambienti virtuali)
3. Esistono tre modi per creare un ambiente virtuale in PyCharm, virtualen, conda e pipen
4. Virtualen può essere immaginato come la creazione di una copia isolata dell'ambiente di sistema corrente. L'interprete utilizzato è lo stesso installato (copia).
5. Conda seleziona una versione specifica di Python in base alle tue esigenze, quindi scarica la versione pertinente da Internet e crea un nuovo ambiente diverso dall'ambiente di sistema. Anche l'interprete utilizzato è diverso da quello installato.
6. Pipen è simile a virtualen. Crea anche una copia basata sull'ambiente di sistema esistente, ma pipen utilizza Pipfile invece del require.txt di virtualen per la gestione delle dipendenze, il che è più conveniente.
6.Interrelazione
7.SVN
1. Quando scarichi dal sito ufficiale, seleziona la versione inglese 1.10, che è diversa dalla versione cinese.
2. Quando installi TortoiseSVN.msi, assicurati di controllare gli strumenti da riga di comando
3. Una serie di file svn.exe apparirà nella directory bin installata.
4. Configura in pycharm e trova il file svn.exe nella directory bin in setting/version control/subversion
5. Fare clic con il pulsante destro del mouse sul file modificato nella cartella per visualizzare le modifiche
6. Fai clic con il pulsante destro del mouse sul progetto e verrà visualizzato un nuovo collegamento di sovversione. Puoi inviare commit e annullare tutte le modifiche ripristinate.
8. Plug-in facili da usare
5. Frammenti di codice comuni
1.Ingresso
1. Ottenere input dall'utente di lunghezza variabile
def getNum(): #Ottiene l'input dell'utente di lunghezza variabile numeri = [] iNumStr = input("Inserisci un numero (premi Invio per uscire): ") while iNumStr != "": nums.append(eval(iNumStr)) iNumStr = input("Inserisci un numero (premi Invio per uscire): ") restituire i numeri
2.Testo
1. Denoising e normalizzazione del testo inglese
def getText(): txt = open("frazione.txt", "r").read() txt = txt.lower() #Converti tutto in lettere minuscole for ch in '!"#$%&()* ,-./:;<=>?@[\\]^_'{|}~': txt = txt.replace(ch, " ") #Sostituisci i caratteri speciali nel testo con spazi restituire il testo
2. Conta la frequenza delle parole nel testo inglese
frazioneTxt = getText() parole = amleTxt.split() #Separa il testo con spazi e convertilo in una lista counts = {} #Crea un nuovo dizionario per parola in parole: #Conta la frequenza di ogni parola, il valore predefinito è 0 conta[parola] = conta.get(parola,0) 1 items = list(counts.items()) #Converti il dizionario in list items.sort(key=lambda x:x[1], reverse=True)# Ordina il secondo elemento in ordine inverso per i nell'intervallo(20): parola, conteggio = elementi[i] print ("{0:<10}{1:>5}".format(parola, conteggio))
3. Statistiche sulla frequenza delle parole del testo cinese
importare jieba txt = open("treregni.txt", "r", codifica='utf-8').read() parole = jieba.lcut(txt) conta = {} per parola in parole: se len(parola) == 1: Continua altro: conta[parola] = conta.get(parola,0) 1 elementi = lista(conta.items()) #Converti in lista per ordinare items.sort(key=lambda x:x[1], reverse=True) per i nell'intervallo(15): parola, conteggio = elementi[i] print ("{0:<10}{1:>5}".format(parola, conteggio))
3.Serie
1. Trova il valore massimo nell'array
1. Gli elementi vengono ripetuti
max_v, m_i = float(-inf), 0 #Combina un oggetto dati attraversabile (come una lista, una tupla o una stringa) in una sequenza di indici, Elenca i pedici dei dati e i dati allo stesso tempo for i, v in enumerate(nums): se v > max_v: max_v = v m_i = io
2. Nessun elemento ripetuto
max_v = max(numeri) m_i = numeri.indice(max_v)
2. Dicotomia
Soluzione di classe: def searchInsert(self, nums: List[int], target: int) -> int: sinistra, destra = 0, len(nums) #Utilizza l'intervallo chiuso sinistro e aperto destro [sinistra, destra) while left < right: # Apre a destra, quindi non può esserci =, l'intervallo non esiste mid = left (destra - sinistra)//2 # Previene l'overflow, //Indica la divisione intera if nums[mid] < target: # Il punto medio è inferiore al valore target Sul lato destro si possono ottenere posizioni uguali sinistra = metà 1 # Chiuso a sinistra, quindi 1 altro: destra = metà # Apri a destra, il vero punto finale destro è metà-1 return left # Al termine dell'algoritmo, è garantito che left = right e il ritorno sarà lo stesso per tutti.
4.Matplotlib
1. Sposta le coordinate su (0,0)
ascia = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('dati', 0)) ax.yaxis.set_ticks_position('sinistra') ax.spines['sinistra'].set_position(('dati', 0))
matematica
1. Calcola la media
def media(numeri): #Calcola la media s = 0,0 per num in numeri: s = s numero return s/len(numeri)
2. Calcola la varianza
def dev(numeri, media): #Calcola la varianza sdev = 0,0 per num in numeri: sdev = sdev (num - media)**2 return pow(sdev / (len(numeri)-1), 0.5)
3. Calcola la mediana
def mediana(numeri): #Calcola la mediana ordinati(numeri) dimensione = lunghezza(numeri) se dimensione % 2 == 0: med = (numeri[dimensione//2-1] numeri[dimensione//2])/2 altro: med = numeri[dimensione//2] restituire il medico
6. Il codice viene eseguito localmente
Infatti, basta definire una funzione principale, costruire un caso d'uso di input, quindi definire una variabile di soluzione e chiamare la funzione minCostClimbingStairs.
0. Abilità di scrittura del codice
1.Formatta
1. Vuoi scrivere più righe in una riga
Aggiungi un punto e virgola alla fine di ogni riga;
2. Una riga è troppo lunga e voglio avvolgerla in una nuova riga.
Basta aggiungere una barra \ alla fine di questa riga
Relativo all'algoritmo
pensiero classico
0.Problemi riscontrati frequentemente
1. Prevenire il traboccamento
1. Quando si calcola il prodotto di molti numeri, per evitare l'overflow, è possibile prendere il logaritmo del prodotto e trasformarlo sotto forma di addizione.
1.Struttura dei dati
1.Array
0.Base
1. Finché vedi che l'array fornito nella domanda dell'intervista è un array ordinato, puoi pensare se puoi utilizzare il metodo della dicotomia
2. Gli elementi dell'array sono continui nell'indirizzo di memoria Un elemento dell'array non può essere cancellato singolarmente, ma solo sovrascritto.
1. Lo stesso elemento dell'array non può essere attraversato due volte.
for (int i = 0; i < numeri.lunghezza; i ) { for (int j = i 1; j < numeri.lunghezza; j ) {
2. Problema della matrice circolare
Quando c'è un vincolo che la testa e la coda non possono essere allo stesso tempo, scomponi la matrice circolare in diversi problemi di matrice ordinaria e trova il valore massimo
3. Problema dell'intervallo dicotomico
1. Sinistra chiusa e destra chiusa [sinistra, destra]
int middle = left ((destra - sinistra) / 2);// previene l'overflow, equivalente a (sinistra destra)/2
while (sinistra <= destra) { // Se sinistra==destra, l'intervallo [sinistra, destra] è ancora valido
if (nums[medio] > target) { destra = centro - 1; // il bersaglio è nell'intervallo a sinistra, quindi [sinistra, centro - 1]
} else if (nums[middle] < target) { sinistra = centro 1; // il bersaglio è nell'intervallo giusto, quindi [centro 1, destra]
2. Chiudi a sinistra e apri a destra [sinistra, destra)
while (sinistra < destra) { // Perché quando sinistra == destra, [sinistra, destra) è uno spazio non valido
if (nums[medio] > target) { destra = centro; // il bersaglio è nell'intervallo sinistro, in [sinistra, centro)
} else if (nums[middle] < target) { sinistra = centro 1; // il bersaglio è nell'intervallo destro, in [centro 1, destra)
2. Tabella hash
0. Finché si tratta di contare il numero di occorrenze di un determinato numero/valore, utilizzare una tabella hash
1. La tabella hash contiene il numero corrispondente di un certo numero e non è essa stessa
for (int i = 0; i < numeri.lunghezza; i ) { complemento int = target - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i)
3. Elenco collegato
1. Aggiungi i numeri in due elenchi collegati
1. In caso di inversione dell'ordine: l'eventuale carry issue dell'ultima aggiunta deve essere considerata separatamente.
2. In sequenza in avanti: invertire l'elenco collegato/utilizzare la struttura dei dati dello stack per ottenere l'inversione
2. Trova la mediana di una lista ordinata concatenata singolarmente (a sinistra chiusa e a destra aperta)
Supponiamo che l'endpoint sinistro dell'elenco collegato corrente sia a sinistra, l'endpoint destro sia a destra e che la relazione di inclusione sia "chiusa a sinistra, aperta a destra". L'elenco collegato fornito è un elenco collegato unidirezionale. È molto facile accedervi elementi successivi, ma non può accedere direttamente agli elementi precedenti. Pertanto, dopo aver trovato il nodo mediano a metà dell'elenco collegato, se si imposta la relazione "sinistra chiusa, destra aperta", è possibile utilizzare direttamente (sinistra, metà) e (mid.next, destra) per rappresentare la lista corrispondente a i sottoalberi sinistro e destro Non è necessario mid.pre e l'elenco iniziale può anche essere convenientemente rappresentato da (head, null)
4.Personaggi
1. Registra se ciascun carattere appare
Set hash: Set<Carattere> occ = new HashSet<Carattere>();
5. Numeri
1. Riportare l'acquisizione per sommare due numeri a una cifra
int somma = riporto x y; int riporto = somma/10;
2. Inversione dell'intero
Per "estrarre" e "spingere" i numeri senza l'aiuto di uno stack/array ausiliario, possiamo usare la matematica, eliminare prima l'ultima cifra, quindi dividere per 10 per rimuovere l'ultima cifra, invertire il numero e continuare a moltiplicarsi dopo 10 , aggiungi l'ultima cifra eliminata e determina prima se andrà in overflow.
6.Albero
1. Trova i nodi precursori
Fai un passo a sinistra, poi continua a camminare a destra finché non puoi andare oltre
predecessore = root.sinistra; while (predecessore.destra!= null && predecessore.destra!= root) { predecessore = predecessore.destra; }
7. Raccolta
1. Rimuovi gli elementi duplicati dall'elenco
s = set(ls); lt = lista(i)
8. Tupla
1. Se non si desidera che i dati vengano modificati dal programma, convertirli in un tipo tupla
lt = tupla(ls)
2. Algoritmo classico
1.Doppio puntatore
0. Comunemente utilizzato in matrici ed elenchi collegati
1. Quando è necessario enumerare due elementi in un array, se si scopre che all'aumentare del primo elemento, il secondo elemento diminuisce, è possibile utilizzare il metodo del doppio puntatore per spostare il secondo puntatore dalla fine dell'array. Inizia l'attraversamento mentre assicurando che il secondo puntatore sia maggiore del primo puntatore, riducendo la complessità temporale dell'enumerazione da O(N^2) a O(N)
2. Quando il risultato della ricerca rientra in un determinato intervallo, i doppi puntatori vengono utilizzati per cambiare continuamente, in modo simile al meccanismo della finestra scorrevole.
2. Metodo del puntatore veloce e lento
Inizialmente, sia il puntatore fast fast che il puntatore slow slow puntano all'endpoint sinistro a sinistra dell'elenco collegato. Mentre spostiamo velocemente il puntatore veloce a destra due volte, spostiamo il puntatore lento a destra una volta finché il puntatore veloce non raggiunge il confine (cioè, il puntatore veloce raggiunge l'estremità destra o il nodo successivo del puntatore veloce è quello destro punto finale). In questo momento, l'elemento corrispondente all'indicatore lento è la mediana
3. Programmazione dinamica
1. Il numero richiesto può essere ottenuto tramite alcune operazioni tramite il numero richiesto precedente ed è possibile trovare l'equazione di trasferimento dinamico
2.Condizioni
Se un problema presenta molti sottoproblemi sovrapposti, la programmazione dinamica è la più efficace.
3. Cinque passaggi
1. Determinare il significato dell'array dp (tabella dp) e degli indici 2. Determinare la formula di ricorsione 3.Come inizializzare l'array dp 4. Determinare l'ordine di attraversamento 5. Derivazione dell'array dp con esempi
Perché determinare prima la formula di ricorsione e poi considerare l'inizializzazione? Perché in alcuni casi la formula ricorsiva determina come inizializzare l'array dp
4.Come eseguire il debug
1. Stampa l'array dp e vedi se viene dedotto secondo le tue idee.
2. Prima di scrivere il codice, assicurati di simulare la situazione specifica del trasferimento di stato sull'array dp e assicurati che il risultato finale sia quello desiderato.
5.Scorri la matrice
Quando l'equazione ricorsiva è correlata solo a pochi numeri adiacenti, è possibile utilizzare una matrice mobile per ottimizzare la complessità dello spazio su O(1)
4. Ricorsione
0. Trilogia ricorsiva
Condizione di terminazione della ricorsione, cosa fa questa ricorsione e cosa restituisce
3. Tecniche comunemente utilizzate
1. Uso intelligente degli indici degli array
1.Applicazione
L'indice di un array è un array implicitamente utile, specialmente quando si contano alcuni numeri (trattando il valore dell'array corrispondente come l'indice temp[arr[i]] del nuovo array) o per determinare se compaiono alcuni numeri interi
2.Esempi
1. Quando ti forniamo una stringa di lettere e ti chiediamo di determinare il numero di volte in cui queste lettere appaiono, possiamo usare queste lettere come pedici Durante l'attraversamento, se la lettera a viene attraversata, allora arr[a] può essere aumentata di 1. Cioè, arr[a]. Grazie a questo uso intelligente dei pedici, non abbiamo bisogno di giudicare lettera per lettera.
2. Ti vengono forniti n array di numeri interi non ordinati arr e l'intervallo di valori di questi numeri interi è compreso tra 0 e 20. È necessario ordinare questi n numeri da piccolo a grande con complessità temporale O(n). ordine e utilizzare il valore corrispondente come indice dell'array. Se questo numero è apparso in precedenza, aggiungere 1 all'array corrispondente.
2. Usa il resto con abilità
1.Applicazione
Quando si attraversa l'array, verrà eseguita una valutazione di fuori limite. Se il pedice è quasi fuori limite, lo imposteremo su 0 e lo attraverseremo nuovamente. Soprattutto in alcuni array ad anello, come le code implementate con array pos = (pos 1) % N
3. Usa abilmente i doppi puntatori
1.Applicazione
Per i puntatori doppi è particolarmente utile quando si pongono domande su elenchi collegati singolarmente.
2.Esempi
1. Determina se una lista concatenata singolarmente ha un ciclo
Imposta un puntatore lento e un puntatore veloce per attraversare l'elenco collegato. Il puntatore lento si sposta di un nodo alla volta, mentre il puntatore veloce si sposta di due nodi alla volta. Se non è presente alcun ciclo nell'elenco collegato, il puntatore veloce attraverserà per primo l'elenco. Se è presente un ciclo, lo farà il puntatore veloce incontrare il puntatore lento durante la seconda traversata.
2. Come trovare il nodo centrale dell'elenco collegato in un attraversamento
Lo stesso vale per impostare un puntatore veloce e uno lento. Quello lento si muove di un nodo alla volta, mentre quello veloce ne muove due. Quando si attraversa l'elenco collegato, una volta completata la corsa veloce del puntatore, il puntatore lento raggiunge appena il punto medio
3. Il nodo k-esimo dall'ultimo in un elenco collegato singolarmente
Imposta due puntatori, uno dei quali sposta prima k nodi. Successivamente entrambi i puntatori si muovono alla stessa velocità. Quando il primo puntatore spostato completa la traversata, il secondo puntatore si trova esattamente al kesimo nodo dal basso.
sottoargomento
4. Utilizzare abilmente le operazioni di turno
1.Applicazione
1. A volte quando eseguiamo operazioni di divisione o moltiplicazione, come n/2, n/4, n/8, possiamo utilizzare il metodo di spostamento per eseguire l'operazione. Attraverso l'operazione di spostamento, la velocità di esecuzione sarà più veloce
2. Esistono anche alcune operazioni come & (e) e | (o), che possono anche velocizzare l'operazione.
2.Esempi
1. Per determinare se un numero è dispari, sarà molto più veloce utilizzare l'operazione AND.
5. Imposta la posizione della sentinella
1.Applicazione
1. Nelle questioni correlate alle liste collegate, spesso impostiamo un puntatore head e questo puntatore head non memorizza alcun dato valido. Solo per comodità di funzionamento, possiamo chiamare questo puntatore head bit sentinella.
2. Quando si utilizza un array, è anche possibile impostare una sentinella, utilizzando arr[0] come sentinella.
2.Esempi
1. Quando vogliamo eliminare il primo nodo, se non è impostato un bit sentinella, l'operazione sarà diversa dall'operazione di eliminazione del secondo nodo. Ma abbiamo predisposto una sentinella, quindi eliminare il primo nodo ed eliminare il secondo nodo sono gli stessi nel funzionamento, senza esprimere giudizi aggiuntivi. Naturalmente lo stesso vale quando si inseriscono i nodi
2. Quando vuoi giudicare se due elementi adiacenti sono uguali, impostare una sentinella non ti preoccuperà dei problemi transfrontalieri puoi direttamente arr[i] == arr[i-1]. Non aver paura di oltrepassare il confine quando i = 0
6. Alcune ottimizzazioni relative alla ricorsione
1. Considerare la preservazione dello stato per problemi che possono essere ricorsivi.
1. Quando utilizziamo la ricorsione per risolvere un problema, è facile calcolare ripetutamente lo stesso sottoproblema. In questo momento dobbiamo considerare la conservazione dello stato per evitare calcoli ripetuti.
2.Esempi
0. Una rana può saltare 1 gradino o 2 gradini alla volta. Scopri in quanti modi la rana può saltare su una scala a n livelli.
1. Questo problema può essere facilmente risolto utilizzando la ricorsione. Supponiamo che f(n) rappresenti il numero totale di passi per n passi, allora f(n) = f(n-1) f(n - 2)
2. La condizione finale della ricorsione è quando 0 <= n <= 2, f(n) = n, è facile scrivere codice ricorsivo
3. Tuttavia, per i problemi che possono essere risolti utilizzando la ricorsione, dobbiamo considerare se ci sono molti calcoli ripetuti. Ovviamente, per la ricorsione di f(n) = f(n-1) f(n-2), ci sono molti calcoli ripetuti.
4. Questa volta dobbiamo considerare la preservazione dello Stato. Ad esempio, puoi utilizzare hashMap per salvare. Ovviamente puoi anche utilizzare un array. In questo momento puoi utilizzare gli indici dell'array come abbiamo detto sopra. Quando arr[n] = 0, significa che n non è stato calcolato. Quando arr[n] != 0, significa che f(n) è stato calcolato. A questo punto, il valore calcolato può essere restituito direttamente.
5. In questo modo l'efficienza dell'algoritmo può essere notevolmente migliorata. Alcuni chiamano questo tipo di conservazione dello stato anche il metodo memo.
2. Pensa dal basso verso l’alto
1. Per i problemi ricorsivi, di solito si procede dall'alto verso il basso finché la ricorsione non raggiunge il fondo, quindi si restituisce il valore strato per strato.
2. Tuttavia, a volte quando n è relativamente grande, come quando n = 10000, è necessario eseguire la ricorsione verso il basso di 10000 livelli fino a n <= 2 prima di restituire lentamente il risultato. Se n è troppo grande, lo spazio dello stack potrebbe non esserlo Abbastanza
3. Per questa situazione, possiamo effettivamente considerare un approccio dal basso verso l'alto.
4. Questo approccio dal basso verso l'alto è anche chiamato ricorsione
3. Vantaggi rispetto ad altre lingue
1.Array
1. I parametri sono array parziali
In Python, i parametri possono restituire direttamente parte dell'array nums[:i]. Non è necessario riprogettare un metodo per intercettare l'array in base al pedice come in Java.
2. Ottieni elementi e pedici allo stesso tempo
for i, v in enumerate(nums):
Strutture dati/algoritmi di uso comune
1. Elenco collegato
2.Impila
1. Stack monotono
1.Definizione
Uno stack in cui gli elementi nello stack aumentano o diminuiscono in modo monotono. Uno stack monotono può essere utilizzato solo nella parte superiore dello stack.
2. Natura
1. Gli elementi nello stack monotono sono monotoni.
2. Prima che gli elementi vengano aggiunti allo stack, tutti gli elementi che distruggono la monotonia dello stack verranno eliminati dalla cima dello stack.
3. Usa lo stack monotono per trovare l'elemento e attraversa a sinistra fino al primo elemento che è più piccolo di esso (stack incrementale)/trova l'elemento e attraversa a sinistra fino al primo elemento che è più grande di esso.
3. Coda
4.Albero
1.BST: albero di ricerca binaria Albero di ordinamento binario
1.Definizione
1. Le chiavi di tutti i nodi del sottoalbero sinistro sono inferiori al nodo radice
2. Le parole chiave di tutti i nodi del sottoalbero destro sono maggiori del nodo radice
3. I sottoalberi sinistro e destro sono ciascuno un albero di ordinamento binario.
L'attraversamento in ordine può ottenere una sequenza ordinata crescente
2.AVL: albero binario bilanciato
1.Definizione
1. Albero binario bilanciato: il valore assoluto della differenza di altezza tra i sottoalberi sinistro e destro di qualsiasi nodo non supera 1
2. Fattore di equilibrio: la differenza di altezza tra i sottoalberi sinistro e destro del nodo -1,0,1
3.mct: albero di copertura minimo
1.Definizione
Ogni albero che consiste solo degli archi di G e contiene tutti i vertici di G è chiamato albero di copertura di G.
5. Figura
1. Terminologia
1. Cluster (sottografo completo)
Insieme di punti: c'è un bordo che collega due punti qualsiasi.
1.1 Insieme indipendente dal punto
Insieme di punti: tra due punti qualsiasi non esiste alcun bordo
2. Grafico di Hamilton Hamilton
Un grafo non orientato va da un punto iniziale specificato a un punto finale specificato, passando per tutti gli altri nodi una sola volta. Un percorso hamiltoniano chiuso è chiamato ciclo hamiltoniano, mentre un percorso contenente tutti i vertici del grafico è chiamato percorso hamiltoniano.
6. Algoritmo
1.BFS: Prima ricerca in ampiezza
1.Definizione
Un algoritmo di attraversamento gerarchico simile a un albero binario, che dà priorità al primo nodo scoperto.
2.DFS: prima ricerca in profondità
1.Definizione
Similmente all'attraversamento preordinato di un albero, viene data priorità all'ultimo nodo scoperto.
buon senso
1.Quante operazioni al secondo?
2. Complessità temporale di algoritmi ricorsivi
Numero di ricorsioni * Numero di operazioni in ciascuna ricorsione
3. Complessità spaziale di algoritmi ricorsivi
Profondità di ricorsione * complessità spaziale di ciascuna ricorsione
punti di conoscenza labuladuo
0.Serie da leggere
1. Pensiero quadro per l'apprendimento di algoritmi e la risoluzione di domande
1. Metodo di archiviazione della struttura dei dati
1. Esistono solo due tipi: array (archiviazione sequenziale) e elenco collegato (archiviazione collegata)
2. Esistono anche varie strutture di dati come tabelle hash, stack, code, heap, alberi, grafici, ecc., che appartengono tutte alla "sovrastruttura", mentre gli array e gli elenchi collegati sono la "base strutturale". quelle diverse strutture di dati. Le loro origini sono operazioni speciali su elenchi o array collegati e le API sono semplicemente diverse.
3. Introduzione alle varie strutture
1. Le due strutture dati "queue" e "stack" possono essere implementate utilizzando elenchi concatenati o array. Se lo implementi con un array, devi affrontare il problema dell'espansione e della contrazione; se lo implementi con una lista concatenata, non hai questo problema, ma hai bisogno di più spazio di memoria per memorizzare i puntatori dei nodi.
2. Due metodi di rappresentazione del "grafico", l'elenco di adiacenza è un elenco collegato e la matrice di adiacenza è un array bidimensionale. La matrice di adiacenza determina rapidamente la connettività e può eseguire operazioni sulla matrice per risolvere alcuni problemi, ma richiede molto spazio se il grafico è scarso. Le liste di adiacenza risparmiano spazio, ma molte operazioni non sono sicuramente efficienti quanto le matrici di adiacenza.
3. La "tabella hash" mappa le chiavi su un array di grandi dimensioni tramite una funzione hash. E come metodo per risolvere i conflitti di hash, il metodo zip richiede caratteristiche di elenco concatenato, che è semplice da utilizzare, ma richiede spazio aggiuntivo per memorizzare i puntatori; il metodo di sondaggio lineare richiede caratteristiche di array per facilitare l'indirizzamento continuo e non richiede spazio di archiviazione per i puntatori , ma l'operazione è un po' complicata
4. L'"albero", implementato con un array, è un "heap", poiché l'"heap" è un albero binario completo. L'utilizzo di un array per l'archiviazione non richiede puntatori ai nodi e l'operazione è relativamente semplice utilizzando un elenco collegato implementarlo è un "albero" molto comune, perché non è necessariamente un albero binario completo, quindi non è adatto per l'archiviazione di array. Per questo motivo, sono stati derivati vari progetti ingegnosi basati su questa struttura ad "albero" di elenchi concatenati, come alberi di ricerca binari, alberi AVL, alberi rosso-neri, alberi a intervalli, alberi B, ecc., per affrontare diversi problemi.
4. Vantaggi e svantaggi
1. Poiché gli array sono compatti e di archiviazione continua, è possibile accedervi in modo casuale, gli elementi corrispondenti possono essere trovati rapidamente tramite gli indici e lo spazio di archiviazione viene relativamente risparmiato. Ma a causa della memorizzazione continua, lo spazio di memoria deve essere allocato in una sola volta. Pertanto, se l'array vuole espandersi, deve riallocare uno spazio più grande e quindi copiare lì tutti i dati se si desidera Durante l'inserimento e l'eliminazione al centro dell'array, tutti i dati successivi devono essere spostati ogni volta per mantenere la continuità. La complessità temporale è O(N).
2. Poiché gli elementi di un elenco collegato non sono continui, ma si basano su puntatori per puntare alla posizione dell'elemento successivo, non vi è alcun problema di espansione dell'array se si conosce il predecessore e il successore di un determinato elemento, è possibile eliminarlo l'elemento o inserire un nuovo elemento utilizzando il puntatore Complessità temporale O(1). Tuttavia, poiché lo spazio di archiviazione non è continuo, non è possibile calcolare l'indirizzo dell'elemento corrispondente in base a un indice, quindi l'accesso casuale non è possibile e poiché ciascun elemento deve memorizzare un puntatore alla posizione dell'elemento precedente e precedente, esso consumerà relativamente più spazio di archiviazione.
2. Operazioni fondamentali delle strutture dati
1. L'operazione di base non è altro che un accesso trasversale. Per essere più specifici, è: aggiungere, eliminare, controllare e modificare
2. Dal livello più alto, ci sono solo due forme di attraversamento e accesso a varie strutture dati: lineare e non lineare.
3. Lineare è rappresentato dall'iterazione for/ while e non lineare è rappresentato dalla ricorsione.
4. Alcune traversate tipiche
1.Array
void traverse(int[] arr) { for (int i = 0; i < arr.lunghezza; i ) { // Itera su arr[i] } }
2. Elenco collegato
/* Nodo base di liste collegate singolarmente */ classe ElencoNodo { valore intero; ListNode successivo; } traversata del vuoto(testa ListNode) { for (ListNode p = testa; p != null; p = p.successivo) { //Accedi iterativamente a p.val } } traversata del vuoto(testa ListNode) { // Accede ricorsivamente a head.val traversa(head.next) }
3. Albero binario
/* Nodo base dell'albero binario */ classeAlberoNodo { valore intero; TreeNode sinistra, destra; } attraversamento vuoto(radice TreeNode) { traversa(root.left) traversa(root.right) }
4.Albero N-ario
/* Nodo base dell'albero N-ario */ classeAlberoNodo { valore intero; TreeNode[] figli; } attraversamento vuoto(radice TreeNode) { per (Figlio TreeNode: root.children) traversa(bambino); }
5. Il cosiddetto quadro è una routine. Indipendentemente da aggiunte, eliminazioni o modifiche, questi codici sono una struttura che non può mai essere separata. È possibile utilizzare questa struttura come struttura e aggiungere semplicemente codici al framework in base a problemi specifici.
3. Guida alla scrittura delle domande sugli algoritmi
1. Spazzola prima l'albero binario, spazzola prima l'albero binario, spazzola prima l'albero binario!
2. Gli alberi binari sono i più facili da coltivare nel pensiero quadro e la maggior parte delle tecniche algoritmiche sono essenzialmente problemi di attraversamento degli alberi.
3. Non sottovalutare queste poche righe di codice non funzionante Quasi tutti i problemi dell'albero binario possono essere risolti utilizzando questo framework.
attraversamento vuoto(radice TreeNode) { // Attraversamento del preordine traversa(root.left) // Attraversamento in ordine traversa(root.right) // Attraversamento postordine }
4. Se non sai come iniziare o hai paura delle domande, potresti anche iniziare con l'albero binario. Le prime 10 domande potrebbero essere un po' scomode, fai altre 20 domande basate sul quadro, e forse tu avrà una certa comprensione; finisci l'intero argomento prima di farlo Se guardi indietro all'argomento delle regole di divisione e conquista, scoprirai che qualsiasi problema che coinvolge la ricorsione è un problema di albero.
5. Cosa devo fare se non riesco a comprendere così tanti codici? Estraendo direttamente il framework, puoi vedere l'idea centrale: infatti, molti problemi di programmazione dinamica implicano l'attraversamento di un albero, se hai familiarità con le operazioni di attraversamento degli alberi, saprai almeno come convertire le idee in codice, e lo saprai anche tu sapere come estrarne gli altri. L'idea centrale della soluzione
2. Quadro di routine per la risoluzione dei problemi di programmazione dinamica
1.Concetti di base
1.Modulo
La forma generale del problema di programmazione dinamica consiste nel trovare il valore ottimale. La programmazione dinamica è in realtà un metodo di ottimizzazione nella ricerca operativa, ma è più comunemente utilizzata nei problemi informatici. Ad esempio, ti chiede di trovare la sottosequenza crescente più lunga e la distanza di modifica minima.
2. Questioni fondamentali
Il problema principale è l’esaurimento. Poiché richiediamo il valore migliore, dobbiamo enumerare in modo esaustivo tutte le possibili risposte e quindi trovare tra queste il valore migliore.
3.Tre elementi
1. Sottoproblemi sovrapposti
0. Esistono "sottoproblemi sovrapposti" in questo tipo di problema. Se il problema è la forza bruta, l'efficienza sarà estremamente inefficiente. Pertanto, è necessario un "memo" o una "tabella DP" per ottimizzare il processo esaustivo ed evitarlo calcoli inutili.
2. Sottostruttura ottimale
0. Il problema di programmazione dinamica deve avere una "sottostruttura ottimale", in modo che il valore ottimo del problema originale possa essere ottenuto attraverso il valore ottimo del sottoproblema.
3. Equazione di transizione di stato
0. I problemi possono essere in continua evoluzione e non è facile enumerare in modo esaustivo tutte le soluzioni fattibili. Solo elencando la corretta "equazione di transizione di stato" possiamo enumerarle correttamente in modo esaustivo.
1. Quadro di pensiero
Chiarire il caso base -> chiarire lo "stato" -> chiarire la "selezione" -> definire il significato dell'array/funzione dp
#Inizializza il caso base dp[0][0][...] = base # Esegue il trasferimento di stato per lo stato 1 in tutti i valori dello stato 1: per lo stato 2 in tutti i valori dello stato 2: per... dp[stato 1][stato 2][...] = trova il valore massimo (seleziona 1, seleziona 2...)
2. Sequenza di Fibonacci
1. Ricorsione violenta
1.Codice
int fib(int N) { if (N == 1 || N == 2) restituisce 1; restituire fib(N - 1) fib(N - 2); }
2. Albero ricorsivo
1. Ogni volta che incontri un problema che richiede la ricorsione, è meglio disegnare un albero di ricorsione. Questo ti sarà di grande aiuto per analizzare la complessità dell'algoritmo e trovare le ragioni dell'inefficienza dell'algoritmo.
2.Immagini
3. Complessità temporale di algoritmi ricorsivi
0. Moltiplicare il numero di sottoproblemi per il tempo necessario per risolvere un sottoproblema
1. Innanzitutto calcolare il numero di sottoproblemi, ovvero il numero totale di nodi nell'albero di ricorsione. Ovviamente il numero totale di nodi dell'albero binario è esponenziale, quindi il numero di sottoproblemi è O(2^n)
2. Quindi calcola il tempo per risolvere un sottoproblema. In questo algoritmo non esiste un ciclo, solo f(n - 1) f(n - 2) un'operazione di addizione, il tempo è O(1).
3. La complessità temporale di questo algoritmo è la moltiplicazione dei due, ovvero O(2^n), livello esponenziale, esplosione
4. Osservando l'albero di ricorsione, è ovvio che si trova la ragione dell'inefficienza dell'algoritmo: ci sono molti calcoli ripetuti.
5. Questa è la prima proprietà dei problemi di programmazione dinamica: sottoproblemi sovrapposti. Successivamente, proveremo a risolvere questo problema
2. Soluzione ricorsiva con memo
1. Poiché il motivo che richiede tempo sono i calcoli ripetuti, possiamo creare un "memo". Non tornare indietro dopo aver calcolato la risposta a una determinata sotto-domanda, annotarla nel "memo" prima di tornare ogni volta incontri una sotto-domanda, controlla prima il problema in "Memo". Se trovi che il problema è stato risolto in precedenza, prendi semplicemente la risposta e usala invece di perdere tempo a fare calcoli.
2. Generalmente, come "memo" viene utilizzato un array. Naturalmente è anche possibile utilizzare una tabella hash (dizionario).
3.Codice
int fib(int N) { se (N < 1) restituisce 0; //Il promemoria è tutto inizializzato a 0 vettore<int>memo(N 1, 0); // Esegue la ricorsione con memo aiutante per la restituzione(memo, N); } int helper(vettore<int>& memo, int n) { // caso base if (n == 1 || n == 2) restituisce 1; //Già calcolato if (memo[n] != 0) return memo[n]; memo[n] = helper(memo, n - 1) helper(memo, n - 2); promemoria di ritorno[n]; }
4. Albero ricorsivo
Infatti, l'algoritmo ricorsivo con "memo" trasforma un albero ricorsivo con enorme ridondanza in un grafo ricorsivo senza ridondanza attraverso il "pruning", che riduce notevolmente il numero di sottoproblemi (ovvero il numero di nodi ricorsivi nel grafo)
5. Complessità
Non vi è alcun calcolo ridondante in questo algoritmo, il numero di sottoproblemi è O(n) e la complessità temporale di questo algoritmo è O(n). Rispetto agli algoritmi violenti, si tratta di un attacco di riduzione della dimensione
6.Confronto con la programmazione dinamica
L'efficienza della soluzione ricorsiva con memo è la stessa della soluzione di programmazione dinamica iterativa, tuttavia questo metodo è chiamato "top-down" e la programmazione dinamica è chiamata "bottom-up".
7. Dall'alto verso il basso
L'albero di ricorsione (o immagine) disegnato si estende dall'alto verso il basso, partendo da un problema originale più ampio come f(20), e gradualmente scompone la scala verso il basso fino a f(1) e f(2). Questi due casi base restituiscono quindi il risposte strato dopo strato.
8. Dal basso verso l'alto
Basta iniziare dal basso, il problema più semplice e più piccolo, con dimensioni f(1) e f(2), e spingersi verso l'alto fino a raggiungere la risposta desiderata f(20). Questa è l'idea della programmazione dinamica. ed è per questo che la programmazione dinamica Planning generalmente si allontana dalla ricorsione e completa invece i calcoli tramite l'iterazione del ciclo
3.Soluzione iterativa dell'array dp
1. Pensieri
Prendendo spunto dal "memo" del passaggio precedente, possiamo separare questo "memo" in una tabella, chiamiamola tabella DP. Non sarebbe carino completare i calcoli "dal basso verso l'alto" su questa tabella?
2.Codice
int fib(int N) { vettore<int> dp(N 1, 0); // caso base dp[1] = dp[2] = 1; for (int i = 3; i <= N; i ) dp[i] = dp[i - 1] dp[i - 2]; ritorna dp[N]; }
3. Equazione di transizione di stato
1. È il fulcro della risoluzione dei problemi. Ed è facile scoprire che, in effetti, l’equazione di transizione di stato rappresenta direttamente la soluzione della forza bruta
2. Non disprezzare le soluzioni violente La cosa più difficile riguardo ai problemi di programmazione dinamica è scrivere questa soluzione violenta, cioè l’equazione di transizione dello stato. Finché scrivi una soluzione di forza bruta, il metodo di ottimizzazione non è altro che l'utilizzo di un memo o di una tabella DP, non c'è alcun mistero.
4. Compressione dello stato
1. Lo stato corrente è correlato solo ai due stati precedenti. In effetti, non è necessaria una tabella DP così lunga per memorizzare tutti gli stati. È sufficiente trovare un modo per memorizzare i due stati precedenti. Pertanto, può essere ulteriormente ottimizzato per ridurre la complessità spaziale a O(1)
2.Codice
int fib(int n) { se (n == 2 || n == 1) ritorno 1; int precedente = 1, corrente = 1; for (int i = 3; i <= n; i ) { somma int = prev curr; prec = corrente; valuta = somma; } valuta di ritorno; }
3. Questa tecnica è la cosiddetta "compressione dello stato". Se scopriamo che ogni trasferimento di stato richiede solo una parte della tabella DP, allora possiamo provare a utilizzare la compressione dello stato per ridurre la dimensione della tabella DP e registrare solo quella. dati necessari In generale, è Comprimere una tabella DP bidimensionale in una dimensione, ovvero comprimere la complessità dello spazio da O(n^2) a O(n).
3. Il problema della riscossione del resto
0.Domanda
Ti vengono date k monete con valore nominale di c1, c2...ck. La quantità di ciascuna moneta è illimitata. Quindi ti viene data una somma totale di denaro. Ti chiedo quante monete ti servono almeno per recuperare tale importo. Se è impossibile recuperare l'importo, l'algoritmo restituisce -1
1. Ricorsione violenta
1. Innanzitutto, questo problema è un problema di programmazione dinamica perché ha una "sottostruttura ottimale". Per rispettare la "sottostruttura ottimale", i sottoproblemi devono essere indipendenti l'uno dall'altro.
2. Tornando al problema della riscossione del resto, perché si dice che sia in linea con la sottostruttura ottimale? Ad esempio, se vuoi trovare il numero minimo di monete quando importo = 11 (domanda originale), se conosci il numero minimo di monete quando importo = 10 (sottodomanda), devi solo aggiungere uno alla risposta a la sotto-domanda (scegli un'altra moneta da 1 valore nominale) è la risposta alla domanda originale. Poiché il numero di monete è illimitato, non esiste alcun controllo reciproco tra i sottoproblemi ed essi sono indipendenti l’uno dall’altro.
3. Quattro passi principali
1. Determinare il caso base Questo è molto semplice Ovviamente, quando l'importo target è 0, l'algoritmo restituisce 0.
2. Determinare lo "stato", cioè le variabili che cambieranno nel problema originale e nei sottoproblemi. Poiché il numero di monete è infinito e anche la denominazione della moneta è data dalla domanda, solo l'importo target continuerà ad avvicinarsi al caso base, quindi l'unico "stato" è l'importo dell'importo target
3. Determinare la "scelta", ovvero il comportamento che provoca il cambiamento dello "stato". Perché l'importo target cambia? Perché stai scegliendo le monete Ogni volta che scegli una moneta, equivale a ridurre l'importo target. Quindi il valore nominale di tutte le monete è la tua "scelta"
4. Chiarire la definizione di funzione/array dp. Ciò di cui stiamo parlando qui è una soluzione top-down, quindi ci sarà una funzione dp ricorsiva. In generale, il parametro della funzione è l'importo che cambierà durante la transizione di stato, che è lo "stato" menzionato sopra; il valore restituito dalla funzione è la quantità che la domanda ci richiede di calcolare. Per quanto riguarda questa domanda, esiste un solo stato, che è "importo target". La domanda richiede di calcolare il numero minimo di monete necessarie per raggiungere l'importo target. Quindi possiamo definire la funzione dp in questo modo: La definizione di dp(n): inserisci un importo target n e restituisci il numero minimo di monete per raggiungere l'importo target n
4. Pseudocodice
def cambiomoneta(monete: Lista[int], importo: int): # Definizione: per recuperare l'importo n sono necessarie almeno dp(n) monete def dp(n): # Fai una scelta e scegli il risultato che richiede meno monete. per moneta in monete: res = min(res, 1 dp(n - moneta)) ritorno ris #Il risultato finale richiesto dalla domanda è dp(importo) ritorno dp(importo)
5.Codice
def cambiomoneta(monete: Lista[int], importo: int): def dp(n): # caso base se n == 0: restituisce 0 se n < 0: restituisce -1 # Trova il valore minimo, quindi inizializzalo su infinito positivo res = float('INF') per moneta in monete: sottoproblema = dp(n - moneta) # Il sottoproblema non ha soluzione, saltalo se sottoproblema == -1: continua res = min(res, 1 sottoproblema) restituisce res se res != float('INF') altrimenti -1 ritorno dp(importo)
6.Equazione di transizione di stato
7. Albero ricorsivo
8. Complessità
Il numero totale di sottoproblemi è il numero di nodi dell'albero ricorsivo. Questo è difficile da vedere. È O(n^k). Ogni sottoproblema contiene un ciclo for con complessità O(k). Quindi la complessità temporale totale è O(k * n^k), livello esponenziale
2. Ricorsione con promemoria
1.Codice
def cambiomoneta(monete: Lista[int], importo: int): #promemoria promemoria = dict() def dp(n): # Controlla il promemoria per evitare il doppio conteggio if n nel memo: return memo[n] # caso base se n == 0: restituisce 0 se n < 0: restituisce -1 res = float('INF') per moneta in monete: sottoproblema = dp(n - moneta) se sottoproblema == -1: continua res = min(res, 1 sottoproblema) # Registra nel promemoria memo[n] = res if res != float('INF') else -1 promemoria di reso[n] ritorno dp(importo)
2. Complessità
Ovviamente, il "memo" riduce notevolmente il numero di sottoproblemi ed elimina completamente la ridondanza dei sottoproblemi, quindi il numero totale di sottoproblemi non supererà la quantità di denaro n, cioè il numero di sottoproblemi è O(n). Il tempo necessario per affrontare un sottoproblema rimane invariato ed è ancora O(k), quindi la complessità temporale totale è O(kn)
3.Soluzione iterativa dell'array dp
1.Definizione di array dp
La funzione dp si riflette nei parametri della funzione, mentre l'array dp si riflette nell'indice dell'array: La definizione di array dp: quando l'importo target è i, sono necessarie almeno monete dp[i] per raccoglierlo
2.Codice
int coinChange(vettore<int>& monete, int importo) { //La dimensione dell'array è pari a 1 e anche il valore iniziale è pari a 1 vettore<int> dp(importo 1, importo 1); // caso base dp[0] = 0; // Il ciclo for esterno attraversa tutti i valori di tutti gli stati for (int i = 0; i < dp.size(); i ) { //Il ciclo for interno trova il valore minimo di tutte le scelte for (int moneta: monete) { // Il sottoproblema non ha soluzione, saltalo se (i - moneta < 0) continua; dp[i] = min(dp[i], 1 dp[i - moneta]); } } return (dp[importo] == importo 1) ? -1 : dp[importo]; }
3. Processo
4.Dettagli
Perché l'array dp è inizializzato sull'importo 1? Poiché il numero di monete che compongono l'importo può essere uguale al massimo all'importo (vengono utilizzate tutte le monete con un valore nominale di 1 yuan), quindi inizializzarlo sull'importo 1 è equivale a inizializzarlo all'infinito positivo, il che facilita il successivo valore minimo
4. Riepilogo
1. In realtà non esistono trucchi magici per i computer per risolvere i problemi. La sua unica soluzione è esaurire in modo esaustivo tutte le possibilità. La progettazione di algoritmi non è altro che pensare prima a "come fare in modo esaustivo" e poi perseguire "come fare in modo esaustivo in modo esaustivo"
2. Elencare l'equazione di trasferimento dinamico significa risolvere il problema di "come farlo in modo esaustivo". Il motivo per cui è difficile è che, in primo luogo, molti calcoli esaustivi devono essere implementati in modo ricorsivo e, in secondo luogo, perché lo spazio di soluzione di alcuni problemi è complesso e non è facile completare il calcolo esaustivo.
3. I promemoria e le tabelle DP mirano a perseguire "come esaustivamente esaustivo". L’idea di scambiare spazio con tempo è l’unico modo per ridurre la complessità del tempo. Inoltre, lasciami chiedere, quali altri trucchi puoi fare?
Algoritmo Likou
0. Pensiero inverso
6.Trasformazione a zigzag
1. Ordina per riga
0.Titolo
Disporre una determinata stringa a forma di Z dall'alto verso il basso e da sinistra a destra in base al numero di righe specificato. Successivamente, l'output deve essere letto riga per riga da sinistra a destra per generare una nuova stringa.
1. Idee
Scorri su ss da sinistra a destra, aggiungendo ogni carattere alla riga appropriata. La riga appropriata può essere tracciata utilizzando la riga corrente e le variabili di direzione corrente. La direzione corrente cambierà solo quando ci spostiamo verso l'alto o verso il basso verso la riga inferiore.
2.Codice
soluzione di classe { public String convert(String s, int numRighe) { if (numRighe == 1) restituisce s; List<StringBuilder> rows = new ArrayList<>(); //Memorizza i caratteri di ogni riga dopo la trasformazione for (int i = 0; i < Math.min(numRighe, s.lunghezza()); i ) righe.add(nuovo StringBuilder()); int curRow = 0; booleano goingDown = false; for (char c : s.toCharArray()) { //Attraversa s direttamente, convertilo in un array di caratteri e assegnalo a c uno per uno righe.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown; curRow = andandoGiù 1: -1; } StringBuilder ret = new StringBuilder(); for (StringBuilder riga: righe) ret.append(riga); return ret.toString(); } }
3. Complessità
Tempo O(n), dove n=len(s), spazio O(n)
1. Algoritmo puro
7. Inversione di numeri interi
1. Domanda
Dato un intero con segno a 32 bit, è necessario invertire ogni bit dell'intero. Supponiamo che il nostro ambiente possa memorizzare solo interi con segno a 32 bit. Se l'intero va in overflow dopo l'inversione, verrà restituito 0.
2. Idee
Per "estrarre" e "spingere" i numeri senza l'aiuto di uno stack/array ausiliario, possiamo usare la matematica, eliminare prima l'ultima cifra, quindi dividere per 10 per eliminare l'ultima cifra, invertire il numero e continuare a moltiplicarsi Dopo 10, aggiungi l'ultima cifra eliminata e determina prima se traboccherà.
3.Codice
soluzione di classe { public int reverse(int x) { int giro = 0; mentre (x!= 0) { int pop = x % 10; //Elimina l'ultima cifra di x x /= 10; //Rimuove l'ultima cifra di x //Quando int occupa 32 bit, l'intervallo di valori è -2147483648~2147483647, quindi pop>7 o pop<-8 if (rev > Intero.MAX_VALUE/10 || (rev == Intero.MAX_VALUE / 10 && pop > Intero.MAX_VALUE % 10)) return 0; if (rev < Intero.MIN_VALUE/10 || (rev == Intero.MIN_VALUE / 10 && pop < Intero.MIN_VALUE % 10)) return 0; rev = rev * 10 pop; //Dopo che il tutto avanza di una cifra, aggiungi l'ultima cifra } ritorno giro; } }
4. Complessità
Tempo: O(log(x)), spazio O(1)
2.Serie
0.frequenza
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.Cerca la posizione di inserimento
1. Dicotomia
0.Titolo
Dato un array ordinato e un valore di destinazione, trova il valore di destinazione nell'array e restituisce il suo indice. Se il valore di destinazione non esiste nell'array, restituisce la posizione in cui verrà inserito in sequenza. Si può presupporre che non siano presenti elementi duplicati nell'array
1. Pensieri
1. Il valore restituito è la posizione del primo valore all'interno di [first, last) che non è inferiore a value. Nota che non è possibile ottenere il limite destro dell'intervallo iniziale, ovvero quello sinistro è chiuso e quello destro è aperto , che determina anche i cambiamenti di sinistra e destra ( left=mid 1,right=mid)
2. Mid=first (last-first)/2 è scritto in questo modo per evitare che i numeri grandi trabocchino
3. Infine, puoi anche restituire per ultimo. I due codici si sovrappongono anche se l'intervallo è vuoto, la risposta non esiste, ci sono elementi ripetuti e i limiti superiore/inferiore della ricerca sono aperti/. chiuso e la regolazione della posizione di più o meno 1 viene visualizzata solo una volta
4. Cosa succede se nums[mid] < valore viene modificato in nums[mid] <= valore? Non è difficile pensarci. Il risultato convergerà alla prima posizione maggiore del valore Perché quando la condizione if è soddisfatta quando sono uguali, l'estremità sinistra dell'intervallo cambierà comunque e la posizione uguale non lo sarà ottenuto.
5. L'array sopra è crescente Se è decrescente, come dovremmo modificarlo? Infatti, dobbiamo solo invertire la condizione e cambiarla in nums[mid]>=value Il risultato convergerà al primo no maggiore della posizione di valore
6. Tornando a questa domanda, trova il valore di destinazione nell'array e restituisci il suo indice. Se il valore di destinazione non esiste nell'array, restituisci la posizione in cui verrà inserito in ordine. Quindi, utilizziamo il modello per trovare un valore che non sia inferiore al valore target e che sia il primo nell'array. Quindi, per soddisfare i requisiti della domanda, dobbiamo solo restituire l'indice del valore calcolato, ovvero. primo o ultimo
2.Codice
Soluzione di classe: def searchInsert(self, nums: List[int], target: int) -> int: sinistra, destra = 0, len(nums) #Utilizza l'intervallo chiuso sinistro e aperto destro [sinistra, destra) while left < right: # Apre a destra, quindi non può esserci =, l'intervallo non esiste mid = left (destra - sinistra)//2 # Previene l'overflow, //Indica la divisione intera if nums[mid] < target: # Il punto medio è inferiore al valore target Sul lato destro si possono ottenere posizioni uguali sinistra = metà 1 # Chiuso a sinistra, quindi 1 altro: destra = metà # Apri a destra, il vero punto finale destro è metà-1 return left # Al termine dell'algoritmo, è garantito che left = right e il ritorno sarà lo stesso per tutti.
3. Complessità
Complessità temporale: O(logn), dove n è la lunghezza dell'array. La complessità temporale richiesta per la ricerca binaria è O(logn)
Complessità spaziale: O(1). Abbiamo solo bisogno di spazio costante per memorizzare diverse variabili
1.La somma di due numeri
1. Legge sulla violenza
0.Titolo
Dato un array di numeri interi e un valore target target, trova i due numeri interi nell'array la cui somma è il valore target e restituisci i relativi indici dell'array. Supponiamo che ogni input corrisponda a una sola risposta. Tuttavia, lo stesso elemento dell'array non può essere utilizzato due volte
soluzione di classe { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < numeri.lunghezza; i ) { for (int j = i 1; j < numeri.lunghezza; j ) { if (nums[j] == target - nums[i]) { restituisce nuovo int[] { i, j }; } } } lancia una nuova IllegalArgumentException("Nessuna soluzione a due somme"); } }
Tempo O(n^2), spazio O(1)
2. Tabella hash a doppio passaggio
soluzione di classe { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < numeri.lunghezza; i ) { map.put(nums[i], i); } for (int i = 0; i < numeri.lunghezza; i ) { complemento int = target - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i) { return new int[] { i, map.get(complemento) }; } } lancia una nuova IllegalArgumentException("Nessuna soluzione a due somme"); } }
Sono state utilizzate due iterazioni. Nella prima iterazione aggiungiamo alla tabella il valore di ciascun elemento e il relativo indice. Quindi, nella seconda iterazione, controlleremo se l'elemento target (target-nums[i]) corrispondente a ciascun elemento esiste nella tabella. Nota che l'elemento target non può essere lo stesso nums[i]! Questo metodo riduce la complessità temporale a O(n) e aumenta la complessità spaziale a O(n).
Tempo O(n), spazio O(n)
3. Passa la tabella hash
soluzione di classe { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < numeri.lunghezza; i ) { complemento int = target - nums[i]; if (map.containsKey(complemento)) { return new int[] { map.get(complemento), i }; } map.put(nums[i], i); } lancia una nuova IllegalArgumentException("Nessuna soluzione a due somme"); } }
C'è una piccola ottimizzazione per il metodo due, che consiste nell'unire le due iterazioni in una e creare una tabella hash. Per ogni x, chiediamo prima se target - x esiste nella tabella hash, quindi inseriamo x nell'hash In the tabella, puoi garantire che x non corrisponderà a te stesso.
4. Riepilogo
Alcune persone hanno sollevato obiezioni all'algoritmo utilizzando la tabella hash. C'è un ciclo in contieneKey di HashMap, che è ancora O(n^2) Map aumenta anche la complessità dello spazio e il sovraccarico più efficace, ma anche questo punto di vista presenta alcuni problemi: il ciclo nel contieneKey entrerà solo se c'è un conflitto Allo stesso tempo, se i conflitti sono frequenti, verrà utilizzato il metodo getTreeNode per ottenere il valore. getTreeNode ottiene il valore da un albero rosso-nero e la complessità temporale è Al massimo O(logN).
167. Somma di due numeri II matrice ordinata
1. Dicotomia
0.Titolo
Dato un array ordinato in ordine crescente, trova due numeri tali che la loro somma sia uguale al numero di destinazione. La funzione dovrebbe restituire i due valori dell'indice indice1 e indice2, dove indice1 deve essere inferiore a indice2 I valori dell'indice restituiti (indice1 e indice2) non sono in base zero. Puoi presumere che ogni input corrisponda a una risposta univoca e non puoi riutilizzare gli stessi elementi
1.Codice
soluzione di classe { public int[] twoSum(int[] numeri, int target) { for (int i = 0; i < numeri.lunghezza; i) { int basso = i 1, alto = numeri.lunghezza - 1; mentre (basso <= alto) { int medio = (alto - basso) / 2 basso; if (numeri[mid] == target - numeri[i]) { restituisce nuovo int[]{i 1, metà 1}; } else if (numeri[mid] > target - numeri[i]) { alto = medio - 1; } altro { basso = medio 1; } } } restituisce nuovo int[]{-1, -1}; } }
2. Complessità
Tempo: O(nlogn), spazio O(1)
1.Doppio puntatore
1.Codice
soluzione di classe { public int[] twoSum(int[] numeri, int target) { int basso = 0, alto = numeri.lunghezza - 1; mentre (basso < alto) { int somma = numeri[basso] numeri[alto]; if (somma == obiettivo) { return new int[]{low 1, high 1}; } altrimenti se (somma < destinazione) { Basso; } altro { --alto; } } restituisce nuovo int[]{-1, -1}; } }
2. Complessità
Tempo: O(n), Spazio: O(1)
170.Somma di due numeri III Progettazione della struttura dei dati VIP
653. La somma di due numeri IV viene inserita nella BST
1. Ricorsione HashSet
0.Titolo
Dato un albero di ricerca binario e un risultato di destinazione, restituisce vero se ci sono due elementi nel BST e la loro somma è uguale al risultato di destinazione fornito
1. Pensieri
Attraversa i suoi due sottoalberi (sottoalbero sinistro e sottoalbero destro) in ciascun nodo dell'albero per trovare un altro numero corrispondente. Durante il processo di attraversamento, inserisci il valore di ciascun nodo in un set
2.Codice
soluzione di classe pubblica { public booleano findTarget(TreeNode root, int k) { Set < Intero > set = new HashSet(); return trova(root, k, set); } booleano pubblico find(TreeNode root, int k, Set < Integer > set) { se (radice == nullo) restituire falso; if (set.contains(k - root.val)) restituisce vero; set.add(root.val); return trova(root.sinistra, k, set) || trova(root.destra, k, set); } }
Poiché la raccolta di insiemi deve essere aggiornata ogni volta che viene attraversata, dovrebbe essere inserita nella nuova funzione come parametro, in modo da aggiungere una nuova funzione.
3. Complessità
Tempo: O(n), spazio O(n)
2.HashSet BFS
1. Pensieri
Attraversa un albero binario utilizzando la ricerca in ampiezza
2.Codice
soluzione di classe pubblica { public booleano findTarget(TreeNode root, int k) { Set < Intero > set = new HashSet(); Coda <TreeNode> coda = new LinkedList(); coda.add(root); while (!queue.isEmpty()) { if (queue.peek() != null) { Nodo TreeNode = coda.remove(); if (set.contains(k - nodo.val)) restituisce vero; set.add(nodo.val); coda.add(nodo.destra); coda.add(nodo.sinistra); } altro coda.remove(); } restituire falso; } }
3. Complessità
Tempo: O(n), spazio O(n)
3. Proprietà dei puntatori doppi BST
1. Pensieri
I risultati dell'attraversamento ordinato della BST sono disposti in ordine crescente. Attraversa il BST specificato in ordine e memorizza i risultati dell'attraversamento nell'elenco Dopo aver completato l'attraversamento, utilizza due puntatori l e r come indice di testa e indice di coda dell'elenco.
2.Codice
soluzione di classe pubblica { public booleano findTarget(TreeNode root, int k) { Lista < intero > lista = new ArrayList(); ordine(radice, lista); int l = 0, r = lista.dimensione() - 1; mentre (l < r) { int somma = list.get(l) lista.get(r); se (somma == k) restituisce vero; se (somma < k) l; altro R--; } restituire falso; } public void inorder(TreeNode root, List < Integer > list) { se (radice == nullo) ritorno; inorder(root.left, lista); lista.add(root.val); inorder(root.right, elenco); } }
3. Complessità
Tempo: O(n), spazio O(n)
1214. Trova la somma vip di due alberi di ricerca binari
560.Il numero di sottoarray consecutivi la cui somma è k
1. Enumerazione
0.Titolo
Dato un array di numeri interi e un intero k, è necessario trovare il numero di sottoarray consecutivi nell'array la cui somma è k
1. Idee
Possiamo enumerare tutti i pedici j in [0..i] per determinare se soddisfano le condizioni. Alcuni lettori potrebbero pensare che, supponendo che abbiamo determinato l'inizio e la fine del sottoarray, abbiamo ancora bisogno di una complessità temporale O(n) per attraversarlo. il sottoarray. Per sommare l'array, la complessità raggiungerà O(n^3) e tutti i casi di test non verranno superati. Ma se conosciamo la somma dei sottoarray [j,i], possiamo dedurre la somma di [j-1,i] in O(1), quindi questa parte dell'attraversamento e della somma non è necessaria l'enumerazione può già trovare la somma dei sottoarray di [j,i] in O(1)
2.Codice
soluzione di classe pubblica { public int suarraySum(int[] numeri, int k) { conteggio intero = 0; for (int inizio = 0; inizio < num.lunghezza; inizio) { somma intera = 0; //Inizia da questo numero e trova la somma delle sottosequenze in sequenza. for (int fine = inizio; fine >= 0; --end) { somma = numeri[fine]; se (somma == k) { contare ; } } } conteggio dei resi; } }
3. Complessità
Tempo: O(n^2), Spazio: O(1)
2. Prefisso e tabella hash
1. Idee
1. Il collo di bottiglia del metodo uno è che per ogni i dobbiamo enumerare tutti i j per determinare se soddisfa le condizioni.
2. Definiamo pre[i] come la somma di tutti i numeri in [0..i], quindi pre[i] può essere derivato ricorsivamente da pre[i−1], ovvero: pre[i]=pre[i− 1 ] nums[i], allora la condizione "la somma dei sottoarray [j..i] è k" può essere convertita in pre[i]−pre[j−1]==k, e la seguente condizione può essere ottenuto spostando i termini che lo Standard j deve soddisfare pre[j−1]==pre[i]−k
3. Quando si considera il numero di sottoarray consecutivi che terminano con i e si sommano a k, conta semplicemente quanti prefissi pre[j] si sommano a pre[i]−k. Costruiamo una tabella hash mp, con sum come chiave, il numero di occorrenze come valore corrispondente, registriamo il numero di occorrenze di pre[i], aggiorniamo mp da sinistra a destra e calcoliamo la risposta, quindi la risposta che termina con i è mp[pre[i] −k] può essere ottenuto in tempo O(1). La risposta finale è la somma del numero di sottoarray che terminano con tutti gli indici con una somma di k
4. Va notato che quando si aggiorna il calcolo del bordo da sinistra a destra, è stato assicurato che l'intervallo del pedice di pre[j] registrato in mp[pre[i]−k] sia 0≤j≤i. Allo stesso tempo, poiché il calcolo di pre[i] è relativo solo alla risposta all'elemento precedente, non è necessario creare un pre array e utilizzare direttamente la variabile pre per registrare la risposta a pre[i-1] .
2.Codice
soluzione di classe pubblica { public int suarraySum(int[] numeri, int k) { int count = 0, pre = 0; //pre è la somma del prefisso HashMap < Intero, Intero > mp = nuova HashMap < > (); mp.put(0, 1); //Inizializzazione, la somma è 0 e il numero di occorrenze è 1 for (int i = 0; i < numeri.lunghezza; i ) { pre = nums[i];//Il calcolo di pre[i] è relativo solo alla risposta all'elemento precedente, non è necessario creare un pre array if (mp.containsKey(pre - k)) { //pre[i]−pre[j−1]==k conteggio = mp.get(pre - k); } mp.put(pre, mp.getOrDefault(pre, 0) 1); //la chiave è pre, il valore è il numero di occorrenze } conteggio dei resi; } }
3. Complessità
Complessità temporale: O(n), dove n è la lunghezza dell'array. La complessità temporale dell'attraversamento dell'array è O(n) e la complessità dell'utilizzo della tabella hash per interrogare ed eliminare è O(1), quindi la complessità temporale totale è O(n)
Complessità spaziale: O(n), dove n è la lunghezza dell'array. La tabella hash può avere n valori chiave diversi nel caso peggiore, quindi richiede una complessità spaziale O(n).
523. Determinare i sottoarray continui la cui somma è un multiplo di k
1. Somma del prefisso
0.Titolo
Dato un array contenente numeri non negativi e un intero di destinazione k, determinare se l'array contiene sottoarray consecutivi con una dimensione di almeno 2 e una somma che è un multiplo di k, ovvero la somma è n*k, dove n è anche un numero intero
1. Idee
1. Utilizzare un array di somma aggiuntivo per memorizzare la somma cumulativa dell'array sum[i] memorizza la somma del prefisso nella i-esima posizione dell'elemento.
2. Non attraverseremo l'intero sottoarray per trovare la somma. Dobbiamo solo utilizzare la somma del prefisso dell'array per trovare il numero i-esimo fino al numero j-esimo. Dobbiamo solo trovare somma[j]. −somma[i] numeri[i]
2.Codice
soluzione di classe pubblica { public booleano checkSubarraySum(int[] nums, int k) { int[] somma = nuovo int[nums.length]; somma[0] = numeri[0]; for (int i = 1; i < numeri.lunghezza; i ) sum[i] = sum[i - 1] nums[i];//sum[i] salva la somma del prefisso nella i-esima posizione dell'elemento for (int inizio = 0; inizio < num.lunghezza - 1; inizio ) { for (int fine = inizio 1; fine < numeri.lunghezza; fine ) { //Per trovare il numero i-esimo fino al numero j-esimo, basta trovare sum[j] - sum[i] nums[i] int somma = somma[fine] - somma[inizio] numeri[inizio]; if (somma == k || (k != 0 && somma % k == 0)) restituisce vero; } } restituire falso; } }
3. Complessità
Tempo: O(n^2), Spazio: O(n)
2. Programmazione dinamica
1. Idee
Calcola la somma dei sottoarray con lunghezza rispettivamente 2, 3,...m e determina se è un multiplo di k. Copiamo i dati di nums su dp Quando si calcola la somma dei sottoarray di lunghezza 2: dp[j] = dp[j] nums[j 1] dove dp[j] è nums[j]; Quando si calcola la somma dei sottoarray di lunghezza 3: dp[j] = dp[j] nums[j 2] dove dp[j] è il dp[j] aggiornato e un dp[j] è equivalente a nums[j ] numeri[j 1] In questo modo, quando si calcola la dimensione del sottoarray di lunghezza p, è possibile utilizzare il sottoarray calcolato di lunghezza p-1 per aggiornare e ottimizzare il triplo loop originale in un doppio loop.
2.Codice
soluzione di classe { int[] dp = nuovo int[10010]; public booleano checkSubarraySum(int[] nums, int k) { if(nums.length < 2) restituisce falso; //Quando k==0, consideralo separatamente In effetti, l'unica differenza tra esso e k!=0 è se eseguire o meno l'operazione modulare. se(k==0){ for(int i = 0; i < numeri.lunghezza; i ){ for(int j = 0; j < numeri.lunghezza-i; j ){ dp[j] = (dp[j] numeri[j i]); if(i != 0 && dp[j] == 0) restituisce vero; } } restituire falso; } //Quando i=k, dp[j] rappresenta la somma di k 1 numeri interi consecutivi con j come pedice iniziale. //Ad esempio, quando i=0, equivale a copiare nums su dp //Quando i=1, dp[0] equivale a iniziare con 0 come pedice e la somma dei due numeri interi in nums, ovvero nums[0] nums[1] //Ogni volta che calcoli, puoi utilizzare i dp originali per aggiornare, invece di aggiungerli uno per uno. for(int i = 0; i < numeri.lunghezza; i ){ for(int j = 0; j < numeri.lunghezza-i; j ){ dp[j] = (dp[j] nums[j i]) % k; if(i != 0 && dp[j] == 0) restituisce vero; } } restituire falso; } }
3. Complessità
Tempo: O(n^2), Spazio: O(n)
3. Tabella hash
1. Idee
1. Usa HashMap per salvare la somma cumulativa fino all'i-esimo elemento, ma dividiamo questa somma del prefisso per k per prendere il resto
2. Attraversiamo l'array specificato e registriamo sum%k fino alla posizione corrente. Una volta trovato il nuovo valore di sum%k, inseriamo un record nella HashMap (sum%k, i)
3. Supponiamo che il valore di sum%k nella i-esima posizione sia rem. Se la somma di qualsiasi sottoarray con i come punto finale sinistro è un multiplo di k, ad esempio, la posizione è j, allora il valore memorizzato nell'elemento jesimo nell'HashMap è (rem n*k)%k, e lo faremo find (rem n ∗k)%k=rem, che è lo stesso valore dell'i-esimo elemento salvato in HashMap
4. Giungere alla conclusione: finché il valore di sum%k è stato inserito nell'HashMap, significa che ci sono due indici i e j e la somma degli elementi tra loro è un multiplo intero di k As fintanto che c'è la stessa sum%k nell'HashMap, possiamo restituire direttamente {True}
2.Codice
soluzione di classe pubblica { public booleano checkSubarraySum(int[] nums, int k) { somma intera = 0; HashMap < intero, intero > mappa = nuova HashMap < > (); mappa.put(0, -1); for (int i = 0; i < numeri.lunghezza; i ) { somma = numeri[i]; se (k!= 0) somma = somma % k; //Finché il valore di sum%k è stato inserito nell'HashMap, significa che ci sono due indici i e j e la somma degli elementi tra loro è un multiplo intero di k if (map.containsKey(somma)) { if (i - map.get(sum) > 1) restituisce vero; } altro map.put(sum, i);//Salva la somma cumulativa fino all'i-esimo elemento e dividila per k per ottenere il resto } restituire falso; } }
3. Complessità
Tempo: O(n), Spazio: O(min(n,k)) HashMap contiene al massimo min(n,k) elementi diversi.
713.Il numero di sottoarray consecutivi il cui prodotto è inferiore a k
1. Dicotomia
0.Titolo
Dato un array di numeri interi positivi. Trova il numero di sottoarray consecutivi nell'array il cui prodotto è inferiore a k
1. Idee
1. Per un i fisso, la ricerca binaria trova il j più grande tale che il prodotto di nums[i] per nums[j] sia inferiore a k. Tuttavia, poiché il prodotto potrebbe essere molto grande e causare un overflow numerico, dobbiamo prendere il logaritmo dell'array nums e convertire la moltiplicazione in addizione.
2. Dopo aver preso il logaritmo di ciascun numero in nums, memorizziamo il suo prefisso e durante la ricerca binaria, per i e j, possiamo usare prefix[j 1]−prefix[i] per ottenere nums[i] Il logaritmo di. il prodotto in nums[j]. Per un i fisso, quando viene trovato il j più grande che soddisfa la condizione, conterrà j − i 1 sottoarray continuo il cui prodotto è inferiore a k.
2.Codice
soluzione di classe { public int numSubarrayProductLessThanK(int[] numeri, int k) { se (k == 0) restituisce 0; doppio logk = Math.log(k); double[] prefix = new double[nums.length 1] //Ottiene la somma del prefisso dopo i logaritmi for (int i = 0; i < numeri.lunghezza; i ) { prefix[i 1] = prefix[i] Math.log(nums[i]);//Il pedice della somma del prefisso inizia da 1 } int ans = 0; for (int i = 0; i < prefix.length; i ) { //La dicotomia viene eseguita nella somma del prefisso, il che equivale a essere eseguita nel risultato int lo = i 1, hi = prefisso.lunghezza; mentre (lo < ciao) { int mi = lo (ciao - lo) / 2; //Quando i=1, prefix[1] equivale a rimuovere nums[0], cioè contare da nums[1] //Quando i=2, prefix[2] equivale a rimuovere nums[0] nums[1], cioè contare da nums[2] if (prefisso[mi] < prefisso[i] logk - 1e-9) lo = mi 1; altrimenti ciao = mi; } ans = lo - i - 1;//Quando i=1, è equivalente al risultato dopo aver rimosso il primo numero } //Quando i=2, equivale al risultato dopo aver rimosso i primi due numeri. return ans; //-1 è perché lo stesso è già maggiore del valore di destinazione } }
3. Complessità
Tempo: O(nlogn), Spazio: O(n)
2.Doppi puntatori
1. Idee
1. Per ogni destra, dobbiamo trovare la sinistra più piccola, in modo che il prodotto dei numeri tra loro sia inferiore a k Poiché quando sinistra aumenta, questo prodotto non aumenta in modo monotono, quindi possiamo utilizzare il metodo del doppio puntatore per spostarci a sinistra monotonicamente.
2. Utilizzare un ciclo per enumerare la destra e impostare il valore iniziale di sinistra su 0. Ad ogni passo del ciclo, significa che destra viene spostata di una posizione a destra e il prodotto viene moltiplicato per nums[right]. A questo punto dobbiamo spostarci da sinistra a destra finché non viene soddisfatta la condizione che il prodotto sia inferiore a k. Ad ogni mossa, il prodotto deve essere diviso per numeri[sinistra]. Quando il movimento a sinistra è completato, per la destra corrente, contiene 1 sottoarray consecutivo destra-sinistra il cui prodotto è inferiore a k
2.Codice
soluzione di classe { public int numSubarrayProductLessThanK(int[] numeri, int k) { se (k <= 1) restituisce 0; int prod = 1, ans = 0, sinistra = 0; for (int destra = 0; destra < num.lunghezza; destra ) { prod *= nums[right];//Prodotto prefisso corrente while (prod >= k) prod /= nums[left];//Se non sei soddisfatto, sposta continuamente il puntatore sinistro verso destra //Per non ripetere i calcoli, dopo aver posizionato ciascun puntatore a destra, viene calcolato solo il numero di sottostringhe che terminano con il puntatore a destra. //Tutto ciò che non termina con diritto è stato calcolato in precedenza e non può essere ricalcolato qui. ans = destra - sinistra 1; } ritorno e; } }
3. Complessità
Tempo: O(n), Spazio: O(1)
53. Importo massimo successivo
1. Programmazione dinamica
0.Titolo
Dato un array intero nums, trova un sottoarray continuo (almeno un elemento) con la somma massima e restituisce la sua somma massima
1. Idee
1. Utilizzare ai per rappresentare nums[i] e utilizzare f(i) per rappresentare la "somma massima di sottoarray consecutivi che terminano con l'i-esimo numero"
2. Come trovare f(i)? Puoi considerare se ai diventa un segmento da solo o si unisce al segmento corrispondente a f(i−1). Ciò dipende dalla dimensione di ai e f(i−1) ai. Scrivi un'equazione di trasferimento di programmazione dinamica di questo tipo: f(i) =max{ f(i−1) ai,ai}
3. Considerando che f(i) è legato solo a f(i−1), possiamo usare solo una variabile pre per mantenere il valore di f(i−1) per l'attuale f(i), complicando così lo spazio La velocità viene ridotta a O(1), che è in qualche modo simile all'idea di "array mobile"
2.Codice
soluzione di classe { public int maxSubArray(int[] numeri) { int pre = 0, maxAns = nums[0]; for (int x: numeri) { //pre per mantenere il valore di f(i−1) per l'attuale f(i) pre = Math.max(pre x, x);//Determina se pre è un numero negativo e se deve essere aggiunto al numero corrente maxAns = Math.max(maxAns, pre);//Ottieni il valore massimo } restituire maxAns; } }
3. Complessità
Tempo: O(n), Spazio: O(1)
2. Dividi e conquista: albero dei segmenti di linea
1. Idee
1. Questo metodo di divisione e conquista è simile all'operazione pushUp di "Risoluzione del problema LCIS dell'albero dei segmenti di linea". Si consiglia di dare un'occhiata al metodo di unione degli intervalli dell'albero dei segmenti di linea per risolvere il "Problema della sequenza di aumento continuo con intervallo più lungo" e "Il problema della somma dei sottosegmenti dell'intervallo massimo" che è stato chiesto molte volte ".
2. Come unire le informazioni nell'intervallo [l,m] e le informazioni nell'intervallo [m 1,r] nelle informazioni nell'intervallo [l,r]. Le due domande più critiche sono: quali informazioni vogliamo mantenere nell'intervallo? Come combiniamo queste informazioni?
3. Per un intervallo [l, r] possiamo mantenere quattro quantità:
4. Dopo aver calcolato le tre quantità precedenti, è facile calcolare la somma m di [l, r]. Possiamo considerare se l'intervallo corrispondente alla somma m di [l, r] si estende su m - potrebbe non estendersi su m, vale a dire, la somma m di [l, r] può essere la somma m del "sottointervallo sinistro" e il "sottointervallo destro" Uno di mSum; può anche estendersi su m e può essere la somma di rSum del "sottointervallo sinistro" e lSum del "sottointervallo destro". Qualunque sia maggiore dei tre
2.Codice
soluzione di classe { public int maxSubArray(int[] numeri) { if (nums == null || nums.length <= 0)// Verifica dell'input restituire 0; int len = nums.length;//Ottieni la lunghezza di input return getInfo(nums, 0, len - 1).mSum; } class wtevTree { //Albero dei segmenti di linea int lSum;//La somma massima di sottosegmenti con l'intervallo sinistro come punto finale int rSum;//La somma massima di sottosegmenti con l'intervallo destro come punto finale int iSum;//La somma di tutti i numeri nell'intervallo int mSum;//La somma massima dei sottosegmenti di questo intervallo wtevTree(int l, int r, int i, int m) { // costruttore lSomma = l; rSomma = r; iSomma = i; mSomma = m; } } // Calcola gli attributi del livello precedente attraverso gli attributi esistenti e ritorna passo dopo passo per ottenere l'albero dei segmenti di linea wtevTree pushUp(wtevTree sinistraT, wtevTree destraT) { // L'lSum della nuova sottosezione è uguale all'lSum dell'intervallo sinistro o alla somma degli intervalli dell'intervallo sinistro più l'lSum dell'intervallo destro int l = Math.max(sinistraT.lSomma, sinistraT.iSomma destraT.lSomma); // L'rSum del nuovo sottosegmento è uguale all'rSum dell'intervallo destro o alla somma degli intervalli dell'intervallo destro più l'rSum dell'intervallo sinistro int r = Math.max(sinistraT.rSomma destraT.iSomma, destraT.rSomma); //La somma degli intervalli del nuovo sottosegmento è uguale alla somma delle somme degli intervalli sinistro e destro int i = sinistraT.iSomma destraT.iSomma; //La somma massima dei sottosegmenti del nuovo sottosegmento, il suo sottosegmento può passare attraverso gli intervalli sinistro e destro, o l'intervallo sinistro, o l'intervallo destro int m = Math.max(sinistraT.rSomma destraT.lSomma, Math.max(sinistraT.mSomma, destraT.mSomma)); restituisce nuovo wtevTree(l, r, i, m); } // Crea e ottiene ricorsivamente la struttura di tutte le sottosezioni dell'intervallo di input wtevTree getInfo(int[] numeri, int sinistra, int destra) { if (sinistra == destra) // Se la lunghezza dell'intervallo è 1, i suoi quattro sottosegmenti sono tutti i loro valori return new wtevTree(nums[left], nums[left], nums[left], nums[left]); int mid = (sinistra destra) >> 1;// Ottenere il punto medio mid, spostarsi di una posizione a destra equivale a dividere per 2, e l'operazione è più veloce wtevTree leftT = getInfo(nums, left, mid); wtevTree rightT = getInfo(nums, mid 1, right);//mid 1, non c'è intersezione tra gli intervalli sinistro e destro. return pushUp(leftT, rightT);//Al termine della ricorsione, esegui l'ultima unione } }
3. Complessità
Tempo: O(n), Spazio: O(logn) stack ricorsivo
4. Riepilogo
1. Il "Metodo 2" ha la stessa complessità temporale del "Metodo 1", ma poiché utilizza la ricorsione e mantiene quattro strutture informative, il tempo di esecuzione è leggermente più lungo e la complessità spaziale non è buona quanto il Metodo 1. Eccellente e difficile capire. Allora qual è il significato di questo metodo?
2. Ma se osservi attentamente il "Metodo 2", non solo può risolvere il problema dell'intervallo [0, n-1], ma può anche essere utilizzato per risolvere il problema di qualsiasi sottointervallo [l, r] . Se memorizziamo le informazioni di tutti i sottointervalli che compaiono dopo aver diviso e conquistato [0, n-1] utilizzando l'heap storage, cioè dopo aver costruito un vero albero, possiamo farlo in tempo O(logn). Possiamo anche modificare i valori nella sequenza ed eseguire qualche semplice manutenzione, quindi possiamo ancora trovare la risposta in qualsiasi intervallo nel tempo O(logn). Per le query su larga scala, si riflettono i vantaggi di questo metodo. Questo albero è una struttura dati magica menzionata sopra: l'albero dei segmenti di linea
152. Prodotto massimo dell'ordine delle parole
1. Programmazione dinamica
0.Titolo
Dato un array intero nums, trova il sottoarray continuo con il prodotto più grande nell'array (il sottoarray contiene almeno un numero) e restituisce il prodotto corrispondente al sottoarray.
1. Pensieri
1. Basandosi sull'esperienza di "53. Somma massima delle sottosuccessioni", è sbagliato scrivere l'equazione di trasferimento della programmazione dinamica: f(i)=max{f(i−1) ai, ai}
2. La definizione qui non soddisfa la "sottostruttura ottimale". La soluzione ottimale nella posizione attuale potrebbe non essere ottenuta trasferendo la soluzione ottima nella posizione precedente.
3. Classificare e discutere in base alla positività Se la posizione attuale è un numero negativo, allora speriamo che anche il prodotto di un segmento che termina con la sua posizione precedente sia un numero negativo, in modo che un negativo possa essere positivo, e lo speriamo. che il prodotto Cerca di "prenderne quanto più possibile", cioè il meno possibile. Se la posizione corrente è un numero positivo, preferiamo che anche il prodotto di un segmento che termina nella sua posizione precedente sia un numero positivo e vogliamo che sia il più grande possibile. Possiamo mantenere un altro fmin(i), che rappresenta il Prodotto del più piccolo sottoinsieme di prodotti che termina con i elementi
4. Equazione di transizione di stato
5. Rappresenta il prodotto fmax(i) del sottoarray con il prodotto più grande alla fine dell'i-esimo elemento. Puoi considerare di aggiungere ai al prodotto del sottoarray con il prodotto più grande o più piccolo alla fine di i -1 elemento e aggiungi ai ai due , il maggiore dei tre è il prodotto del sottoarray di prodotti più grande alla fine dell'elemento i-esimo. Il prodotto fmin(i) del sottoarray di prodotti più piccolo al la fine dell'i-esimo elemento è la stessa.
2.Codice
soluzione di classe { public int maxProdotto(int[] numeri) { int lunghezza = num.lunghezza; int[] maxF = nuovo int[lunghezza]; int[] minF = new int[length];//Il prodotto del sottoarray più piccolo che termina con l'i-esimo elemento System.arraycopy(nums, 0, maxF, 0, length);//Copia i numeri a partire da 0 a maxF a partire da 0, length length System.arraycopy(nums, 0, minF, 0, lunghezza); for (int i = 1; i < lunghezza; i) { //Due equazioni di transizione di stato maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(nums[i], minF[i - 1] * nums[i])); minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(nums[i], maxF[i - 1] * nums[i])); } int ans = maxF[0]; for (int i = 1; i < lunghezza; i) { ans = Math.max(ans, maxF[i]); } ritorno e; } }
3. Complessità
Il tempo e lo spazio sono entrambi O(n)
2. Ottimizza lo spazio
1. Pensieri
Poiché lo stato i-esimo è correlato solo allo stato i-1, secondo l'idea del "rolling array", possiamo utilizzare solo due variabili per mantenere lo stato al tempo i-1, una per mantenere fmax e una per mantenere fmin .
2.Codice
soluzione di classe { public int maxProdotto(int[] numeri) { int maxF = numeri[0], minF = numeri[0], ans = numeri[0]; int lunghezza = num.lunghezza; for (int i = 1; i < lunghezza; i) { int mx = maxF, mn = minF;//Solo due variabili vengono utilizzate per mantenere lo stato al tempo i−1 e ottimizzare lo spazio maxF = Math.max(mx * nums[i], Math.max(nums[i], mn * nums[i])); minF = Math.min(mn * nums[i], Math.min(nums[i], mx * nums[i])); ans = Math.max(maxF, ans); } ritorno e; } }
3. Complessità
Tempo: O(n), Spazio: O(1)
198. Rapina
1. Array mobile di programmazione dinamica
0.Titolo
Sei un ladro professionista che intende rubare le case lungo la strada. In ogni stanza è nascosta una certa quantità di contanti. L'unico fattore restrittivo che influisce sul furto è che le case adiacenti sono dotate di sistemi antifurto interconnessi. Se due case adiacenti vengono scassinate dai ladri nella stessa notte, il sistema suonerà automaticamente l'allarme. Data una serie di numeri interi non negativi che rappresentano la quantità di denaro custodita in ogni casa, calcola la quantità massima di denaro che puoi rubare in una notte senza attivare il dispositivo di allarme.
1. Idee
1. Consideriamo innanzitutto il caso più semplice. Se c'è solo una casa, ruba quella casa e potrai rubare fino all'importo totale massimo. Se ci sono solo due case, poiché le due case sono adiacenti, non puoi rubare contemporaneamente. Puoi rubare solo una delle case, quindi scegli la casa con un importo da rubare più alto e puoi rubare fino a importo complessivo massimo.
2. Se le case sono più di due, come calcolare l'importo totale massimo che può essere rubato? Per la (k>2)esima casa ci sono due opzioni: 1. Se rubi la casa k-esima, non puoi rubare la casa k-1. L'importo totale rubato è la somma dell'importo totale massimo delle prime case k-2 e dell'importo della casa k-esima. 2. Se la casa k-esima non viene rubata, l'importo totale rubato è l'importo totale massimo delle prime case k-1. Scegli l'opzione con l'importo totale del furto maggiore tra le due opzioni. L'importo totale del furto corrispondente a questa opzione è l'importo totale massimo di denaro che può essere rubato dalle prime k case.
3.dp[i] rappresenta l'importo totale massimo che può essere rubato dalle prime i case, equazione di transizione di stato: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4. Condizioni al contorno: dp[0]=nums[0] e dp[1]=max(nums[0],nums[1]), la risposta finale è dp[n−1]
2.Codice
soluzione di classe { public int rob(int[] numeri) { if (nums == nul|| nums.length == 0) { restituire 0; } int lunghezza = num.lunghezza; se (lunghezza == 1) { restituisci numeri[0]; } int[] dp = nuovo int[lunghezza]; dp[0] = nums[0];//Due condizioni al contorno dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < lunghezza; i ) {//Equazione di trasferimento dinamico dp[i] = Math.max(dp[i - 2] nums[i], dp[i - 1]); } return dp[lunghezza - 1]; } }
3. Complessità
Tempo: O(n), Spazio: O(n)
4. Ottimizzazione
Il metodo precedente utilizza un array per memorizzare i risultati. Considerando che l'importo totale massimo di ciascuna casa è correlato solo all'importo totale massimo delle prime due case della casa, è possibile utilizzare un array mobile per memorizzare solo l'importo totale massimo delle prime due case in ogni momento.
soluzione di classe { public int rob(int[] numeri) { if (nums == nul|| nums.length == 0) { restituire 0; } int lunghezza = num.lunghezza; se (lunghezza == 1) { restituisci numeri[0]; } //In ogni momento, solo la quantità totale più alta delle prime due case deve essere memorizzata, in un array mobile int primo = nums[0], secondo = Math.max(nums[0], nums[1]); for (int i = 2; i < lunghezza; i ) { int temp = secondo; secondo = Math.max(primi numeri[i], secondo); primo = temp; } ritorno secondo; } }
Tempo: O(n), Spazio: O(1)
213. Rapina II Formare un cerchio ×
1. Programmazione dinamica
0.Titolo
Sei un ladro professionista che pianifica di rubare case lungo la strada, con una certa somma di denaro nascosta in ogni stanza. Tutte le case in questo posto sono in cerchio, il che significa che la prima casa e l'ultima casa sono una accanto all'altra. Allo stesso tempo, le case adiacenti sono dotate di sistemi antifurto interconnessi. Se due ladri fanno irruzione in due case adiacenti nella stessa notte, il sistema attiverà automaticamente l'allarme. Data una serie di numeri interi non negativi che rappresentano la quantità di denaro immagazzinata in ogni casa, calcola la quantità massima di denaro che puoi rubare senza attivare il dispositivo di allarme.
1. Idee
1. La disposizione circolare significa che solo una tra la prima e l'ultima casa può essere scelta per il furto, quindi questo problema della disposizione circolare delle stanze può essere ridotto a due sottoproblemi di disposizione delle stanze su una sola fila:
2. Senza rubare la prima casa (ovvero nums[1]), l'importo massimo è p1. Senza rubare l'ultima casa (ovvero nums[n−1]), l'importo massimo è p2 (p1,p2)
3. Equazione di transizione di stato: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4.dp[n] è correlato solo a dp[n−1] e dp[n−2], quindi possiamo impostare due variabili cur e pre da registrare alternativamente, riducendo la complessità spaziale a O(1)
2.Codice
soluzione di classe { public int rob(int[] numeri) { if(nums.length == 0) return 0; if(nums.length == 1) return nums[0]; //La lunghezza della copia viene lasciata chiusa e aperta a destra [0, lunghezza-2] return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)), myRob(Arrays.copyOfRange(nums, 1, nums.length))); } private int mioRob(int[] numeri) { int pre = 0, cur = 0, tmp; for(int num : numeri) { tmp = cur; cur = Math.max(pre num, cur); pre=tmp; } ritorno cur; } }
3. Complessità
Tempo: O(n), Spazio: O(1)
337. Albero binario di Rapina III
1. Programmazione dinamica
0.Titolo
Dopo aver derubato l'ultima volta una strada e una cerchia di case, il ladro ha trovato una nuova zona dove poteva rubare. C'è solo un ingresso in quest'area, che chiamiamo "radice". Oltre alla "radice", ogni casa ha una ed una sola casa "genitrice" ad essa collegata. Dopo qualche ricognizione, l'astuto ladro si rese conto che "tutte le case di questo luogo sono disposte come un albero binario". Se nella stessa notte vengono derubate due case direttamente collegate, la casa chiamerà automaticamente la polizia. Calcola la cifra massima che un ladro può rubare in una notte senza far scattare l'allarme
1. Idee
1. Semplifica questo problema: un albero binario. Ogni punto sull'albero ha un peso corrispondente Ogni punto ha due stati (selezionato e non selezionato). Chiedi informazioni sulla situazione in cui i punti con una relazione genitore-figlio non possono essere selezionati contemporaneamente Qual è la somma massima del peso dei punti selezionabili?
2. Utilizzare f(o) per rappresentare la somma del peso massimo dei nodi selezionati sul sottoalbero del nodo o quando è selezionato il nodo; g(o) rappresenta la somma del peso massimo del nodo selezionato sul sottoalbero del nodo o quando o il nodo non è selezionato La somma del peso massimo del nodo selezionato; l e r rappresentano i figli sinistro e destro di o
3. Quando o è selezionato, né il figlio sinistro né quello destro di o possono essere selezionati. Pertanto, la somma massima del peso dei punti selezionati sul sottoalbero quando o è selezionato è la somma delle somme massime del peso di l e r quando sono selezionati. non sono selezionati, ovvero f (o)= g(l) g(r)f(o)=g(l) g(r)
4. Quando o non è selezionato, i figli sinistro e destro di o possono essere selezionati o meno. Per uno specifico figlio x di o, il suo contributo a o è il valore maggiore della somma dei pesi quando x è selezionato e quando x non è selezionato. Pertanto g(o)=max{f(l),g(l)} max{f(r),g(r)}
5. Utilizzare una mappa hash per memorizzare i valori della funzione di f e g e utilizzare un metodo di ricerca approfondito per attraversare l'albero binario in ordine e possiamo ottenere f e g di ciascun nodo. Il valore massimo di f e g del nodo radice è la risposta che stiamo cercando
2.Codice
soluzione di classe { Map<TreeNode, Integer> f = new HashMap<TreeNode, Integer>(); Map<TreeNode, Integer> g = new HashMap<TreeNode, Integer>(); public int rob(radice TreeNode) { dfs(root);//La profondità attraversa prima il nodo radice return Math.max(f.getOrDefault(root, 0), g.getOrDefault(root, 0)); } public void dfs(nodo TreeNode) { if (nodo== null) { ritorno; } dfs(node.left);//Adotta il metodo di attraversamento post-ordine dfs(nodo.destra); //Quando il nodo è selezionato, la somma del peso massimo del punto selezionato sul sottoalbero è la somma delle somme del peso massimo di l e r quando non sono selezionati. f.put(node, node.val g.getOrDefault(node.left, 0) g.getOrDefault(node.right, 0)); //Un figlio specifico x quando il nodo non è selezionato, il suo contributo al nodo è maggiore della somma dei pesi quando x è selezionato e quando x non è selezionato. g.put(node, Math.max(f.getOrDefault(node.left, 0), g.getOrDefault(node.left, 0)) Math.max(f.getOrDefault(node.right, 0), g.getOrDefault(node.right, 0))); } }
3. Complessità
L'algoritmo di cui sopra esegue un attraversamento post-ordine dell'albero binario e la complessità temporale è O(n poiché la ricorsione utilizzerà lo spazio dello stack, il costo dello spazio è O(n) e il costo dello spazio della mappa hash è; anche O(n), quindi anche la complessità dello spazio è O(n)
4. Ottimizzazione
Che sia f(o) o g(o), i loro valori finali sono legati solo a f(l), g(l), f(r), g(r), quindi per ogni nodo ci interessa solo riguardo a cosa sono la f e la g dei nodi figli. Possiamo progettare una struttura per rappresentare i valori f e g di un determinato nodo Ogni volta che ritorna la ricorsione, f e g corrispondenti a questo punto vengono restituiti alla chiamata del livello precedente, il che può far risparmiare spazio alla mappa hash. .
soluzione di classe { public int rob(radice TreeNode) { int[] rootStatus = dfs(root); return Math.max(rootStatus[0], rootStatus[1]); } public int[] dfs(nodo TreeNode) { if (nodo== null) { restituisce nuovo int[]{0, 0}; } int[] l = dfs(nodo.sinistra); int[] r = dfs(nodo.destra); //l[0], r[0] rappresenta il peso della presa, l[1], r[1] rappresenta il peso della non presa int selezionato = node.val l[1] r[1]; int notSelected = Math.max(l[0], l[1]) Math.max(r[0], r[1]); return new int[]{selezionato, nonselezionato}; } }
Complessità temporale: O(n). Analizzato sopra. Complessità spaziale: O(n). Sebbene la versione ottimizzata elimini lo spazio della mappa hash, il costo dell’utilizzo dello spazio dello stack è ancora O(n), quindi la complessità dello spazio rimane invariata.
15.Somma di tre numeri
1. Ordinamento del doppio puntatore
0.Titolo
Dato un array nums contenente n numeri interi, determinare se ci sono tre elementi a, b, c in nums tali che a b c = 0? Trova tutte le triple che soddisfano le condizioni e non vengono ripetute
1. Nessuna ripetizione
Non è possibile utilizzare semplicemente un triplo loop per enumerare tutte le triple. È necessario utilizzare una tabella hash per eseguire operazioni di deduplicazione, ordinare gli elementi nell'array da piccoli a grandi, quindi utilizzare un normale triplo loop per soddisfare i requisiti di cui sopra.
Per ogni ciclo, gli elementi di due enumerazioni adiacenti non possono essere uguali, altrimenti si causeranno anche duplicazioni.
2.Doppi puntatori
Quando è necessario enumerare due elementi in un array, se si scopre che all'aumentare del primo elemento, il secondo elemento diminuisce, è possibile utilizzare il metodo del doppio puntatore per ridurre la complessità temporale dell'enumerazione da O( N^2) ridotto a O(N)
Mantieni invariato il secondo ciclo e trasforma il terzo ciclo in un puntatore che inizia dall'estremità destra dell'array e si sposta verso sinistra.
Per aggiungere dettagli, è necessario mantenere il puntatore sinistro a sinistra del puntatore destro (ovvero b ≤ c)
3.Codice
soluzione di classe { public List<List<Integer>> threeSum(int[] nums) { int n = numeri.lunghezza; Arrays.sort(nums); //Ordina l'array List<List<Integer>> ans = new ArrayList<List<Integer>>(); // enumera a for (int primo = 0; primo < n; primo) { // Deve essere diverso dall'ultimo numero enumerato if (primo > 0 && numeri[primo] == numeri[primo - 1]) { Continua; } // Il puntatore corrispondente a c inizialmente punta all'estremità più a destra dell'array int terzo = n - 1; int destinazione = -nums[primo]; // enumera b for (int secondo = primo 1; secondo < n; secondo) { // Deve essere diverso dall'ultimo numero enumerato if (secondo > primo 1 && numeri[secondo] == numeri[secondo - 1]) { Continua; } // È necessario assicurarsi che il puntatore di b si trovi sul lato sinistro del puntatore di c while (secondo < terzo && nums[secondo] nums[terzo] > target) { --terzo; } if (second == terzo) { // Se i puntatori si sovrappongono, la condizione successivamente non verrà soddisfatta e potrai uscire dal ciclo rottura; } if (nums[secondo] nums[terzo] == target) { ans.add(Arrays.asList(nums[primo],nums[secondo],nums[terzo])); } } } ritorno e; } }
4. Complessità
Complessità temporale: O(N^2), dove N è la lunghezza dei numeri dell'array
Complessità spaziale: O(logN). Ignoriamo lo spazio per memorizzare le risposte e la complessità spaziale dell'ordinamento aggiuntivo è O(logN). Tuttavia, abbiamo modificato l'array di input nums, cosa che potrebbe non essere consentita nelle situazioni reali. Pertanto, si può anche considerare l'utilizzo di un array aggiuntivo per memorizzare una copia di nums e ordinarla. La complessità dello spazio è O(N).
18.Somma di quattro numeri
1. Ordinamento del doppio puntatore
0.Titolo
Dato un array nums contenente n numeri interi e un valore target target, determinare se ci sono quattro elementi a, b, c e d in nums tali che il valore di abcd sia uguale a target? Trova tutte le quadruple che soddisfano la condizione e non si ripetono.
1. Idee
Si tratta di aggiungere uno strato di loop basato sulla somma di tre numeri, quindi utilizzare i doppi puntatori, ma utilizzare i valori massimo e minimo per giudicare in anticipo, risparmiando tempo
2.Codice
soluzione di classe { public List<List<Integer>> fourSum(int[] nums,int target){ List<List<Integer>> result=new ArrayList<>();/*Definisci un valore di ritorno*/ if(nums==null||nums.lunghezza<4){ risultato restituito; } Arrays.sort(nums); int lunghezza=num.lunghezza; /*Definisci 4 puntatori k, i, j, h k inizia ad attraversare da 0, i inizia ad attraversare da k 1, lasciando j e h, j punta a i 1, h punta al valore massimo dell'array*/ for(int k=0;k<lunghezza-3;k){ /*Ignora quando il valore di k è uguale al valore precedente*/ if(k>0&&numeri[k]==numeri[k-1]){ Continua; } /*Ottiene il valore minimo corrente Se il valore minimo è maggiore del valore target, significa che i valori successivi sempre più grandi non funzioneranno affatto*/ int min1=num[k]num[k1]num[k2]num[k3]; se(min1>obiettivo){ break;//L'interruzione utilizzata qui esce direttamente dal ciclo, perché i numeri successivi saranno solo più grandi. } /*Ottiene il valore massimo corrente. Se il valore massimo è inferiore al valore target, significa che i successivi valori sempre più piccoli sono semplicemente inutili, ignoralo*/ int max1=nums[k] num[lunghezza-1] num[lunghezza-2] num[lunghezza-3]; if(max1<obiettivo){ continue;//Usa continua qui per continuare il ciclo successivo, perché il ciclo successivo ha un numero maggiore } /*Loop di secondo livello i, il valore iniziale punta a k 1*/ for(int i=k 1;i<lunghezza-2;i){ if(i>k 1&&nums[i]==nums[i-1]){/*Ignora quando il valore di i è uguale al valore precedente*/ Continua; } int j=i 1;/*definisci il puntatore j in modo che punti a i 1*/ int h=lunghezza-1;/*Definisci il puntatore h in modo che punti alla fine dell'array*/ int min=num[k]num[i]num[j]num[j 1]; se(min>obiettivo){ rottura; } int max=num[k]num[i]num[h]num[h-1]; if(max<obiettivo){ Continua; } /*Avvia l'esecuzione del puntatore j e del puntatore h, calcola la somma corrente, se è uguale al valore target, j e la deduplicazione, h-- e la deduplicazione, quando la somma corrente è maggiore del valore target, h-- , quando la somma corrente è inferiore al valore target j */ mentre (j<h){ int curr=num[k]num[i]num[j]num[h]; if(curr==obiettivo){ result.add(Arrays.asList(nums[k],nums[i],nums[j],nums[h]));//Completa in un solo passaggio J; while(j<h&&numeri[j]==numeri[j-1]){ J; } H--; while(j<h&&i<h&&num[h]==num[h 1]){ H--; } }altrimenti se(curr>destinazione){ H--; }altro { J; } } } } risultato restituito; } }
3. Complessità
Tempo: O(n^3), la complessità temporale dell'ordinamento è O(nlogn)
Spazio: O(logn), dipende principalmente dallo spazio aggiuntivo utilizzato dall'ordinamento
454. Aggiunta di quattro array ×
Utilizza la mappa per il raggruppamento a coppie
0.Titolo
Date quattro liste di array A, B, C, D contenenti numeri interi, conta quante tuple (i, j, k, l) sono tali che A[i] B[j] C[k] D[l] = 0
1. Idee
1. Dividilo in due gruppi, memorizza un gruppo in HashMap e confronta l'altro gruppo con HashMap
2. In questo caso, la situazione può essere suddivisa in tre tipologie: 1.HashMap memorizza un array, come A. Quindi calcola la somma dei tre array, come BCD. La complessità temporale è: O(n) O(n^3), che risulta in O(n^3). 2.HashMap memorizza la somma di tre array, come ABC. Quindi calcola un array, come D. La complessità temporale è: O(n^3) O(n), che risulta in O(n^3). 3.HashMap memorizza la somma di due array, come AB. Quindi calcola la somma dei due array, come CD. La complessità temporale è: O(n^2) O(n^2), che risulta in O(n^2).
3. Secondo il secondo punto, possiamo concludere che due array vengono contati come due array.
4. Prendiamo come esempio la memorizzazione della somma di due array AB. Per prima cosa, trova la somma sumAB di due numeri qualsiasi A e B, usa sumAB come chiave e il numero di volte in cui sumAB appare come valore e memorizzalo nell'hashmap. Quindi calcola sumCD, l'inverso della somma di due numeri qualsiasi in C e D, e scopri se la chiave è sumCD nell'hashmap.
2.Codice
soluzione di classe { public int fourSumCount(int[] A, int[] B, int[] C, int[] D) { HashMap<Integer, Integer> mappa = new HashMap<Integer,Integer>(); int len=A.lunghezza; for(int a:A) { for(int b:B) { //Memorizza il risultato di a b, non c'è assegnazione di 1, esiste sulla base originale 1 map.merge(a b, 1, (vecchio,nuovo_)->vecchio 1); } } int res=0; for(int c:C) { for(int d:D) { res =map.getOrDefault(0-c-d, 0); } } restituire ris; } }
3. Complessità
Tempo: O(n^2), Spazio: O(1)
4. Riepilogo
Nell'algoritmo vengono utilizzati i nuovi metodi di mappatura merge e getOrDefault. Rispetto al metodo tradizionale di scrittura dei giudizi, l'efficienza è notevolmente migliorata.
sottoargomento
3. Elenco collegato
0.frequenza
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. Aggiungi due numeri in ordine inverso
0.Titolo
Date due liste collegate non vuote che rappresentano due numeri interi non negativi. Tra questi, le rispettive cifre sono memorizzate in ordine inverso e ciascuno dei loro nodi può memorizzare solo una cifra. Se aggiungiamo questi due numeri, verrà restituita una nuova lista collegata per rappresentare la loro somma. Puoi supporre che, ad eccezione del numero 0, nessuno dei due numeri inizierà con 0.
1.Soluzione
classe pubblica ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummyHead = nuovo ListNode(0); ListNode p = l1, q = l2, curr = dummyHead; int riporto = 0; while (p!= nullo || q!= nullo) { int x = (p!= null) ? p.val : 0; int y = (q!= null) ? q.val : 0; somma int = trasporta x y; riporto = somma / 10; curr.next = nuovo ListNode(somma % 10); curr = curr.successivo; if (p!= null) p = p.successivo; se (q!= null) q = q.successivo; } se (porta > 0) { curr.next = nuovo ListNode(carry); } ritorna dummyHead.next; }
Il punto più importante di questa domanda è la situazione di riporto dopo l'aggiunta, che è facile da ignorare. Un altro punto importante è che dopo aver completato l'aggiunta dell'ultima cifra, potrebbe ancora verificarsi un riporto. Questa situazione deve essere gestita separatamente. L'idea iniziale era di convertire i due elenchi collegati in numeri e sommarli, ma questo metodo causerà un overflow per i numeri troppo lunghi, quindi gli elenchi collegati potranno solo essere elaborati.
Tempo: O(max(m, n)), Spazio: O(max(m, n))
2. Aggiungi due numeri in sequenza
public ListNode addTwoNumbersExt(ListNode l1, ListNode l2) { //Definisce un nuovo elenco collegato per memorizzare i risultati del calcolo Il nodo principale viene creato per impostazione predefinita, quindi il valore predefinito è 0 ListNode temp = nuovo ListNode(0); ListNode node = temp; // Ricorda il primo nodo int carry = 0;//carry, ovvero se l'addizione è maggiore di 10, aggiungiamo 1 alla cifra successiva, il valore predefinito è 0 Stack<Integer> stackL1 = new Stack<>(); // Utilizza stack per ricevere dati dalla lista collegata l1 Stack<Integer> stackL2 = new Stack<>();//Utilizza lo stack per ricevere dati dalla lista collegata l2 // Attraversa i due elenchi collegati e li aggiunge allo stack while (l1 != null || l2 != null) { // Determina se l'elenco collegato l1 è vuoto. In caso contrario, punta al nodo successivo. se (l1!= nullo) { stackL1.push(l1.val); l1 = l1.successivo; } // Determina se l'elenco collegato l2 è vuoto In caso contrario, punta al nodo successivo. se (l2!= nullo) { stackL2.push(l2.val); l2 = l2.successivo; } } // Attraversa entrambi gli stack while (!stackL1.empty() || !stackL2.empty()) { int x = (!stackL1.empty()) ? stackL1.pop() : 0;//Se lo stack stackL1 non è nullo, pop lo stack, altrimenti è 0 int y = (!stackL2.empty()) ? stackL2.pop() : 0;//Se lo stack stackL2 non è nullo, pop lo stack, altrimenti è 0 int sum = x y carry;//Poiché vengono aggiunte singole cifre, l'indice massimo è uguale a 19 ed è necessario aggiungere il carry // Memorizza il risultato nell'elenco collegato, ma deve essere modulo 10. Finché è maggiore o uguale a 10, otterrà una singola cifra. temp.next = nuovo ListNode(somma % 10); temp = temp.next;//punta al nodo successivo // Trova il riporto Se è maggiore di 10, puoi scoprire che il riporto è 1, altrimenti è 0. riporto = somma / 10; } // Potrebbe verificarsi una situazione in cui il calcolo del valore finale delle due liste collegate è maggiore o uguale a 10 ed entrambe le liste collegate sono vuote, quindi il riporto deve essere inserito nell'ultimo nodo se (porta > 0) { temp.next = new ListNode(carry); temp = temp.successiva; } // Poiché il nodo principale viene creato per impostazione predefinita, dobbiamo iniziare da quello successivo nell'elenco collegato temporaneo. restituisce nodo.next; }
Invece di archiviare in ordine inverso, dovresti trovare un modo per modificarlo in ordine inverso. È possibile utilizzare il metodo dell'ordine inverso fornito con l'elenco collegato oppure è possibile utilizzare lo stack per ottenere l'ordine inverso. Infine, è necessario invertire il risultato.
4. Corda
0.frequenza
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 sottostringa più lunga senza caratteri ripetuti
1. Finestra scorrevole
1. Premessa ideologica
Supponiamo di selezionare il k-esimo carattere nella stringa come posizione iniziale e di ottenere la posizione finale della sottostringa più lunga che non contiene caratteri ripetuti come r_k. Poi quando selezioniamo il k 1° carattere come posizione di partenza, i caratteri da k 1 a r_k ovviamente non vengono ripetuti, e poiché manca il k-esimo carattere originale, possiamo provare a continuare ad aumentare r_k fino a destra finché non compaiono caratteri ripetuti su il lato.
2. Pensieri concreti
In questo modo, possiamo utilizzare la "finestra scorrevole" per risolvere questo problema: Usiamo due puntatori per rappresentare una sottostringa (i confini sinistro e destro) di una stringa. Il puntatore sinistro rappresenta la "posizione iniziale della sottostringa enumerata" menzionata sopra, e il puntatore destro è r_k menzionato sopra; In ogni passaggio dell'operazione, sposteremo il puntatore sinistro di uno spazio a destra, indicando che iniziamo a enumerare il carattere successivo come posizione iniziale, quindi potremo continuare a spostare il puntatore destro a destra, ma dobbiamo farlo assicurarsi che i due puntatori corrispondano. Non ci sono caratteri ripetuti nella sottostringa. Una volta completato lo spostamento, questa sottostringa corrisponde alla sottostringa più lunga che inizia dal puntatore sinistro e non contiene caratteri ripetuti. Registriamo la lunghezza di questa sottostringa; Alla fine dell'enumerazione, la risposta sarà la lunghezza della sottostringa più lunga trovata.
3. Tabella hash per determinare i caratteri ripetuti
Dobbiamo anche utilizzare una struttura dati per determinare se sono presenti caratteri ripetuti. La struttura dati comunemente utilizzata è un set di hash Quando il puntatore sinistro si sposta verso destra, rimuoviamo un carattere dal set di hash a destra, quando ci muoviamo, aggiungiamo un carattere al set di hash.
4.Codice
soluzione di classe { public int lengthOfLongestSubstring(String s) { // Raccolta hash, registra se ciascun carattere appare o meno Set<Carattere> occ = new HashSet<Carattere>(); int n = s.lunghezza(); // Il puntatore destro, il valore iniziale è -1, che equivale a trovarci sul lato sinistro del limite sinistro della stringa e non iniziare ancora a muoverci. int rk = -1, ans = 0; for (int i = 0; i < n; i) { se (i!= 0) { //Sposta il puntatore sinistro di uno spazio a destra e rimuove un carattere occ.remove(s.charAt(i - 1)); } while (rk 1 < n && !occ.contains(s.charAt(rk 1))) { // Continua a spostare il puntatore destro occ.add(s.charAt(rk 1)); rk; } // I caratteri i to rk sono una sottostringa di caratteri estremamente lunga e non ripetitiva ans = Math.max(ans, rk - i 1); } ritorno e; } }
5. Complessità
Tempo: O(N), dove N è la lunghezza della stringa, spazio: O(∣Σ∣),∣Σ∣ rappresenta la dimensione del set di caratteri
5. La sottostringa palindroma più lunga
1. Programmazione dinamica dp
1. Pensieri
Per una sottostringa, se è una stringa palindroma e la lunghezza è maggiore di 2, dopo aver rimosso la prima e le ultime due lettere, è ancora una stringa palindroma.
2. Equazione di transizione di stato
P(i,j)=P(i 1,j−1)∧(Si == Sj)
3. Condizioni al contorno
P(i,i)=vero carattere singolo P(i,i 1)=(Si==Si 1) due caratteri
4.Codice
soluzione di classe { public String longestPalindrome(String s) { int n = s.lunghezza(); booleano[][] dp = nuovo booleano[n][n]; String ans = ""; for (int k = 0; k < n; k) { for (int i = 0; i K < n; i) { int j = io k; //Prima di tutto affronta due situazioni critiche if (k == 0) { //Quando k=0, j=i, dp[i][j] equivale a un carattere, che deve essere una stringa palindroma dp[i][j] = vero; } else if (k == 1) { //Quando k=1, j=i 1, dp[i][j] equivale a due caratteri consecutivi. Quando i due caratteri sono uguali, deve essere una stringa palindroma dp[i][j] = (s.charAt(i) == s.charAt(j)); } else { //Quando k>1, l'equazione di trasferimento della programmazione dinamica deve essere soddisfatta: 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()) { //Aggiorna la lunghezza della stringa palindromo ans = s.substring(i, i k 1); //Presta attenzione all'uso specifico del metodo sottostringa } } } ritorno e; } }
5. Complessità
Complessità temporale: O(n^2), dove n è la lunghezza della stringa. Il numero totale di stati nella programmazione dinamica è O(n^2) e per ciascuno stato il tempo necessario per il trasferimento è O(1). Complessità spaziale: O(n^2), ovvero lo spazio richiesto per memorizzare lo stato di programmazione dinamica
2. Espansione centrale
1. Prerequisiti
Secondo l'equazione di transizione degli stati, si può constatare che tutti gli stati hanno possibilità uniche durante la transizione. In altre parole, possiamo iniziare ad "espandere" da ciascun caso limite e possiamo anche ottenere le risposte corrispondenti a tutti gli stati.
La sottostringa corrispondente al "caso limite" è in realtà il "centro palindromo" della stringa palindroma che abbiamo "espanso"
2.Essenza
Enumerare tutti i "centri palindromi" e provare ad "espandersi" fino a quando non può essere espanso. La lunghezza della stringa palindroma in questo momento è la lunghezza della stringa palindromo più lunga sotto questo "centro palindromo".
3.Codice
soluzione di classe { public String longestPalindrome(String s) { if (s == null || s.lunghezza() < 1){ ritorno ""; } int start = 0;//Inizializza il punto iniziale e il punto finale della sottostringa palindroma più grande intero fine = 0; // Attraversa ogni posizione e usala come posizione centrale for (int i = 0; i < s.length(); i ) { // Ottiene rispettivamente la lunghezza della sottostringa palindroma pari e dispari int len_odd = espansoCentro(s,i,i); int len_even = espansoCentro(s,i,i 1); int len = Math.max(len_odd,len_even); // Confronta la lunghezza massima // Calcola il punto iniziale e il punto finale corrispondenti alla sottostringa palindroma più grande e disegna un'immagine per determinarli if (len > fine - inizio){ //Perché qui è necessario len-1 Spiegalo qui, perché il ciclo for inizia da 0, //Se si tratta di un numero dispari di palindromi, assumendo che ce ne siano 3, allora len=3, in questo momento il centro i è il pedice 1 (a partire da 0), quindi I risultati di (len-1)/2 e len/2 sono entrambi 1, perché i numeri interi vengono arrotondati per difetto. //Ma se è un numero pari di palindromi, assumendo che ce ne siano 4, allora len=4, il centro in questo momento è una linea tratteggiata, ma la posizione di i Ma in 1, (perché S si percorre da sinistra a destra, se da destra a sinistra, //La posizione di i sarà su 2.) A questo punto, (len-1)/2=1, len/2=2 Ovviamente, per garantire che il pedice sia corretto, ciò di cui abbiamo bisogno è (len-1)/2 Il motivo è in realtà che i è una posizione a sinistra della linea centrale, //Quindi riducilo di 1. inizio = i - (len - 1)/2; fine = ilen/2; } } return s.substring(start,end 1);// Nota: end 1 qui è a causa della funzione di chiusura a sinistra e di apertura a destra di Java } private int espansoCenter(String s,int left,int right){ //Confini iniziali sinistro e destro // Quando sinistra = destra, il centro del palindromo è un carattere e la lunghezza della stringa palindromo è un numero dispari. // Quando destra = sinistra 1, il centro del palindromo è uno spazio vuoto e la lunghezza della stringa palindroma è un numero pari. // Quando si esce dal ciclo, s.charAt(left) è esattamente soddisfatto! = s.charAt(a destra) while (sinistra >= 0 && destra < s.length() && s.charAt(sinistra) == s.charAt(destra)){ Sinistra--; Giusto ; } return destra - sinistra - 1; // La lunghezza della stringa palindromo è destra-sinistra 1-2 = destra - sinistra - 1 } }
4. Complessità
Complessità temporale: O(n^2), dove n è la lunghezza della stringa. Esistono n e n-1 centri palindromi di lunghezza 1 e 2 rispettivamente, e ciascun centro palindromo si espanderà verso l'esterno al massimo O(n) volte. Complessità spaziale: O(1)
3.Manacher carro trainato da cavalli
5.Ricerca binaria
0.frequenza
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. Trova la mediana di due array di ordine positivo
1. Pensiero convenzionale
1. Utilizzare il metodo di unione per unire due array ordinati per ottenere un array ordinato di grandi dimensioni. L'elemento al centro di un grande array ordinato è la mediana.
2. Non è necessario unire due array ordinati, basta trovare la posizione della mediana. Poiché le lunghezze dei due array sono note, è nota anche la somma degli indici dei due array corrispondenti alla mediana. Mantieni due puntatori, che inizialmente puntano alla posizione dell'indice 0 dei due array. Ogni volta, il puntatore che punta al valore più piccolo viene spostato indietro di un bit (se un puntatore ha raggiunto la fine dell'array, devi solo spostare quello. puntatore dell'altro array ), fino a raggiungere la posizione mediana
La complessità temporale della prima idea è O(m n), e la complessità spaziale è O(m n). Sebbene la seconda idea possa ridurre la complessità spaziale a O(1), la complessità temporale è ancora O(m n). La complessità temporale richiesta dalla domanda è O(log(m n)), quindi le due idee precedenti non soddisfano la complessità temporale richiesta dalla domanda.
2.Ricerca binaria: kesimo decimale
1. Tre situazioni
Se A[k/2-1] < B[k/2-1], allora i numeri minori di A[k/2-1] sono al più i primi k/2−1 numeri di A e il primo k/ di B 2-1 numeri, cioè ci sono solo k-2 numeri minori di A[k/2-1] al massimo, quindi A[k/2-1] non può essere il k-esimo numero, A[0 ] in A[k/2−1] è anche impossibile che sia il numero k-esimo e tutti possono essere esclusi. Se A[k/2-1] > B[k/2-1], allora da B[0] a B[k/2-1] può essere escluso. Se A[k/2-1] = B[k/2-1] si può classificare nel primo caso.
2. Risultati dell'elaborazione
Dopo aver confrontato A[k/2−1] e B[k/2−1], k/2 numeri che non possono essere il k-esimo numero più piccolo possono essere eliminati e l'intervallo di ricerca viene ridotto della metà. Allo stesso tempo, continueremo a eseguire la ricerca binaria sul nuovo array dopo l'eliminazione e ridurremo il valore di k in base al numero di numeri che escludiamo. Questo perché i numeri che escludiamo non sono maggiori del k-esimo numero più piccolo.
3. Tre situazioni particolari
Se A[k/2−1] o B[k/2−1] sono fuori limite, allora possiamo selezionare l'ultimo elemento nell'array corrispondente. In questo caso dobbiamo ridurre il valore di k in base al numero di esclusioni, invece di sottrarre direttamente k/2 da k. Se un array è vuoto, significa che tutti gli elementi dell'array sono stati esclusi e possiamo restituire direttamente il kesimo elemento più piccolo in un altro array. Se k=1, dobbiamo solo restituire il valore minimo dei primi elementi dei due array.
4. Algoritmo
soluzione di classe { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int lunghezza1 = nums1.lunghezza, lunghezza2 = nums2.lunghezza; int lunghezza totale = lunghezza1 lunghezza2; if (lunghezza totale % 2 == 1) { int midIndex = lunghezza totale / 2; doppia mediana = getKthElement(nums1, nums2, midIndex 1); mediana dei rendimenti; } altro { int midIndex1 = lunghezza totale / 2 - 1, midIndex2 = lunghezza totale / 2; doppia mediana = (getKthElement(nums1, nums2, midIndex1 1) getKthElement(nums1, nums2, midIndex2 1)) / 2.0; mediana dei rendimenti; } } public int getKthElement(int[] nums1, int[] nums2, int k) { /* Idea principale: per trovare il kesimo (k>1) elemento più piccolo, confrontare pivot1 = nums1[k/2-1] e pivot2 = nums2[k/2-1] * Il "/" qui significa divisione intera * Ci sono nums1[0 .. k/2-2] elementi in nums1 che sono minori o uguali a pivot1, per un totale di k/2-1 * Ci sono nums2[0 .. k/2-2] elementi in nums2 che sono minori o uguali a pivot2, per un totale di k/2-1 * Considerando pivot = min(pivot1, pivot2), il numero totale di elementi inferiori o uguali al pivot nei due array non supererà (k/2-1) (k/2-1) <= k-2 * In questo modo, il perno stesso può essere solo il k-1esimo elemento più piccolo. * Se pivot = pivot1, allora nums1[0 .. k/2-1] non può essere il kesimo elemento più piccolo. "Elimina" tutti questi elementi e usa il resto come un nuovo array nums1 * Se pivot = pivot2, allora nums2[0 .. k/2-1] non può essere il kesimo elemento più piccolo. "Elimina" tutti questi elementi e usa il resto come un nuovo array nums2 * Poiché "eliminiamo" alcuni elementi (questi elementi sono più piccoli del k-esimo elemento più piccolo), dobbiamo modificare il valore di k, meno il numero di elementi eliminati. */ int lunghezza1 = nums1.lunghezza, lunghezza2 = nums2.lunghezza; int indice1 = 0, indice2 = 0; int kthElemento = 0; mentre (vero) { // Casi limite if (indice1 == lunghezza1) { return nums2[indice2 k - 1]; } if (indice2 == lunghezza2) { return numeri1[indice1 k - 1]; } se (k == 1) { return Math.min(nums1[indice1], nums2[indice2]); } // In circostanze normali, l'indice1 viene utilizzato come punto di partenza e viene costantemente aggiornato. int metà = k/2; int newIndex1 = Math.min(index1 half, length1) - 1 //La lunghezza dell'array potrebbe essere inferiore alla precedente int nuovoIndice2 = Math.min(indice2 metà, lunghezza2) - 1; int perno1 = nums1[nuovoIndice1], perno2 = nums2[nuovoIndice2]; if (pivot1 <= pivot2) { // Combina le due situazioni k -= (newIndex1 - index1 1); //Anche l'indice1 sottostante cambia allo stesso tempo, questa è la lunghezza che viene sottratta indice1 = nuovoIndice1 1; } altro { k -= (nuovoIndice2 - indice2 1); indice2 = nuovoIndice2 1; } } } }
5. Complessità
Complessità temporale: O(log(m n)), dove m e n sono rispettivamente la lunghezza degli array nums1 e nums2. Inizialmente, c'è k=(m n)/2 oppure k=(m n)/2 1. Ogni ciclo di ciclo può ridurre l'intervallo di ricerca della metà, quindi la complessità temporale è O(log(m n)). Complessità spaziale: O(1).
3. Dividere l'array
1. Il ruolo della mediana
Dividere un insieme in due sottoinsiemi di uguale lunghezza, in modo tale che gli elementi di un sottoinsieme siano sempre più grandi degli elementi dell'altro sottoinsieme
2. Due situazioni
Quando la lunghezza totale di A e B è un numero pari, se può essere confermato: len(parte_sinistra)=len(parte_destra) max(parte_sinistra)≤min(parte_destra) Quindi, tutti gli elementi di {A,B} sono stati divisi in due parti della stessa lunghezza, e gli elementi della prima parte sono sempre minori o uguali agli elementi della seconda parte. La mediana è la media del valore massimo della parte precedente e del valore minimo della parte successiva.
Quando la lunghezza totale di A e B è un numero dispari, se può essere confermato: len(parte_sinistra)=len(parte_destra) 1 max(parte_sinistra)≤min(parte_destra) Quindi, tutti gli elementi in {A,B} sono stati divisi in due parti, la prima parte ha un elemento in più rispetto alla seconda parte e gli elementi nella prima parte sono sempre inferiori o uguali agli elementi nella seconda parte . La mediana è il valore massimo della parte precedente
3. Unisci le due situazioni
Per garantire queste due condizioni, basta assicurarsi: i j=m−i n−j (quando m n è un numero pari) o i j=m−i n−j 1 (quando m n è un numero dispari). Il lato sinistro del segno uguale è il numero di elementi nella parte precedente, mentre il lato destro del segno uguale è il numero di elementi nell'ultima parte. Sposta tutti i e j a sinistra del segno uguale e otteniamo i j=(m n 1)/2. Il risultato frazionario qui conserva solo la parte intera
Si stabilisce che la lunghezza di A sia inferiore o uguale alla lunghezza di B, cioè m≤n. In questo modo, per ogni i∈[0,m], esiste j=(m n 1)/2−i∈[0,n]. Se la lunghezza di A è maggiore, basta scambiare A e B. Se m> n, allora j risultante può essere un numero negativo.
B[j−1]≤A[i] e A[i−1]≤B[j], ovvero il valore massimo della prima parte è inferiore o uguale al valore minimo della seconda parte
4. Semplificare l'analisi
Supponiamo che A[i−1],B[j−1],A[i],B[j] esistano sempre. Per condizioni critiche come i=0, i=m, j=0, j=n, dobbiamo solo stipulare che A[−1]=B[−1]=−∞, A[m]=B[n ]= ∞ è sufficiente. Anche questo è relativamente intuitivo: quando un array non appare nella parte precedente, il valore corrispondente è infinito negativo, che non influenzerà il valore massimo della parte precedente, quando un array non appare nell'ultima parte, il valore corrispondente è positivo Infinity non influenzerà il valore minimo di quest'ultima parte.
5. lavoro
Trovare i in [0,m] tale che B[j-1]≤A[i] e A[i-1]≤B[j], dove j=(m n 1)/2−i Equivale a trovare i in [0,m] tale che A[i-1]≤B[j]
6. Algoritmo
soluzione di classe { public double findMedianSortedArrays(int[] nums1, int[] nums2) { if (nums1.lunghezza > nums2.lunghezza) { return findMedianSortedArrays(nums2, nums1); } int m = nums1.lunghezza; int n = nums2.lunghezza; int sinistra = 0, destra = m, ansi = -1; //mediana1: il valore massimo della parte precedente //mediana2: il valore minimo di quest'ultima parte int mediana1 = 0, mediana2 = 0; mentre (sinistra <= destra) { // La parte precedente contiene nums1[0 .. i-1] e nums2[0 .. j-1] // L'ultima parte contiene nums1[i .. m-1] e nums2[j .. n-1] int i = (sinistra destra) / 2; //dicotomia, i inizia dalla metà dell'intervallo int j = (m n 1) / 2 - i;//L'operazione 1 combina il numero totale di numeri pari e dispari in un unico caso //nums_im1, nums_i, nums_jm1, nums_j rappresentano rispettivamente nums1[i-1], nums1[i], nums2[j-1], nums2[j] //Quando un array non appare nella parte precedente, il valore corrispondente è infinito negativo, che non influenzerà il valore massimo della parte precedente. int nums_im1 = (i == 0 ? Intero.MIN_VALUE : nums1[i - 1]); //Quando un array non appare nell'ultima parte, il valore corrispondente è infinito positivo, il che non influenzerà il valore minimo dell'ultima parte. int nums_i = (i == m ? Intero.MAX_VALUE : nums1[i]); int nums_jm1 = (j == 0 ? Intero.MIN_VALUE : nums2[j - 1]); int nums_j = (j == n ? Intero.MAX_VALUE : nums2[j]); se (nums_im1 <= numeri_j) { ansi = io; mediana1 = Math.max(nums_im1, nums_jm1); mediana2 = Math.min(nums_i, nums_j); sinistra = i1; } altro { destra = i - 1; } } rendimento (m n) % 2 == 0 ? (mediana1 mediana2) / 2.0 : mediana1; } }
7. Complessità
Complessità temporale: O(log min(m,n)), dove m e n sono rispettivamente la lunghezza degli array nums1 e nums2 Complessità spaziale: O(1)
6.Albero
0.frequenza
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. Albero binario
0. Attraversa e organizza modelli comuni
144. Preordine di attraversamento dell'albero binario
1. Iterazione
1. Pensieri
Simile all'iterazione dell'attraversamento in ordine, con lievi modifiche
2.Codice
soluzione di classe { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); //Stack è implementato in base a array dinamici, mentre LinkedList è implementato in base a elenchi collegati bidirezionali è più efficiente nell'aggiunta, eliminazione e modifica rispetto a Stack Deque<TreeNode> stk = new LinkedList<TreeNode>();//Coda a doppia estremità //Il nodo non è vuoto o lo stack non è vuoto e il ciclo continua while (root!= null || !stk.isEmpty()) { mentre (radice!= null) { res.add(root.val);//Si accede direttamente al nodo root e quindi lo si inserisce nello stack stk.push(root); root = root.left;//Dopo che il nodo radice termina, accedi al suo figlio sinistro e diventa il nuovo nodo radice } root = stk.pop();//Quando si salta fuori dal ciclo, è stato effettuato l'accesso a tutti i figli di sinistra, quindi si accede al figlio di destra radice = radice.destra; } restituire ris; } }
2.1 Pitone
Soluzione di classe: def preorderTraversal(self, root: TreeNode) -> List[int]: res = lista() se non root: ritorno ris pila = [] nodo=radice while stack o nodo:#Il nodo non è vuoto oppure lo stack non è vuoto e il ciclo continua mentre nodo: res.append(node.val) #Si accede direttamente al nodo radice e quindi lo si inserisce nello stack stack.append(nodo) node = node.left #Dopo che il nodo radice termina, accedi al suo figlio sinistro e diventa il nuovo nodo radice node = stack.pop() #Quando si esce dal ciclo, è stato effettuato l'accesso a tutti i figli di sinistra, quindi si accede al figlio di destra nodo = nodo.destra ritorno ris
3.Un altro metodo
Partendo dal nodo radice, ogni iterazione estrae l'elemento superiore dello stack corrente e spinge il suo nodo figlio nello stack, premendo prima il figlio destro e poi il figlio sinistro.
soluzione di classe { public List<Integer> preorderTraversal(TreeNode root) { Stack LinkedList<TreeNode> = new LinkedList<>(); LinkedList<Integer> output = nuova LinkedList<>(); se (radice == null) { uscita di ritorno; } stack.add(root); while (!stack.isEmpty()) { TreeNode node = stack.pollLast();//Recupera e rimuove l'ultimo elemento di questo elenco output.add(nodo.val); if (node.right!= null) {//Spingi nello stack solo un figlio destro e uno sinistro del nodo corrente, indipendentemente dagli altri stack.add(nodo.destra); } if (nodo.sinistra!= null) { stack.add(nodo.sinistra); } } uscita di ritorno; } }
4. Complessità
Tempo: O(n), Spazio: O(n)
2.Morris Traversata
1. Pensieri
1. Accedi ai nodi predecessori dell'attraversamento del preordine dal nodo corrente verso il basso e ciascun nodo predecessore viene visitato esattamente due volte.
2. Per prima cosa inizia dal nodo corrente, fai un passo verso il figlio sinistro e poi visita tutto il basso lungo il figlio destro fino a raggiungere un nodo foglia (il nodo predecessore trasversale in ordine del nodo corrente), quindi aggiorniamo il output e crea un predecessore pseudo edge right = root aggiorna il punto successivo di questo predecessore. Se visitiamo il nodo precedente per la seconda volta, poiché punta già al nodo corrente, rimuoviamo lo pseudo spigolo e passiamo al vertice successivo
3. Se il movimento a sinistra nel primo passaggio non esiste, aggiorna direttamente l'output e spostati a destra
2.Codice
soluzione di classe { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); TreeNode predecessor = null;//Il nodo predecessore del nodo corrente, il nodo più a destra del sottoalbero sinistro mentre (radice!= null) { //A seconda che sia presente un figlio sinistro, determina se trovare il nodo predecessore o accedere direttamente al figlio destro se (root.sinistra!= null) { //Trova prima il predecessore, quindi assegna un valore al figlio destro e quindi attraversa il figlio sinistro del nodo corrente //Il nodo predecessore è il nodo radice corrente, fai un passo a sinistra e poi continua a camminare a destra finché non può più andare. predecessore = root.sinistra; while (predecessore.destra!= null && predecessore.destra!= root) { predecessore = predecessore.destra; } //A seconda che il figlio destro del predecessore sia vuoto, determina se accedere direttamente al sottoalbero sinistro e quindi assegnare il valore, oppure se l'accesso al sottoalbero sinistro è completato. //Lascia che il puntatore destro del predecessore punti alla radice e continui ad attraversare il sottoalbero sinistro if (predecessore.destra == null) { res.add(root.val); predecessore.destra = radice; radice = radice.sinistra; } //Indica che il sottoalbero sinistro è stato visitato. Dobbiamo disconnettere il collegamento e continuare a visitare il sottoalbero destro. altro { predecessore.destra = null; radice = radice.destra; } } // Se non è presente il figlio sinistro, accede direttamente al figlio destro altro { res.add(root.val); radice = radice.destra; } } restituire ris; } }
2.1 Pitone
Soluzione di classe: def preorderTraversal(self, root: TreeNode) -> List[int]: res = lista() se non root: ritorno ris p1 = radice mentre p1: p2 = p1.left #Il nodo predecessore del nodo corrente, il nodo più a destra del sottoalbero sinistro if p2: #A seconda se c'è un figlio sinistro, determina se trovare il suo nodo predecessore o accedere direttamente al figlio destro mentre p2.right e p2.right != p1: p2 = p2.destra #A seconda che il figlio destro di p2 sia vuoto, determinare se accedere direttamente al sottoalbero sinistro e quindi assegnare il valore, oppure se l'accesso al sottoalbero sinistro è completato. if not p2.right:#Lascia che il puntatore destro di p2 punti a p1 e continua ad attraversare il sottoalbero sinistro res.append(p1.val) p2.destra = p1 p1 = p1.sinistra Continua else: #Indica che il sottoalbero di sinistra è stato visitato, dobbiamo disconnettere il collegamento e continuare a visitare il sottoalbero di destra p2.right = Nessuno else: #Se non c'è il figlio sinistro, accedi direttamente al figlio destro res.append(p1.val) p1 = p1.destra ritorno ris
3. Complessità
Tempo: O(n), Spazio: O(1)
94. Attraversamento in ordine di alberi binari
1. Ricorsione
1. Pensieri
Questo albero viene attraversato visitando il sottoalbero sinistro - nodo radice - sottoalbero destro e quando visitiamo il sottoalbero sinistro o il sottoalbero destro, attraversiamo allo stesso modo finché non viene attraversato l'albero completo. Pertanto, l'intero processo di attraversamento è naturalmente ricorsivo. Possiamo utilizzare direttamente funzioni ricorsive per simulare questo processo.
2.Codice
soluzione di classe { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); ordine(root, res); restituire ris; } public void inorder(TreeNode root, List<Integer> res) { se (radice == null) { ritorno; } inorder(root.left, res); res.add(root.val); inorder(root.right, res); } }
2.1 Pitone
Soluzione di classe: def preorderTraversal(self, root: TreeNode) -> List[int]: def dfs(cur): se non cur: ritorno # Ricorsione di preordine res.append(cur.val) dfs(cur.sinistra) dfs(cur.destra) # # Ricorsione in ordine #dfs(cur.sinistra) # res.append(cur.val) #dfs(cur.destra) # # Ricorsione postordine #dfs(cur.sinistra) #dfs(cur.destra) # res.append(cur.val) ris = [] dfs(radice) ritorno ris
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi dell'albero binario. Nell'attraversamento dell'albero binario, ciascun nodo verrà visitato una e una sola volta.
Complessità spaziale: O(n). La complessità dello spazio dipende dalla profondità dello stack della ricorsione e la profondità dello stack raggiungerà il livello O(n) quando l'albero binario è una catena.
2.Iterazione dello stack
1. Idee
Possiamo anche implementare la funzione ricorsiva del metodo 1 in modo iterativo. La differenza è che uno stack viene mantenuto implicitamente durante la ricorsione e dobbiamo simulare esplicitamente questo stack durante l'iterazione.
2.Codice
soluzione di classe { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); //Stack è implementato in base a array dinamici, mentre LinkedList è implementato in base a elenchi collegati bidirezionali è più efficiente nell'aggiunta, eliminazione e modifica rispetto a Stack Deque<TreeNode> stk = new LinkedList<TreeNode>();//Coda a doppia estremità //Il nodo non è vuoto o lo stack non è vuoto e il ciclo continua while (root!= null || !stk.isEmpty()) { // Attraversa prima il figlio sinistro finché il figlio sinistro non è vuoto e termina il ciclo mentre (radice!= null) { stk.push(root); radice = radice.sinistra; } //Quando si esce dal ciclo, il figlio sinistro è vuoto. In questo momento, quello uscito dallo stack è equivalente al nodo radice, quindi tocca al figlio destro. root = stk.pop(); res.add(root.val); radice = radice.destra; } restituire ris; } }
2.1 Pitone
Soluzione di classe: def inorderTraversal(self, root: TreeNode) -> Lista[int]: res = lista() se non root: ritorno ris pila = [] nodo=radice while stack o nodo:#Il nodo non è vuoto oppure lo stack non è vuoto e il ciclo continua mentre nodo: stack.append(node) #Attraversa prima il figlio sinistro finché il figlio sinistro non è vuoto e termina il ciclo nodo = nodo.sinistra node = stack.pop() #Il figlio sinistro è vuoto quando salta fuori dal ciclo. In questo momento, quello uscito dallo stack è equivalente al nodo radice, quindi tocca al figlio destro. res.append(nodo.val) nodo = nodo.destra ritorno ris
3. Complessità
Tempo: O(n), Spazio: O(n)
3.Attraversamento in ordine di Morris
1. Pensieri
1. Se x non ha un figlio sinistro, aggiungi prima il valore di x all'array di risposte, quindi accedi al figlio destro di x, ovvero x=x.right
2. Se x ha un figlio sinistro, trova il nodo più a destra sul sottoalbero sinistro di x (ovvero, l'ultimo nodo del sottoalbero sinistro nell'attraversamento in ordine e il nodo predecessore di x nell'attraversamento in ordine) , che registriamo come predecessore. A seconda che il figlio destro del predecessore sia vuoto, eseguire le seguenti operazioni
3. Se il figlio destro del predecessore è vuoto, puntare il figlio destro su x, quindi accedere al figlio sinistro di x, ovvero x=x.left
4. Se il figlio destro del predecessore non è vuoto, allora il suo figlio destro punta a x in questo momento, indicando che abbiamo attraversato il sottoalbero sinistro di x. Lasciamo vuoto il figlio destro del predecessore, aggiungiamo il valore di x all'array di risposte, quindi accedere al figlio destro di x, ovvero x=x.right
5. Ripetere le operazioni precedenti fino alla visita dell'albero completo
6. In effetti, faremo un ulteriore passo nell'intero processo: assumendo che il nodo attualmente attraversato sia x, puntiamo il figlio destro del nodo più a destra nel sottoalbero sinistro da x a x, in modo che dopo l'attraversamento del sottoalbero sinistro sia completato, utilizzeremo questo puntatore a Resi
2.Codice
soluzione di classe { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); TreeNode predecessor = null;//Il nodo predecessore del nodo corrente, il nodo più a destra del sottoalbero sinistro mentre (radice!= null) { //A seconda che sia presente un figlio sinistro, determina se trovare il nodo predecessore o accedere direttamente al figlio destro se (root.sinistra!= null) { //Trova prima il predecessore, quindi assegna un valore al figlio destro e quindi attraversa il figlio sinistro del nodo corrente //Il nodo predecessore è il nodo radice corrente, fai un passo a sinistra e poi continua a camminare a destra finché non può più andare. predecessore = root.sinistra; while (predecessore.destra!= null && predecessore.destra!= root) { predecessore = predecessore.destra; } //Determina se l'assegnazione o l'accesso al sottoalbero sinistro termina in base al fatto che il figlio destro del predecessore sia vuoto. //Lascia che il puntatore destro del predecessore punti alla radice e continui ad attraversare il sottoalbero sinistro if (predecessore.destra == null) { predecessore.destra = radice; radice = radice.sinistra; } //Indica che il sottoalbero sinistro è stato visitato A questo punto, visitare il nodo radice, quindi disconnettere il collegamento e continuare a visitare il sottoalbero destro. altro { res.add(root.val); predecessore.destra = null; radice = radice.destra; } } // Se non è presente il figlio sinistro, accede direttamente al figlio destro altro { res.add(root.val); radice = radice.destra; } } restituire ris; } }
2.1 Pitone
Soluzione di classe: def inorderTraversal(self, root: TreeNode) -> Lista[int]: res = lista() se non root: ritorno ris p1 = radice mentre p1: p2 = p1.left #Il nodo predecessore del nodo corrente, il nodo più a destra del sottoalbero sinistro if p2: #A seconda se c'è un figlio sinistro, determina se trovare il suo nodo predecessore o accedere direttamente al figlio destro mentre p2.right e p2.right != p1: p2 = p2.destra #A seconda che il figlio destro di p2 sia vuoto, determinare se accedere direttamente al sottoalbero sinistro e quindi assegnare il valore, oppure se l'accesso al sottoalbero sinistro è completato. if not p2.right:#Lascia che il puntatore destro di p2 punti a p1 e continua ad attraversare il sottoalbero sinistro p2.destra = p1 p1 = p1.sinistra Continua else: #Indica che il sottoalbero sinistro è stato visitato A questo punto, visitare il nodo radice, quindi disconnettere il collegamento e continuare a visitare il sottoalbero destro. res.append(p1.val) p2.right = Nessuno else: #Se non c'è il figlio sinistro, accedi direttamente al figlio destro res.append(p1.val) p1 = p1.destra ritorno ris
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero di ricerca binario. Ogni nodo nell'attraversamento di Morris verrà visitato due volte, quindi la complessità temporale totale è O(2n)=O(n)
Complessità spaziale: O(1)
4. Metodo di marcatura a colori (universale)
1. Pensieri
0. Ha l'efficienza del metodo di iterazione dello stack ed è conciso e facile da comprendere come il metodo ricorsivo, cosa ancora più importante, questo metodo può scrivere codice completamente coerente per l'attraversamento del pre-ordine, del mid-order e del post-order.
1. Utilizza i colori per contrassegnare lo stato dei nodi. I nuovi nodi sono bianchi e i nodi visitati sono grigi. Se il nodo incontrato è bianco, contrassegnalo come grigio, quindi inserisci il nodo figlio destro, se stesso e il nodo figlio sinistro nello stack in sequenza. Se il nodo incontrato è grigio, viene emesso il valore del nodo
2. Se desideri implementare l'attraversamento pre-ordine e post-ordine, devi solo regolare l'ordine di sovrapposizione dei nodi figlio sinistro e destro.
2.Codice
Soluzione di classe: def inorderTraversal(self, root: TreeNode) -> Lista[int]: BIANCO, GRIGIO = 0, 1 #BIANCO non è stato visitato, GRIGIO ha visitato ris = [] pila = [(BIANCO, radice)] mentre pila: colore, nodo = stack.pop() se il nodo è Nessuno: continua se colore == BIANCO: #L'ordine di metà ordine è radice sinistra destra, e l'ordine di spingere nello stack è radice destra sinistra. Regola l'ordine per completare altri attraversamenti. stack.append((BIANCO, nodo.destra)) stack.append((GRAY, node)) #Il nodo corrente diventa grigio ed è stato visitato stack.append((BIANCO, nodo.sinistra)) altro: res.append(nodo.val) ritorno ris
3. Complessità
Tempo: O(n), Spazio: O(n)
145. Attraversamento post-ordine di un albero binario
1. Iterazione
1. Idee
Quando si ritorna al nodo radice, contrassegnare il nodo corrente per determinare se tornare dal sottoalbero sinistro o dal sottoalbero destro. Quando si ritorna dal sottoalbero destro, iniziare ad attraversare il nodo
2.Codice
soluzione di classe { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); Stack<NodoAlbero> stack = nuovo Stack<>(); //Il pre-nodo viene utilizzato per registrare il nodo precedentemente visitato, distinguendo tra la restituzione del nodo radice dal sottoalbero sinistro o dal sottoalbero destro. TreeNode pre = null; while(root!=null || !stack.empty()){ mentre(root!=null){ stack.push(root); //Spinge continuamente il nodo sinistro nello stack radice = radice.sinistra; } root = stack.peek();// Ritorna dal sottoalbero di sinistra, ottieni solo il nodo radice e non visualizzarlo, non è stato ancora visitato if(root.right==null || root.right==pre){ //Se il nodo destro è vuoto o il nodo destro è stato visitato res.add(root.val); //Puoi accedere al nodo root in questo momento pre = radice; pila.pop(); root = null; // In questo momento, non inserire il sottoalbero sinistro nello stack nel ciclo successivo. È già stato inserito e determinare direttamente l'elemento superiore dello stack. }altro{ root = root.right; //Non rimuoverlo ancora dallo stack, inserisci il suo nodo destro nello stack } } restituire ris; } }
2.1 Pitone
Soluzione di classe: def postorderTraversal(self, root: TreeNode) -> List[int]: se non root: lista restituita() res = lista() pila = lista() prev = None #Il nodo prev viene utilizzato per registrare il nodo visitato in precedenza, che distingue se restituire il nodo radice dal sottoalbero sinistro o dal sottoalbero destro. mentre root o stack: mentre root: stack.append(root) #Spinge continuamente il nodo sinistro nello stack radice = radice.sinistra #Questa applicazione viene visualizzata, ma non esiste un metodo simile nell'elenco. Puoi solo visualizzarlo prima e poi aggiungerlo. root = stack.pop() #Ritorna dal sottoalbero di sinistra, non è stato ancora visitato e verrà aggiunto allo stack in seguito. if not root.right o root.right == prev:#Se il nodo destro è vuoto o il nodo destro è stato visitato res.append(root.val) #Puoi accedere al nodo root in questo momento prev = root #Registra il nodo corrente root = None #In questo momento, non inserire il sottoalbero sinistro nello stack nel ciclo successivo. È già stato inserito e determina direttamente l'elemento superiore dello stack. altro: stack.append(root) #Se non è stato effettuato l'accesso prima, aggiungilo allo stack radice = radice.destra ritorno ris
3. Trasformare l'attraversamento del preordine
//Modifica il codice per scrivere i nodi nell'elenco collegato dei risultati nel codice di attraversamento del preordine: cambia l'inserimento alla fine della coda con l'inserimento nell'inizio della coda //Modifica la logica di controllo prima del nodo sinistro e poi del nodo destro nel codice di attraversamento del preordine: cambia per controllare prima il nodo destro e poi il nodo sinistro. //L'attraversamento pre-ordine è costituito dalle radici sinistra e destra, l'ordine inverso è la radice destra-sinistra e l'attraversamento post-ordine è costituito dalle radici sinistra e destra. soluzione di classe { public List<Integer> postorderTraversal(TreeNode root) { ListaLinked res = nuova ListaLinked(); Stack<NodoAlbero> stack = nuovo Stack<>(); TreeNode pre = null; while(root!=null || !stack.empty()){ mentre(root!=null){ res.addFirst(root.val); //Inserisci il primo della coda stack.push(root); root = root.right; //Prima a destra poi a sinistra } radice = stack.pop(); radice = radice.sinistra; } restituire ris; } }
4. Complessità
Tempo: O(n), Spazio: O(n)
2.Morris Traversata
1. Idee
1. L'idea centrale del Morris Traversal è quella di utilizzare un gran numero di puntatori liberi nell'albero per ottenere la massima riduzione dello spazio in testa. Le regole successive per l'attraversamento del postorder sono riassunte come segue:
2. Se il nodo figlio sinistro del nodo corrente è vuoto, attraversare il nodo figlio destro del nodo corrente.
3. Se il nodo figlio sinistro del nodo corrente non è vuoto, trova il nodo predecessore del nodo corrente sotto l'attraversamento in ordine nel sottoalbero sinistro del nodo corrente.
4. Se il nodo figlio destro del nodo predecessore è vuoto, impostare il nodo figlio destro del nodo predecessore sul nodo corrente e aggiornare il nodo corrente sul nodo figlio sinistro del nodo corrente.
5. Se il nodo figlio destro del nodo predecessore è il nodo corrente, reimpostare il nodo figlio destro su vuoto. Visualizza tutti i nodi sul percorso dal nodo figlio sinistro del nodo corrente al nodo predecessore in ordine inverso. Il nodo corrente viene aggiornato al nodo figlio destro del nodo corrente
2.Codice
soluzione di classe { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); se (radice == null) { restituire ris; } TreeNode p1 = radice, p2 = null; //p1 nodo corrente, p2 nodo predecessore mentre (p1!= nullo) { p2 = p1.sinistra; se (p2!= nullo) { while (p2.destra!= null && p2.destra!= p1) { p2 = p2.destra; } se (p2.destra == null) { p2.destra = p1; p1 = p1.sinistra; Continua; } altro { p2.destra = nullo; addPath(res, p1.sinistra); } } p1 = p1.destra; } addPath(res, root); restituire ris; } public void addPath(List<Integer> res, nodo TreeNode) { conteggio intero = 0; while (nodo!= null) { contare; res.add(nodo.val); nodo = nodo.destra; } //Inverte i nodi appena aggiunti int sinistra = res.size() - conteggio, destra = res.size() - 1; mentre (sinistra < destra) { int temp = res.get(sinistra); res.set(sinistra, res.get(destra)); res.set(destra, temp); Sinistra ; Giusto--; } } }
2.1 Pitone
Soluzione di classe: def postorderTraversal(self, root: TreeNode) -> List[int]: def addPath(nodo: TreeNode): conteggio = 0 mentre nodo: conteggio = 1 res.append(nodo.val) nodo = nodo.destra #Inverti i nodi appena aggiunti i, j = len(res) - conteggio, len(res) - 1 mentre io < j: res[i], res[j] = res[j], res[i] #Non sono richieste variabili intermedie io=1 j -= 1 se non root: lista restituita() res = lista() p1 = radice mentre p1: p2 = p1.left #p2 nodo predecessore se p2: mentre p2.right e p2.right != p1: p2 = p2.destra se non p2.right: p2.destra = p1 p1 = p1.sinistra Continua altro: p2.right = Nessuno addPath(p1.left) p1 = p1.destra aggiungiPercorso(radice) ritorno ris
3. Complessità
Tempo: O(n), Spazio: O(1)
102.Attraversamento dell'ordine dei livelli di un albero binario
0.Titolo
Restituisce i valori dei nodi dell'attraversamento dell'ordine dei livelli dell'albero binario e i nodi a ciascun livello sono posizionati insieme.
1. Idee
1. Il metodo più semplice consiste nell'utilizzare una tupla (nodo, livello) per rappresentare lo stato, che rappresenta un nodo e il livello in cui si trova. Il valore del livello di ciascun nodo appena inserito è il valore del livello del nodo genitore più uno . Infine, i punti vengono classificati in base al livello di ciascun punto. Durante la classificazione, possiamo utilizzare una tabella hash per mantenere un array con level come chiave e il valore del nodo corrispondente come valore. Dopo aver completato la ricerca in ampiezza, premi il livello chiave per recuperare tutto il valore, basta formare la risposta e restituirla
2. Considera come ottimizzare l'overhead di spazio: come implementare questa funzione senza utilizzare la mappatura hash e utilizzando solo un nodo variabile per rappresentare lo stato.
3. Modifica BFS in modo intelligente: innanzitutto, l'elemento root viene aggiunto alla coda. Quando la coda non è vuota, trova la lunghezza Si della coda corrente, quindi prendi gli elementi Si dalla coda per espandersi, quindi inserisci il. successiva iterazione.
4. La differenza tra questo e BFS è che BFS richiede solo un elemento alla volta per espandersi, mentre qui richiede ogni volta elementi Si. Nell'iterazione i-esima del processo di cui sopra, elementi Si dell'i-esimo livello di si ottiene l'albero binario.
2.Codice
soluzione di classe { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>(); se (radice == null) { ritorno; } Queue<TreeNode> coda = new LinkedList<TreeNode>(); coda.offer(root);//Aggiungi l'elemento specificato alla fine di questo elenco (l'ultimo elemento) while (!queue.isEmpty()) { Livello List<Integer> = new ArrayList<Integer>(); int dimensioneLivellocorrente = coda.dimensione(); //Rimuove dalla coda tutti gli elementi nella coda corrente for (int i = 1; i <= dimensionelivellocorrente; i) { Nodo TreeNode = coda.poll(); livello.add(nodo.val); if (nodo.sinistra!= null) { coda.offerta(nodo.sinistra); } if (nodo.destra!= null) { coda.offerta(nodo.destra); } } ret.add(livello); } ritorno; } }
2.1 Pitone
Soluzione di classe: def livelloOrdine(self, root: TreeNode) -> Lista[Lista[int]]: se non root: return [] # In casi speciali, ritorna direttamente se root è vuoto dalle raccolte import deque # Quello che segue è il contenuto del modello BFS. La chiave di BFS è l'uso delle code. strato = deque() layer.append(root) # Spinge il nodo iniziale res = [] # Set di risultati mentre strato: cur_layer = [] # Variabile temporanea, registra i nodi del layer corrente for _ in range(len(layer)): # Attraversa i nodi di un certo layer node = layer.popleft() # Estrae il nodo da elaborare cur_layer.append(node.val) if node.left: # Se il nodo corrente ha nodi sinistro e destro, inserirli nella coda Secondo il significato della domanda, prestare attenzione ai nodi sinistro e poi a quello destro. layer.append(nodo.sinistra) se nodo.destra: layer.append(nodo.destra) res.append(cur_layer) # Dopo che tutti i nodi di un determinato livello sono stati elaborati, inserisce i risultati del livello corrente nel set di risultati ritorno ris
Amici che usano altre lingue, per essere sicuri, il valore di len(layer) dovrebbe essere salvato prima dell'attraversamento, perché anche gli elementi vengono aggiunti al livello durante l'attraversamento e il valore del livello cambierà in questo momento. Forse Python è un linguaggio speciale, infatti quando viene chiamato for _ in range(len(layer)), il numero di loop è stato determinato in base alla sequenza corrispondente generata e la modifica nella lunghezza del layer nel loop verrà modificata. non hanno alcun impatto se viene utilizzato un ciclo while. len(layer) verrà aggiornato ogni volta
3. Complessità
Complessità temporale: ogni punto entra ed esce dalla coda una volta, quindi la complessità temporale asintotica è O(n)
Complessità dello spazio: il numero di elementi nella coda non supera n, quindi la complessità dello spazio asintotico è O(n)
104.La profondità massima di un albero binario
1. Ricorsione
1. Idee
1. Se conosciamo la profondità massima l e r del sottoalbero sinistro e del sottoalbero destro, allora la profondità massima dell'albero binario è max(l,r) 1
2. La profondità massima del sottoalbero sinistro e del sottoalbero destro può essere calcolata allo stesso modo. Pertanto, quando calcoliamo la profondità massima dell'albero binario corrente, possiamo prima calcolare ricorsivamente la profondità massima del suo sottoalbero sinistro e del sottoalbero destro, quindi calcolare la profondità massima dell'albero binario corrente in tempo O(1). La ricorsione termina quando viene raggiunto un nodo vuoto
2.Codice
soluzione di classe { public int maxDepth(radice TreeNode) { if(root==null) return 0;//Quando l'ultimo 1 è un nodo foglia, l'altezza verrà restituita a 1 return Math.max(maxDepth(root.left),maxDepth(root.right)) 1; } }
2.1 Pitone
Soluzione di classe: def maxDepth(self, root: TreeNode) -> int: se non root: restituisce 0 lefth = self.maxDepth(root.left) #Devi usare self.self quando chiami te stesso destra = self.maxProfondità(root.destra) return max(sinistra,destra) 1
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi dell'albero binario. Ogni nodo viene attraversato una sola volta nella ricorsione
Complessità spaziale: O(altezza), dove altezza rappresenta l'altezza dell'albero binario. Le funzioni ricorsive richiedono spazio nello stack e lo spazio nello stack dipende dalla profondità della ricorsione, quindi la complessità dello spazio è equivalente all'altezza dell'albero binario
2. Ricerca in ampiezza
1. Idee
La coda della ricerca in ampiezza memorizza "tutti i nodi dello strato corrente". Ogni volta che espandiamo il livello successivo, a differenza della ricerca in ampiezza, che elimina dalla coda solo un nodo alla volta, dobbiamo eliminare tutti i nodi della coda per l'espansione, in modo da garantire che la coda venga espansa ogni volta vengono memorizzati tutti i nodi del livello corrente, ovvero espandiamo livello per livello Infine, utilizziamo una variabile ans per mantenere il numero di espansioni. La profondità massima dell'albero binario è ans
2. Algoritmo
soluzione di classe { public int maxDepth(radice TreeNode) { se (radice == null) { restituire 0; } Queue<TreeNode> coda = new LinkedList<TreeNode>(); coda.offerta(root); int ans = 0; while (!queue.isEmpty()) { int dimensione = coda.dimensione(); while (size > 0) { //opera uno strato di elementi alla volta Nodo TreeNode = coda.poll(); if (nodo.sinistra!= null) { coda.offerta(nodo.sinistra); } if (nodo.destra!= null) { coda.offerta(nodo.destra); } misurare--; } ans ;//Dopo aver completato un livello di operazione, il numero di livelli è 1 } ritorno e; } }
2.1 Pitone
collezioni di importazione Soluzione di classe: def maxDepth(self, root: TreeNode) -> int: se non root: restituisce 0 coda = collezioni.deque() coda.append(root) ans = 0 durante la coda: as = 1 for _ in range(len(queue)): #Attraversa ogni volta gli elementi di un livello nodo = coda.popleft() se nodo.sinistra: coda.append(nodo.sinistra) se nodo.destra: coda.append(nodo.destra) ritorno es
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero binario. Stessa analisi del metodo 1, ogni nodo verrà visitato una sola volta
Complessità spaziale: il consumo di spazio di questo metodo dipende dal numero di elementi memorizzati nella coda, che nel caso peggiore raggiungerà O(n)
226. Inverti un albero binario
1. Ricorsione
1. Pensieri
Iniziamo dal nodo radice, attraversiamo ricorsivamente l'albero e iniziamo prima a sfogliare dai nodi foglia. Se i sottoalberi sinistro e destro della radice del nodo attualmente attraversato sono stati invertiti, allora dobbiamo solo scambiare le posizioni dei due sottoalberi per completare l'inversione dell'intero sottoalbero con radice come nodo radice.
2.Codice
soluzione di classe { public TreeNode invertTree(TreeNode root) { se (radice == null) { restituire null; } //Attraversa l'albero in modo ricorsivo e inizia prima a sfogliare dai nodi foglia TreeNode sinistra = invertTree(root.left); TreeNode destra = invertTree(root.right); radice.sinistra = destra; radice.destra = sinistra; restituisce radice; } }
2.1 pitone
Soluzione di classe: def invertTree(self, root: TreeNode) -> TreeNode: se non root: restituisce radice #Attraversa l'albero in modo ricorsivo e inizia prima a sfogliare dai nodi foglia sinistra = self.invertTree(root.sinistra) destra = self.invertTree(root.right) radice.sinistra, radice.destra = destra, sinistra restituisce radice
3. Complessità
Complessità temporale: O(N), dove N è il numero di nodi dell'albero binario. Attraverseremo ogni nodo dell'albero binario e per ogni nodo scambieremo i suoi due sottoalberi in tempo costante
Complessità spaziale: O(N). Lo spazio utilizzato è determinato dalla profondità dello stack di ricorsione, che è uguale all'altezza del nodo corrente nell'albero binario. In circostanze medie, l'altezza di un albero binario ha una relazione logaritmica con il numero di nodi, che è O(logN). Nel caso peggiore, l’albero forma una catena e la complessità spaziale è O(N)
96. Numero di diversi alberi binari di ricerca
1. Programmazione dinamica
0.Titolo
Dato un intero n, quanti alberi di ricerca binari ci sono con 1...n come nodi?
1. Idee
1. Attraversa ogni numero i, usa il numero come radice dell'albero, usa la sequenza 1⋯(i−1) come sottoalbero sinistro e usa la sequenza (i 1)⋯n come sottoalbero destro. Quindi possiamo costruire ricorsivamente il sottoalbero sinistro e il sottoalbero destro nello stesso modo
2. Poiché i valori della radice sono diversi, possiamo garantire che ciascun albero di ricerca binario sia unico
3. Il problema originale può essere scomposto in due sottoproblemi più piccoli e le soluzioni ai sottoproblemi possono essere riutilizzate. La programmazione dinamica può essere utilizzata per risolvere questo problema
4. Possiamo definire due funzioni: G(n): Il numero di diversi alberi binari di ricerca che possono essere formati da una sequenza di lunghezza n. F(i,n): il numero di diversi alberi binari di ricerca con i come radice e lunghezza della sequenza n (1≤i≤n). Si può vedere che G(n) è la funzione che dobbiamo risolvere
5. Il numero totale di diversi alberi binari di ricerca G(n) è la somma di F(i,n) che attraversa tutti i(1≤i≤n)
6. Per i casi limite, quando la lunghezza della sequenza è 1 (solo la radice) o 0 (albero vuoto), c'è solo una situazione, vale a dire: G(0)=1, G(1)=1
7. Seleziona il numero i come radice, quindi l'insieme di tutti gli alberi di ricerca binari con la radice i è il prodotto cartesiano dell'insieme del sottoalbero sinistro e dell'insieme del sottoalbero destro
8. Combina le due formule:
2.Codice
soluzione di classe { public int numTrees(int n) { int[] G = nuovo int[n 1]; G[0] = 1; G[1] = 1; for (int i = 2; i <= n; i) { for (int j = 1; j <= i; j) { G[i] = G[j - 1] * G[i - j]; } } ritorna G[n]; } }
2.1 Pitone
Soluzione di classe: def numAlberi(self, n): G = [0]*(n 1) #Lista di lunghezza n 1 G[0], G[1] = 1, 1 for i in range(2, n 1): #Traversa da G[2] a n for j in range(1, i 1): #Formula di somma corrispondente da 1 a n G[i] = G[j-1] * G[i-j] restituire G[n]
3. Complessità
Complessità temporale: O(n^2), dove n rappresenta il numero di nodi nell'albero di ricerca binario. La funzione G(n) ha un totale di n valori che devono essere risolti. Ciascuna soluzione richiede una complessità temporale O(n), quindi la complessità temporale totale è O(n^2).
Complessità spaziale: O(n). Abbiamo bisogno di spazio O(n) per memorizzare l'array G
2. Numero catalano
1. Idee
1. Il valore della funzione G(n) derivata nel metodo 1 è matematicamente chiamato numero catalano Cn
2.Codice
soluzione di classe { public int numTrees(int n) { // Suggerimento: qui è necessario utilizzare il tipo lungo per evitare l'overflow durante il calcolo. lungo C = 1; for (int i = 0; i < n; i) { C = C * 2 * (2 * i 1) / (i 2); } ritorno (int) C; } }
2.1 Pitone
classe Soluzione(oggetto): def numAlberi(self, n): C=1 per i nell'intervallo(0, n): C = C * 2*(2*i 1)/(i 2) ritorno int(C)
3. Complessità
Complessità temporale: O(n), dove n rappresenta il numero di nodi nell'albero di ricerca binario. Dobbiamo eseguirlo in loop solo una volta
Complessità spaziale: O(1). Abbiamo solo bisogno di spazio costante per memorizzare diverse variabili
95. Diverse combinazioni di alberi di ricerca binari
1. Ricorsione
0.Titolo
Dato un numero intero n, genera tutti gli alberi di ricerca binari costituiti dai nodi 1...n
1. Idee
1. Supponiamo che la lunghezza della sequenza corrente sia n, e se enumeriamo il valore del nodo radice come i, allora in base alle proprietà dell'albero di ricerca binario, possiamo sapere che l'insieme dei valori del nodo del il sottoalbero di sinistra è [1...i−1] e l'insieme dei valori dei nodi del sottoalbero di destra è [1...i−1]. L'insieme dei valori dei nodi dell'albero è [ io 1…n]. Rispetto al problema originale, la generazione del sottoalbero sinistro e del sottoalbero destro è un sottoproblema con lunghezza di sequenza ridotta, quindi possiamo pensare di utilizzare metodi ricorsivi per risolvere questo problema.
2. Definire la funzione generateTrees(start, end) per indicare che il valore corrente impostato è [start, end] e restituire tutti gli alberi di ricerca binari possibili generati dalla sequenza [start, end]. Secondo l'idea di cui sopra, consideriamo che il valore ii nell'enumerazione [inizio, fine] è la radice dell'albero binario di ricerca corrente, quindi la sequenza è divisa in due parti: [inizio, i−1] e [i 1 , FINE]. Chiamiamo queste due parti in modo ricorsivo, vale a dire generateTrees(start, i - 1) e generateTrees(i 1, end), per ottenere tutti i sottoalberi sinistri fattibili e i sottoalberi destri fattibili. Quindi, nell'ultimo passaggio, dobbiamo solo raccogliere i sottoalberi sinistri fattibili sottoalberi dai possibili sottoalberi di sinistra Selezionane uno, quindi selezionane uno dall'insieme di sottoalberi di destra possibili e uniscilo al nodo radice e inserisci l'albero di ricerca binario generato nell'array di risposte.
3. Il punto di ingresso della ricorsione è generateTrees(1, n), e il punto di uscita è quando \textit{start}>\textit{end}start>end, l'albero di ricerca binario corrente è vuoto, restituisce semplicemente un nodo vuoto.
2.Codice
Soluzione di classe: def generateTrees(self, n: int) -> Lista[NodoAlbero]: def generareAlberi(inizio, fine): se inizio > fine: ritorno [Nessuno,] tutti gli alberi = [] for i in range(start, end 1): # Enumera i nodi radice ammissibili #Completa prima la costruzione ricorsiva dei sottoalberi sinistro e destro, quindi utilizza l'insieme completo dei sottoalberi sinistro e destro per costruire un albero completo leftTrees = generateTrees(start, i - 1) # Ottieni l'insieme di tutti i sottoalberi a sinistra possibili rightTrees = generateTrees(i 1, end) # Ottieni l'insieme di tutti i sottoalberi destri possibili # Seleziona un sottoalbero sinistro dal set di sottoalberi sinistro, seleziona un sottoalbero destro dal set di sottoalberi destro e uniscili al nodo radice per l in leftTrees: #Relazione del prodotto di Cartesio per r in rightTrees: currTree = TreeNode(i) #Nodo radice currTree.sinistra = l currTree.right = r allTrees.append(currTree) restituisce tutti gli alberi return generateTrees(1, n) if n else []
3. Complessità
Complessità temporale: la complessità temporale dell'intero algoritmo dipende dal "numero di alberi binari di ricerca fattibili" e il numero di alberi binari di ricerca generati per n punti è equivalente all'ennesimo "numero di Cattleya" in matematica, utilizzando G_n mezzi. I lettori sono invitati a verificare da soli i dettagli specifici del numero Cattleya. Non entrerò qui nei dettagli e fornirò solo la conclusione. La generazione di un albero di ricerca binario richiede una complessità temporale O(n). Ci sono G_n alberi di ricerca binari in totale, ovvero O(nG_n).
Complessità spaziale: ci sono Gn alberi di ricerca binari generati da n punti, ogni albero ha n nodi, quindi lo spazio di archiviazione richiede O(nG_n)
617.Unisci alberi binari
1. Ricerca in profondità
0.Titolo
Dati due alberi binari, immagina che quando ne sovrapponi uno all'altro, alcuni dei nodi dei due alberi binari si sovrappongono. È necessario unirli in un nuovo albero binario. La regola della fusione è che se due nodi si sovrappongono, i loro valori vengono aggiunti come nuovo valore dopo l'unione del nodo. Altrimenti, il nodo che non è NULL verrà utilizzato direttamente come nodo del nuovo albero binario.
1. Idee
1. Attraversare due alberi binari contemporaneamente partendo dal nodo radice e unire i nodi corrispondenti I nodi corrispondenti dei due alberi binari possono avere le seguenti tre situazioni e utilizzare metodi di unione diversi per ciascuna situazione.
2. Se i nodi corrispondenti dei due alberi binari sono vuoti, anche i nodi corrispondenti dell'albero binario unito saranno vuoti.
3. Se solo uno dei nodi corrispondenti dei due alberi binari è vuoto, il nodo corrispondente dell'albero binario unito sarà il nodo non vuoto.
4. Se i nodi corrispondenti dei due alberi binari non sono vuoti, il valore del nodo corrispondente dell'albero binario unito è la somma dei valori dei nodi corrispondenti dei due alberi binari In questo momento, i due i nodi devono essere uniti esplicitamente.
5. Dopo aver unito un nodo, è necessario unire rispettivamente i sottoalberi sinistro e destro del nodo. Questo è un processo ricorsivo
2.Codice
soluzione di classe { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { se (t1 == nullo) { restituire t2; } se (t2 == nullo) { restituire t1; } //Prima unisci il nodo corrente, quindi ricorsivi i suoi sottoalberi sinistro e destro TreeNode unito = nuovo TreeNode(t1.val t2.val); merged.left = mergeTrees(t1.left, t2.left); merged.right = mergeTrees(t1.right, t2.right); ritorno unito; } }
2.1 Pitone
Soluzione di classe: def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode: se non t1: restituire t2 se non t2: restituire t1 #Prima di tutto unisci il nodo corrente, quindi ricorsivi i suoi sottoalberi sinistro e destro uniti = TreeNode(t1.val t2.val) merged.left = self.mergeTrees(t1.left, t2.left) merged.right = self.mergeTrees(t1.right, t2.right) restituire unito
3. Complessità
Complessità temporale: O(min(m,n)), dove m e n sono rispettivamente il numero di nodi dei due alberi binari. La ricerca approfondita viene eseguita su due alberi binari contemporaneamente. Solo quando i nodi corrispondenti nei due alberi binari non sono vuoti, il nodo verrà unito esplicitamente. Pertanto, il numero di nodi visitati non supererà il numero di albero binario più piccolo
Complessità spaziale: O(min(m,n)), dove m e n sono rispettivamente il numero di nodi dei due alberi binari. La complessità dello spazio dipende dal numero di livelli di chiamate ricorsive. Il numero di livelli di chiamate ricorsive non supererà l'altezza massima dell'albero binario più piccolo. Nel peggiore dei casi, l'altezza dell'albero binario è uguale al numero di nodi .
173.Iteratore dell'albero di ricerca binario
1. Appiattire l'albero di ricerca binario
0.Titolo
Implementare un iteratore dell'albero di ricerca binario. Inizializzerai l'iteratore utilizzando il nodo radice dell'albero di ricerca binario. La chiamata a next() restituirà il numero più piccolo successivo nell'albero di ricerca binaria
1. Idee
1. Il modo più semplice per implementare un iteratore è su un'interfaccia contenitore simile ad un array. Se abbiamo un array, abbiamo solo bisogno di un puntatore o indice per implementare facilmente le funzioni next() e hasNext()
2. Utilizzare un array aggiuntivo ed espandere l'albero di ricerca binario per memorizzarlo. Vogliamo che gli elementi dell'array siano ordinati in ordine crescente, quindi dovremmo eseguire un attraversamento in ordine dell'albero di ricerca binario e quindi costruire una funzione iteratore nell'array
3. Una volta che tutti i nodi sono nell'array, abbiamo solo bisogno di un puntatore o indice per implementare le funzioni next() e hasNext. Ogni volta che viene chiamato hasNext(), dobbiamo solo verificare se l'indice raggiunge la fine dell'array. Ogni volta che viene chiamato next(), restituiamo semplicemente l'elemento puntato dall'indice e andiamo avanti di un passo per simulare l'avanzamento dell'iteratore
2. Algoritmo
classe BSTIteratore { ArrayList<Integer> nodesSorted; indice intero; pubblico BSTIterator(radice TreeNode) { this.nodesSorted = new ArrayList<Integer>(); this.indice = -1; this._inorder(root); } private void _inorder(radice TreeNode) { if (root == null) ritorno; this._inorder(root.left); this.nodesSorted.add(root.val); this._inorder(root.right); } pubblico int successivo() { restituisce this.nodesSorted.get( this.index); } booleano pubblico hasNext() { restituisce this.index 1 < this.nodesSorted.size(); } }
2.1 Pitone
classe BSTIteratore: self.nodi_ordinati = [] indice.self = -1 self._inorder(radice) def __init__(self, root: TreeNode): self.nodi_ordinati = [] self.indice = -1 self._inorder(radice) def _inorder(self, root): se non root: ritorno self._inorder(root.left) self.nodes_sorted.append(root.val) self._inorder(root.right) def successivo(self) -> int: self.indice = 1 restituisce self.nodi_ordinati[self.indice] def hasNext(self) -> bool: return self.index 1 < len(self.nodes_sorted)
3. Complessità
Complessità temporale: il tempo impiegato per costruire l'iteratore è O(N). L'enunciazione del problema richiede solo di analizzare la complessità delle due funzioni, ma quando si implementa la classe, dobbiamo prestare attenzione anche al tempo richiesto per inizializzare l'oggetto della classe. ; in questo caso Sotto, la complessità temporale è correlata linearmente al numero di nodi nell'albero di ricerca binario: next(): O(1), hasNext(): O(1)
Complessità spaziale: O(N), poiché abbiamo creato un array per contenere tutti i valori dei nodi nell'albero di ricerca binario, che non soddisfa i requisiti nella dichiarazione del problema, la complessità spaziale massima di qualsiasi funzione dovrebbe essere O(h) , dove h si riferisce all'altezza dell'albero Per un albero di ricerca binario bilanciato, l'altezza è solitamente logN
2. Ricorsione controllata
1. Idee
1. Il metodo utilizzato in precedenza ha una relazione lineare nello spazio con il numero di nodi nell'albero binario di ricerca. Tuttavia, il motivo per cui dobbiamo utilizzare questo approccio è che possiamo eseguire l'iterazione sull'array e non possiamo mettere in pausa la ricorsione per poi avviarla ad un certo punto. Tuttavia, se possiamo simulare la ricorsione controllata con attraversamento in ordine, in realtà non abbiamo bisogno di utilizzare altro spazio oltre a quello sullo stack utilizzato per simulare la ricorsione
2. Utilizzare un metodo iterativo per simulare l'attraversamento in ordine invece di un metodo ricorsivo, nel fare ciò, possiamo facilmente implementare le chiamate di queste due funzioni invece di utilizzare altro spazio aggiuntivo;
3. Inizializzare uno stack vuoto S, utilizzato per simulare l'attraversamento in ordine di un albero di ricerca binario. Per l'attraversamento in ordine utilizziamo lo stesso metodo di prima, tranne per il fatto che ora utilizziamo il nostro stack invece dello stack del sistema. Poiché utilizziamo una struttura dati personalizzata, la ricorsione può essere messa in pausa e ripresa in qualsiasi momento
4. Deve essere implementata anche una funzione di supporto, che verrà richiamata più e più volte durante l'implementazione. Questa funzione è chiamata _inorder_left e aggiunge allo stack tutti i figli sinistri di un dato nodo finché il nodo non ha figli sinistri.
5. Quando la funzione next() viene chiamata per la prima volta, deve essere restituito l'elemento minimo dell'albero binario di ricerca, quindi simuliamo la ricorsione e dobbiamo andare avanti di un passo, cioè spostarci all'elemento minimo successivo di l'albero binario di ricerca. La parte superiore dello stack contiene sempre l'elemento restituito dalla funzione next(). hasNext() è facile da implementare poiché dobbiamo solo verificare se lo stack è vuoto
6. Innanzitutto, dato il nodo radice dell'albero di ricerca binario, chiamiamo la funzione _inorder_left, che garantisce che la parte superiore dello stack contenga sempre l'elemento restituito dalla funzione next()
7. Supponiamo di chiamare next(), dobbiamo restituire il successivo elemento più piccolo nell'albero di ricerca binario, che è l'elemento in cima allo stack. Ci sono due possibilità: Uno è che il nodo in cima allo stack è un nodo foglia. Questo è lo scenario migliore perché non dobbiamo fare altro che estrarre il nodo dallo stack e restituirne il valore. Quindi questa è un'operazione a tempo costante. Un altro caso è quando il nodo in cima allo stack possiede il nodo giusto. Non abbiamo bisogno di controllare il nodo sinistro perché è già stato aggiunto allo stack. L'elemento superiore dello stack non ha un nodo sinistro oppure il nodo sinistro è stato elaborato. Se c'è un nodo destro in cima allo stack, allora dobbiamo chiamare la funzione helper sul nodo destro. La complessità temporale dipende dalla struttura dell'albero
2. Algoritmo
classe BSTIteratore { Pila<NodoAlbero> pila; pubblico BSTIterator(radice TreeNode) { this.stack = new Stack<TreeNode>();//Usa lo stack per simulare la ricorsione this._leftmostInorder(root);//L'algoritmo inizia con la chiamata della funzione helper } //Funzione di supporto, aggiunge tutti i nodi secondari di sinistra allo stack private void _leftmostInorder(radice TreeNode) { mentre (radice!= null) { this.stack.push(root); radice = radice.sinistra; } } pubblico int successivo() { TreeNode topmostNode = this.stack.pop();//L'elemento superiore dello stack è l'elemento più piccolo //Mantieni l'elemento superiore dello stack come l'elemento più piccolo Se il nodo ha un figlio destro, chiama la funzione di aiuto if (topmostNode.right!= null) { this._leftmostInorder(topmostNode.right); } restituisce topmostNode.val; } booleano pubblico hasNext() { restituisce this.stack.size() > 0; } }
2.1 Pitone
classe BSTIteratore: def __init__(self, root: TreeNode): self.stack = [] #Utilizza lo stack per simulare la ricorsione self._leftmost_inorder(root) #L'algoritmo inizia con la chiamata della funzione helper Funzione #Help, aggiungi tutti i nodi secondari di sinistra allo stack def _leftmost_inorder(self, root): mentre root: self.stack.append(root) radice = radice.sinistra def successivo(self) -> int: topmost_node = self.stack.pop() #L'elemento in cima allo stack è l'elemento più piccolo #Mantieni l'elemento superiore dello stack come l'elemento più piccolo Se il nodo ha un figlio destro, chiama la funzione helper se topmost_node.right: self._leftmost_inorder(topmost_node.right) restituisce topmost_node.val def hasNext(self) -> bool: restituisce len(self.stack) > 0
3. Complessità
Complessità temporale: hasNext(): se ci sono ancora elementi nello stack, restituisce true, altrimenti restituisce false. Quindi questa è un'operazione O(1). next(): contiene due passaggi principali. Uno è quello di estrarre un elemento dallo stack, che è l'elemento più piccolo successivo. Questa è un'operazione O(1). Tuttavia, dobbiamo chiamare la funzione helper _inorder_left, che deve essere ricorsiva, aggiungendo il nodo sinistro allo stack, che è un'operazione temporale lineare, O(N) nel caso peggiore. Ma lo chiamiamo solo sul nodo che contiene il nodo giusto e non sempre elaborerà N nodi. Solo se abbiamo un albero inclinato ci saranno N nodi. Pertanto, la complessità temporale media di questa operazione è ancora O(1), il che è in linea con i requisiti del problema.
Complessità spaziale: O(h), utilizzando uno stack per simulare la ricorsione
1130. Costo minimo dell'albero di copertura dei valori foglia ×
1. Programmazione dinamica
0.Titolo
Dato un array di interi positivi arr, consideriamo tutti gli alberi binari che soddisfano le seguenti condizioni: Ogni nodo ha 0 o 2 nodi figli. I valori nell'array arr corrispondono uno a uno ai valori di ciascun nodo foglia nell'attraversamento in ordine dell'albero. Il valore di ciascun nodo non foglia è uguale al prodotto del valore massimo del nodo foglia nel suo sottoalbero sinistro e nel sottoalbero destro. In tutti questi alberi binari, restituisce la più piccola somma possibile dei valori di ciascun nodo non foglia
1. Idee
Il nocciolo di questa domanda è: Devi sapere che l'attraversamento in ordine determina che tutti gli elementi di sinistra (incluso se stesso) dell'elemento k-esimo nell'array arr (0...n-1) sono nel sottoalbero di sinistra e i suoi elementi di destra sono tutti in il sottoalbero destro e in questo momento il prodotto dei valori massimi selezionati dai sottoalberi sinistro e destro è la radice in questo momento, che è il nodo non foglia menzionato nella domanda, quindi possiamo supporre che da da i a j, la somma minima può essere: k in questo momento Il sottoproblema prodotto del valore massimo degli elementi sui lati sinistro e destro di k è il valore minimo del sottoproblema k sulla sinistra (i, k), il valore minimo del lato destro di k cifre (k 1, j), Cioè: dp[i][j]=min(dp[i][j], dp[i][k] dp[k 1][j] max[i][k]*max[k 1][ J ]) Questa domanda è la stessa di leetcode1039
2.Codice
soluzione di classe { public int mctFromLeafValues(int[] arr) { int n = arr.lunghezza; //Trova il valore massimo degli elementi in arr da i a j e memorizzalo in max[i][j]. i e j possono essere uguali. int[][]max = nuovo int[n][n]; for (int j=0;j<n;j) { intvaloremax = arr[j]; for (int i=j;i>=0;i--) { valoremax = Math.max(valoremax, arr[i]); max[i][j] = valoremax; } } int[][] dp = nuovo int[n][n]; for (int j=0; j<n; j ) { for (int i=j; i>=0; i--) { //k è un valore compreso tra i e j, i<=k<j int min = intero.MAX_VALUE; for (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;//Mantieni sempre la somma minima tra i e j } } } ritorna dp[0][n-1]; } }
2.1 Pitone
Soluzione di classe: def mctFromLeafValues(self, arr: Lista[int]) -> int: n = len(arr) dp = [[float('inf') for _ in range(n)] for _ in range(n)] # Imposta il valore iniziale al massimo maxval = [[0 for _ in range(n)] for _ in range(n)] # Imposta il valore massimo della query dell'intervallo iniziale su 0 for i in range(n):# Trova l'elemento più grande nell'intervallo [i, j] per j nell'intervallo(i, n): maxval[i][j] = max(arr[i:j 1]) for i in range(n):# I nodi foglia non partecipano al calcolo dp[i][i] = 0 for l in range(1, n): # Lunghezza dell'intervallo di enumerazione for s in range(n - l): # Enumera il punto iniziale dell'intervallo for k in range(s, s l):# Enumera e divide due sottoalberi, k è un valore compreso tra s e 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]) ritorna dp[0][n - 1]
3. Complessità
Tempo: O(n^3), Spazio: O(n^2)
2. Decrementare lo stack
0.Collegamento
1. Idee
1. Se si desidera ridurre al minimo il valore mct, i nodi foglia con valori più piccoli dovrebbero essere posizionati il più in basso possibile e i nodi foglia con valori più grandi dovrebbero essere posizionati il più in alto possibile. Perché i nodi foglia in basso servono per la moltiplicazione più volte. Ciò determina che dobbiamo trovare un valore minimo. Puoi trovare un valore minimo mantenendo uno stack monotonicamente decrescente, perché poiché è uno stack monotonicamente decrescente, il nodo a sinistra deve essere maggiore del nodo in cima allo stack e il nodo corrente (a destra) è anche maggiore del nodo in cima allo stack (perché il nodo corrente è più piccolo della cima dello stack), viene inserito direttamente nello stack)
2. Dopo aver trovato questo valore minimo, è necessario guardare a sinistra e a destra per vedere quale valore è più piccolo a sinistra o a destra. Lo scopo è posizionare il valore più piccolo il più in basso possibile.
2.Codice
soluzione di classe { public int mctFromLeafValues(int[] arr) { Stack<Intero> st = new Stack(); st.push(Integer.MAX_VALUE); int mct = 0; //Inizia a costruire uno stack decrescente Se l'elemento corrente è maggiore dell'elemento in cima allo stack, l'elemento in cima allo stack verrà estratto dallo stack e sarà possibile trovare il valore minimo. for (int i = 0; i < arr.lunghezza; i ) { while (arr[i] >= st.peek()) { //L'elemento superiore dello stack viene estratto dallo stack e combinato con gli elementi più piccoli sui lati sinistro e destro mct = st.pop() * Math.min(st.peek(), arr[i]); } st.push(arr[i]); } while (st.dimensione() > 2) { mct = st.pop() * st.peek(); } ritorno mct; } }
2.1 Pitone
Soluzione di classe: def mctFromLeafValues(self, A: Lista[int]) -> int: res, n = 0, len(A) stack = [float('inf')] #Metti il valore massimo per primo nello stack #Inizia a costruire uno stack decrescente. Se l'elemento corrente è maggiore dell'elemento superiore dello stack, l'elemento superiore dello stack verrà estratto dallo stack. È possibile trovare il valore minimo. per una in A: mentre a >= pila[-1]: mid = stack.pop() #L'elemento superiore dello stack viene estratto dallo stack e combinato con gli elementi più piccoli sui lati sinistro e destro res = mid * min(stack[-1], a) stack.append(a) while len(stack) > 2: res = stack.pop() * stack[-1] ritorno ris
3. Complessità
Tempo: O(n), Spazio: O(n)
108. Converti un array ordinato in un albero di ricerca binario bilanciato
1. Ricorsione in ordine
0.Titolo
Converte un array ordinato in ordine crescente in un albero di ricerca binario con altezza bilanciata. Un albero binario con altezza bilanciata significa che il valore assoluto della differenza di altezza tra i sottoalberi sinistro e destro di ciascun nodo di un albero binario non supera 1.
1. Idee
1. Selezionare il numero centrale come nodo radice dell'albero di ricerca binario, in modo che il numero di numeri assegnati ai sottoalberi sinistro e destro sia lo stesso o differisca solo di 1, il che può mantenere l'albero bilanciato.
2. Dopo aver determinato il nodo radice dell'albero di ricerca binario bilanciato, i numeri rimanenti si trovano rispettivamente nel sottoalbero sinistro e nel sottoalbero destro dell'albero di ricerca binario bilanciato. Anche il sottoalbero sinistro e il sottoalbero destro sono rispettivamente alberi di ricerca binari bilanciati può creare un albero di ricerca binario bilanciato in modo ricorsivo
3. Perché si può garantire che tale risultato sarà "equilibrato"? Qui puoi fare riferimento a "1382. Bilancia l'albero di ricerca binario"
4. La situazione di base della ricorsione è che l'albero di ricerca binario bilanciato non contiene alcun numero. In questo momento, l'albero di ricerca binario bilanciato è vuoto.
5. Nel caso di un dato array di sequenze attraversali in ordine, i numeri in ciascun sottoalbero devono essere continui nell'array. I numeri contenuti nel sottoalbero possono essere determinati dall'intervallo dell'indice dell'array. L'intervallo dell'indice è contrassegnato come [sinistra , destra], quando sinistra>destra, l'albero di ricerca binaria bilanciata è vuoto
2.Codice
Soluzione di classe: def sortedArrayToBST(self, nums: List[int]) -> TreeNode: def aiutante(sinistra, destra): se sinistra > destra: #Condizione di terminazione della dicotomia ritorno Nessuno mid = (sinistra destra) // 2 #Seleziona sempre il numero a sinistra della posizione centrale come nodo radice root = TreeNode(nums[mid]) #Determina prima il nodo radice, quindi ricorsivi i sottoalberi sinistro e destro root.left = helper(sinistra, metà - 1) root.right = helper(metà 1, destra) restituisce radice return helper(0, len(nums) - 1)
3. Complessità
Complessità temporale: O(n), dove n è la lunghezza dell'array. Visita ciascun numero una sola volta
Complessità spaziale: O(logn), dove n è la lunghezza dell'array. La complessità dello spazio non considera il valore restituito, quindi la complessità dello spazio dipende principalmente dalla profondità dello stack ricorsivo. La profondità dello stack ricorsivo è O(logn)
109. Conversione ordinata di elenchi concatenati in albero di ricerca binario bilanciato
1. Converti un elenco collegato ordinato in un array ordinato
1. Complessità
Tempo: O(n), Spazio: O(n)
2. I puntatori veloci e lenti dividono e conquistano
1. Pensieri
1. Impostare l'intervallo di chiusura sinistra e apertura destra
Supponiamo che l'endpoint sinistro dell'elenco collegato corrente sia a sinistra, l'endpoint destro sia a destra e che la relazione di inclusione sia "chiusa a sinistra, aperta a destra". L'elenco collegato fornito è un elenco collegato unidirezionale. È molto facile accedervi elementi successivi, ma non può accedere direttamente agli elementi precedenti. Pertanto, dopo aver trovato il nodo mediano a metà dell'elenco collegato, se si imposta la relazione "sinistra chiusa, destra aperta", è possibile utilizzare direttamente (sinistra, metà) e (mid.next, destra) per rappresentare la lista corrispondente a i sottoalberi sinistro e destro Non è necessario mid.pre e l'elenco iniziale può anche essere convenientemente rappresentato da (head, null)
2. Utilizzare i puntatori veloce e lento per trovare la mediana
Inizialmente, il puntatore fast fast e il puntatore slow slow puntano entrambi all'endpoint sinistro a sinistra dell'elenco collegato. Mentre spostiamo velocemente il puntatore veloce a destra due volte, spostiamo il puntatore lento a destra una volta finché il puntatore veloce non raggiunge il confine (cioè, il puntatore veloce raggiunge l'estremità destra o il nodo successivo del puntatore veloce è quello destro punto finale). In questo momento, l'elemento corrispondente all'indicatore lento è la mediana
3. Costruire ricorsivamente l'albero
Dopo aver trovato il nodo mediano, lo usiamo come elemento del nodo radice corrente e costruiamo ricorsivamente il sottoalbero sinistro corrispondente alla lista concatenata sul lato sinistro e il sottoalbero destro corrispondente alla lista concatenata sul lato destro.
2. Algoritmo
Soluzione di classe: def sortedListToBST(self, head: ListNode) -> TreeNode: def getMedian(sinistra: ListNode, destra: ListNode) -> ListNode: veloce=lento=sinistra while fast != right and fast.next != right:#Definisce l'intervallo come chiuso a sinistra e aperto a destra fast = fast.next.next#Il puntatore veloce si muove due volte e il puntatore lento si muove una volta lento = lento.successivo return slow #Il puntatore slow è la mediana in questo momento def buildTree(sinistra: ListNode, destra: ListNode) -> TreeNode: se sinistra == destra: ritorno Nessuno mid = getMedian(left, right) #L'intervallo è chiuso a sinistra e aperto a destra root = TreeNode(mid.val) #Determina prima il nodo radice, quindi costruisci i sottoalberi sinistro e destro root.left = buildTree(left, mid) #Non è necessario mid.pre root.right = buildTree(mid.next, destra) restituisce radice restituisce buildTree(head, None)
3. Complessità
Complessità temporale: O(nlogn), dove n è la lunghezza dell'elenco concatenato. Supponiamo che il tempo per costruire un albero di ricerca binario da un elenco concatenato di lunghezza n sia T(n) e che la formula di ricorrenza sia T(n)=2⋅T(n/2) O(n). teorema, T(n)= O(nlogn)
Complessità dello spazio: O(logn), qui viene calcolato solo lo spazio diverso dalla risposta restituita. L'altezza dell'albero binario bilanciato è O(logn), che è la profondità massima dello stack durante il processo ricorsivo, che è lo spazio richiesto.
3. Ottimizzazione trasversale in ordine diviso e conquista
1. Pensieri
1. Supponiamo che il numero dell'endpoint sinistro dell'elenco collegato corrente sia sinistro, il numero dell'endpoint destro sia destro, la relazione di inclusione sia "doppia chiusa", il numero dei nodi dell'elenco collegato sia [0, n) e l'ordine di attraversamento in ordine è "sottoalbero sinistro - nodo radice - sottoalbero destro", quindi nel processo di divisione e conquista, non dobbiamo affrettarci a trovare il nodo mediano dell'elenco collegato, ma utilizzare un nodo segnaposto e quindi inserisci il suo valore quando il nodo viene attraversato in ordine
2. Eseguire l'attraversamento di ordine medio calcolando l'intervallo numerico: il numero corrispondente al nodo mediano è mid=(sinistra destra 1)/2 e gli intervalli numerici corrispondenti ai sottoalberi sinistro e destro sono [sinistra, metà−1] e [mid 1 rispettivamente. ,right], se sinistra>destra, allora la posizione attraversata corrisponde a un nodo vuoto, altrimenti corrisponde a un nodo nell'albero binario di ricerca.
3. Conosciamo già la struttura di questo albero di ricerca binario e la domanda fornisce il risultato dell'attraversamento in ordine. Quindi dobbiamo solo eseguire un attraversamento in ordine su di esso per ripristinare l'intero albero di ricerca binario.
4. Diagramma del processo ricorsivo
2.Codice
Soluzione di classe: def sortedListToBST(self, head: ListNode) -> TreeNode: def getLength(head: ListNode) -> int: ret = 0 mentre testa: ret=1 testa = testa.successivo ritorno ret def buildTree(sinistra: int, destra: int) -> TreeNode: se sinistra > destra: #Condizione finale dell'attraversamento di ordine intermedio ritorno Nessuno metà = (sinistra destra 1) // 2 radice = TreeNode() #Utilizzare l'attraversamento in ordine, costruire prima il sottoalbero sinistro, quindi ritornare al nodo radice e quindi costruire il sottoalbero destro, iniziando dal primo elemento root.left = buildTree(sinistra, metà - 1) head non locale #può modificare la variabile esterna non globale head root.val = head.val #Inizia ad assegnare i valori dal primo nodo testa = testa.successivo root.right = buildTree(metà 1, destra) restituisce radice lunghezza = getLunghezza(testa) restituisce buildTree(0, lunghezza - 1)
3. Complessità
Complessità temporale: O(n), dove n è la lunghezza dell'elenco concatenato. Supponiamo che il tempo per costruire un albero di ricerca binario da un elenco concatenato di lunghezza n sia T(n) e che la formula di ricorrenza sia T(n)=2⋅T(n/2) O(1). teorema, T(n)= O(n)
Complessità dello spazio: O(logn), qui viene calcolato solo lo spazio diverso dalla risposta restituita. L'altezza dell'albero binario bilanciato è O(logn), che è la profondità massima dello stack durante il processo ricorsivo, che è lo spazio richiesto.
297. Serializzazione e deserializzazione di alberi binari
1. Viaggio in preordine
0.Titolo
La serializzazione è l'operazione di conversione di una struttura di dati o di un oggetto in bit continui. I dati convertiti possono quindi essere archiviati in un file o in memoria e possono anche essere trasmessi a un altro ambiente informatico attraverso la rete e ricostruiti nel modo opposto dati. Si prega di progettare un algoritmo per implementare la serializzazione e la deserializzazione degli alberi binari. La logica di esecuzione dell'algoritmo di sequenza/deserializzazione non è limitata qui. Devi solo assicurarti che un albero binario possa essere serializzato in una stringa e deserializzare la stringa nella struttura ad albero originale.
1. Idee
1. Serializzazione
1. L'attraversamento del preordine viene scelto perché l'ordine di stampa di root|left|right rende più semplice individuare il valore del nodo root durante la deserializzazione.
2. Quando incontra un nodo nullo, deve essere tradotto nel simbolo corrispondente. Solo durante la deserializzazione sa che corrisponde a null.
3. Grafico ricorsivo
2.Deserializzazione
1. Per costruire un albero, costruire prima il nodo radice. La funzione ausiliaria buildTree riceve l'array di elenco convertito dalla stringa serializzata.
2. Visualizzare a turno il primo elemento dell'elenco e costruire il nodo radice del sottoalbero corrente. Dopo l'array dell'elenco, verranno costruiti per primi il nodo radice, il sottoalbero sinistro e quello destro. Se il carattere che appare è 'X', viene restituito un nodo nullo. Se il carattere che appare non è "X", crea un nodo, costruisci ricorsivamente i suoi sottoalberi sinistro e destro e restituisce il sottoalbero corrente.
2.Codice
codice di classe: def serialize(self, root): #Preorder attraversal implementa la serializzazione se root == Nessuno: restituisce 'X,' leftserilized = self.serialize(root.left) dirittiserilizzati = self.serialize(root.right) return str(root.val) ',' leftserilized rightserilized def deserialize(self, data): dati = dati.split(',') root = self.buildTree(dati) restituisce radice def buildTree(sé, dati): val = data.pop(0) #pop il primo carattere if val == 'X': return None #Restituisce un nodo vuoto nodo = TreeNode(val) nodo.sinistra = self.buildTree(dati) nodo.destra = self.buildTree(dati) nodo di ritorno
3. Complessità
Tempo: O(n), Spazio: O(n)
100. Alberi identici
1. Innanzitutto la profondità
0.Titolo
Dati i nodi radice p e q di due alberi binari, scrivere una funzione per verificare se i due alberi sono uguali
1. Idee
1. Se entrambi gli alberi binari sono vuoti, allora i due alberi binari sono gli stessi. Se uno e solo uno dei due alberi binari è vuoto, allora i due alberi binari non devono essere uguali
2. Se entrambi gli alberi binari non sono vuoti, determinare innanzitutto se i valori dei relativi nodi radice sono uguali. Se non sono uguali, i due alberi binari devono essere diversi. Se sono uguali, determinare se i sottoalberi sinistro e destro dei due alberi binari sono gli stessi. Questo è un processo ricorsivo, quindi puoi utilizzare la ricerca approfondita per determinare ricorsivamente se due alberi binari sono uguali.
2.Codice
Soluzione di classe: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: se non p e non q: #Entrambi gli alberi sono vuoti e uguali restituire Vero elif not p o not q: #Solo uno degli alberi è vuoto, i due alberi non sono uguali restituire Falso elif p.val != q.val: restituire Falso altro: return self.isSameTree(p.left, q.left) e self.isSameTree(p.right, q.right)
3. Complessità
Complessità temporale: O(\min(m,n))O(min(m,n)), dove mm e n sono rispettivamente il numero di nodi dei due alberi binari. Eseguire una ricerca approfondita su due alberi binari contemporaneamente. Si accederà al nodo solo quando i nodi corrispondenti nei due alberi binari non saranno vuoti, quindi il numero di nodi visitati non supererà il numero di nodi nel più piccolo. albero binario.
Complessità dello spazio: O(\min(m,n))O(min(m,n)), dove mm e n sono rispettivamente il numero di nodi dei due alberi binari. La complessità dello spazio dipende dal numero di livelli di chiamate ricorsive. Il numero di livelli di chiamate ricorsive non supererà l'altezza massima dell'albero binario più piccolo. Nel peggiore dei casi, l'altezza dell'albero binario è uguale al numero di nodi .
2.Prima di tutto l'ampiezza
1. Idee
1. Determinare innanzitutto se i due alberi binari sono vuoti. Se entrambi gli alberi binari non sono vuoti, avviare la ricerca in ampiezza dai nodi radice dei due alberi binari.
2. Utilizzare due code per memorizzare rispettivamente i nodi di due alberi binari. Inizialmente, i nodi radice dei due alberi binari vengono aggiunti rispettivamente alle due code. Ogni volta viene estratto un nodo dalle due code e viene eseguita la successiva operazione di confronto. Confronta i valori di due nodi. Se i valori dei due nodi non sono uguali, i due alberi binari devono essere diversi; Se i valori di due nodi sono uguali, determinare se i nodi figli dei due nodi sono vuoti Se solo il nodo figlio sinistro di un nodo è vuoto, o il nodo figlio destro di un solo nodo è vuoto, le strutture dei due alberi binari sono diversi Pertanto i due alberi binari devono essere diversi; Se le strutture dei nodi figli dei due nodi sono le stesse, aggiungi rispettivamente i nodi figli non vuoti dei due nodi alle due code. È necessario prestare attenzione all'ordine quando si aggiungono i nodi figli alla coda i nodi figlio sinistro e destro non sono vuoti, aggiungi prima il nodo figlio sinistro, quindi aggiungi il nodo figlio destro
3. Se entrambe le code sono vuote contemporaneamente alla fine della ricerca, i due alberi binari sono gli stessi. Se solo una coda è vuota, la struttura dei due alberi binari è diversa, quindi i due alberi binari sono diversi
2.Codice
Soluzione di classe: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: se non p e non q: #Entrambi gli alberi sono vuoti e uguali restituire Vero se non p o non q: #Solo uno degli alberi è vuoto, i due alberi non sono uguali restituire Falso coda1 = collezioni.deque([p]) coda2 = collezioni.deque([q]) mentre coda1 e coda2: nodo1 = coda1.popleft() nodo2 = coda2.popleft() se nodo1.val!= nodo2.val: restituire Falso sinistra1, destra1 = nodo1.sinistra, nodo1.destra sinistra2, destra2 = nodo2.sinistra, nodo2.destra if (non left1) ^ (non left2): # ^ significa XOR, se sono diversi, sono 1, se sono uguali, sono 0 restituire Falso if (non giusto1) ^ (non giusto2): restituire Falso if left1: #Inserisci il nodo in coda quando non è vuoto coda1.append(sinistra1) se giusto1: coda1.append(right1) se lasciato2: coda2.append(sinistra2) se giusto2: coda2.append(right2) restituisce non coda1 e non coda2
3. Complessità
Complessità temporale: O(\min(m,n))O(min(m,n)), dove mm e n sono rispettivamente il numero di nodi dei due alberi binari. Eseguire una ricerca in ampiezza su due alberi binari contemporaneamente. Si accederà al nodo solo quando i nodi corrispondenti nei due alberi binari non saranno vuoti, quindi il numero di nodi visitati non supererà il numero di nodi nel più piccolo. albero binario.
Complessità dello spazio: O(\min(m,n))O(min(m,n)), dove mm e n sono rispettivamente il numero di nodi dei due alberi binari. La complessità dello spazio dipende dal numero di elementi nella coda. Il numero di elementi nella coda non supererà il numero di nodi dell'albero binario più piccolo.
105. Costruisci un albero binario da sequenze di attraversamento in preordine e inordine
1. Ricorsione
1. Idee
1. Per qualsiasi albero, la forma di attraversamento del preordine è sempre [Nodo radice, [risultato dell'attraversamento in preordine del sottoalbero sinistro], [risultato dell'attraversamento in preordine del sottoalbero destro]] Cioè, il nodo radice è sempre il primo nodo nell'attraversamento del preordine. La forma di attraversamento in ordine è sempre [ [Risultato dell'attraversamento in ordine del sottoalbero sinistro], nodo radice, [Risultato dell'attraversamento in ordine del sottoalbero destro] ]
2. Finché individuiamo il nodo radice nell'attraversamento in ordine, possiamo conoscere il numero di nodi rispettivamente nel sottoalbero sinistro e nel sottoalbero destro. Poiché le lunghezze dell'attraversamento in preordine e dell'attraversamento in ordine dello stesso sottoalbero sono ovviamente le stesse, possiamo corrispondere ai risultati dell'attraversamento in preordine e individuare tutte le parentesi sinistre e destre nella forma precedente.
3. In questo modo, conosciamo i risultati dell'attraversamento in preordine e dell'attraversamento in ordine del sottoalbero sinistro, e i risultati dell'attraversamento in preordine e dell'attraversamento in ordine del sottoalbero destro Possiamo costruire ricorsivamente il sottoalbero sinistro e il sottoalbero destro, quindi collegare questi due sottoalberi alle posizioni sinistra e destra del nodo radice
4. Ottimizzazione: quando si individua il nodo radice nell'attraversamento in ordine, un metodo semplice consiste nel scansionare direttamente l'intero risultato dell'attraversamento in ordine e trovare il nodo radice list.index(x), ma la complessità temporale di tale operazione è relativamente alto alto. Possiamo prendere in considerazione l'utilizzo di una HashMap per aiutarci a individuare rapidamente il nodo radice. Per ogni coppia chiave-valore nella mappa hash, la chiave rappresenta un elemento (il valore del nodo) e il valore rappresenta la sua posizione di occorrenza nell'attraversamento in ordine. Prima di costruire l'albero binario, possiamo scansionare l'elenco attraversato per costruire una volta questa mappa hash. Nel successivo processo di costruzione dell'albero binario, abbiamo bisogno solo di tempo O(1) per individuare il nodo radice.
2.Codice
1. Ufficialmente ottimizzato, ma ci sono molti parametri. Puoi utilizzare due parametri. Vedi la combinazione di post-ordine e metà ordine.
Soluzione di classe: def buildTree(self, preordine: Lista[int], ordine: Lista[int]) -> TreeNode: def mioBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): se preordine_sinistra > preordine_destra: ritorno Nessuno preorder_root = preorder_left # Il primo nodo nell'attraversamento del preordine è il nodo radice inorder_root = index[preorder[preorder_root]] # Individua il pedice del nodo root nell'attraversamento in ordine root = TreeNode(preorder[preorder_root]) # Per prima cosa crea il nodo root size_left_subtree = inorder_root - inorder_left # Ottieni il numero di nodi nel sottoalbero sinistro # Costruisci ricorsivamente il sottoalbero sinistro e collegalo al nodo radice # Gli elementi "size_left_subtree a partire dal bordo sinistro 1" nell'attraversamento in preordine corrispondono agli elementi "a partire dal bordo sinistro fino al posizionamento del nodo radice -1" nell'attraversamento in ordine. root.left = myBuildTree(preorder_left 1, preorder_left size_left_subtree, inorder_left, inorder_root - 1) # Costruisci ricorsivamente il sottoalbero destro e connettiti al nodo radice # Nell'attraversamento in preordine, gli elementi "dal confine sinistro 1 e il numero di nodi del sottoalbero sinistro al confine destro" corrispondono agli elementi "dal posizionamento del nodo radice 1 al confine destro" nell'attraversamento in ordine . root.right = myBuildTree(preorder_left 1 size_left_subtree, preorder_right, inorder_root 1, inorder_right) restituisce radice n = len(preordine) indice = {elemento: i per i, elemento in enumerate(inorder)} #Costruisci una mappa hash, la chiave è l'elemento, il valore è il pedice, individua rapidamente il nodo radice restituisce mioBuildTree(0, n - 1, 0, n - 1)
2. Versione semplice
Soluzione di classe: def buildTree(self, preordine: Lista[int], ordine: Lista[int]) -> TreeNode: def funzione_ricorre(inordine): x = preorder.pop(0) # Elimina ogni volta l'elemento più a sinistra dell'elenco di preordine node = TreeNode(x) # Usa questo elemento per generare un nodo idx = inorder.index(x) # Trova l'indice dell'elemento nella lista inorder left_l = inorder[:idx] # Usa questo elemento per dividere la lista in ordine destra_l = ordine[idx 1:] node.left = recur_func(left_l) se left_l altrimenti Nessuno node.right = recur_func(right_l) se right_l altrimenti Nessuno # Esplora fino alla foglia più a sinistra in basso, quindi ritorna strato per strato dal basso verso l'alto. nodo di ritorno se non in preordine o non in ordine: return None # Test vuoto restituisce funzione_ricorrenza(in ordine)
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero
Complessità dello spazio: O(n), oltre allo spazio O(n) richiesto per restituire la risposta, dobbiamo anche usare lo spazio O(n) per memorizzare la mappa hash e O(h) (dove h è l'altezza dell'albero) lo spazio rappresenta lo spazio dello stack durante la ricorsione
2.Iterazione
1. Idee
1. Usiamo uno stack e un puntatore per assistere nella costruzione dell'albero binario. Inizialmente, il nodo radice (il primo nodo dell'attraversamento in preordine) viene memorizzato nello stack e il puntatore punta al primo nodo dell'attraversamento in ordine "il nodo finale raggiunto dal nodo corrente che si sposta continuamente verso sinistra ."
1. Lo stack viene utilizzato per mantenere "tutti i nodi antenati del nodo corrente che non hanno ancora considerato il figlio giusto". In altre parole, solo i nodi nello stack possono connettersi a un nuovo figlio destro
2. Enumerare ciascun nodo nell'attraversamento del preordine tranne il primo nodo. Se l'indice punta al nodo superiore dello stack, estraiamo continuamente il nodo superiore dello stack e spostiamo l'indice a destra e utilizziamo il nodo corrente come figlio destro dell'ultimo nodo estratto
1. Per due nodi consecutivi u e v nell'attraversamento del preordine, secondo il processo di attraversamento del preordine, possiamo sapere che u e v hanno solo due possibili relazioni: v è il figlio sinistro di u. Questo perché dopo aver attraversato u, il nodo successivo attraversato è il figlio sinistro di u, che è v; u non ha un figlio sinistro e v è il figlio destro di un nodo antenato di u (o di u stesso). Se non hai un figlio sinistro, il nodo successivo attraversato è il figlio destro di te. Se u non ha un figlio destro, torneremo indietro verso l'alto finché non incontreremo il primo nodo ua che ha un figlio destro (e u non è nel sottoalbero del suo figlio destro), allora v è il figlio destro di ua
2. Quando attraversiamo 10, la situazione è diversa. Troviamo che l'indice punta semplicemente all'attuale nodo superiore 4 dello stack, il che significa che 4 non ha un figlio sinistro, quindi 10 deve essere il figlio destro di un nodo nello stack.
3. L'ordine dei nodi nello stack è coerente con l'ordine in cui appaiono nell'attraversamento del preordine e il figlio destro di ciascun nodo non è stato ancora attraversato, quindi l'ordine di questi nodi è coerente con l'ordine in cui compaiono nell'attraversamento in ordine L'ordine deve essere invertito
4. Possiamo continuare a spostare l'indice verso destra e confrontarlo con il nodo in cima allo stack. Se l'elemento corrispondente a indice è esattamente uguale al nodo superiore dello stack, significa che abbiamo trovato il nodo superiore dello stack durante l'attraversamento in ordine, quindi aumentiamo indice di 1 e facciamo apparire il nodo superiore dello stack fino al l'elemento corrispondente all'indice non è uguale al nodo superiore dello stack. Secondo questo processo, l'ultimo nodo x che visualizziamo è il nodo genitore di 10. Questo perché 10 appare tra x e l'attraversamento in ordine del nodo successivo di x nello stack, quindi 10 è il figlio giusto di x
3. Se l'indice è diverso dal nodo superiore dello stack, utilizziamo il nodo corrente come figlio sinistro del nodo superiore dello stack.
1. Attraversiamo 9. 9 deve essere il figlio sinistro del nodo 3 in cima allo stack. Usiamo la dimostrazione per assurdo, assumendo che 9 sia il figlio destro di 3, quindi 3 non ha figlio sinistro e l'indice dovrebbe semplicemente puntare a 3, ma in realtà è 4, creando così una contraddizione. Quindi prendiamo 9 come figlio sinistro di 3 e mettiamo 9 in pila
4. Non importa quale sia la situazione, alla fine inseriremo il nodo corrente nello stack
2.Codice
Soluzione di classe: def buildTree(self, preordine: Lista[int], ordine: Lista[int]) -> TreeNode: se non preordinato: ritorno Nessuno root = TreeNode(preordine[0]) pila = [radice] inorderIndex = 0 #Il nodo finale raggiunto dal nodo corrente che si sposta continuamente verso sinistra, coerente con l'attraversamento in ordine for i in range(1, len(preorder)):#Attraversa l'attraversamento del preordine a partire dal secondo nodo preordineVal = preordine[i] nodo = stack[-1] #Il nodo superiore dello stack if node.val != inorder[inorderIndex]: #Quando il nodo superiore dello stack non è uguale, è il figlio sinistro node.left = TreeNode(preorderVal) #Costruisce direttamente il figlio sinistro stack.append(node.left) #Spingi il figlio sinistro nello stack else: #Quando i nodi superiori dello stack sono uguali, è stato attraversato fino al punto più a sinistra e deve essere estratto dallo stack e quindi attraversato fino al figlio destro di un determinato nodo. while stack e stack[-1].val == inorder[inorderIndex]: node = stack.pop() #L'elemento in cima allo stack viene estratto dallo stack inorderIndex = 1 #Sostituisci l'elemento più a sinistra node.right = TreeNode(preorderVal) #Quando i due sono diversi, viene trovato il nodo genitore dell'elemento corrente stack.append(node.right) #Spinge anche l'elemento corrente nello stack restituisce radice
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero
Complessità dello spazio: O(n). Oltre allo spazio O(n) richiesto per la risposta restituita, dobbiamo anche utilizzare lo spazio O(h) (dove h è l'altezza dell'albero) per memorizzare lo stack.
106. Costruisci un albero binario da sequenze di attraversamento inordine e postordine
1. Ricorsione
1. Idee
1. L'ultimo elemento dell'array attraversato in post-ordine rappresenta il nodo radice e quindi suddiviso in attraversamenti in ordine.
2. Tieni presente che esiste una relazione di dipendenza in cui è necessario creare prima il sottoalbero destro e poi il sottoalbero sinistro. Si può capire che nell'array di attraversamento post-ordine, l'intero array memorizza prima i nodi del sottoalbero sinistro, poi i nodi del sottoalbero destro e infine il nodo radice Se si seleziona "l'ultimo nodo del post-ordine. order traversal" come radice di ogni nodo temporale, il primo costruito dovrebbe essere il sottoalbero destro
2.Codice
1.Ufficiale: due parametri
Soluzione di classe: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: def helper(in_sinistra, in_destra): if in_left > in_right:# Se non ci sono nodi qui per costruire un albero binario, finirà ritorno Nessuno val = postorder.pop()# Seleziona l'elemento nella posizione post_idx come nodo radice della sottostruttura corrente radice = TreeNode(val) indice = idx_map[val] # Divide in sottoalberi sinistro e destro in base alla posizione della radice #Il sottoalbero destro deve essere costruito per primo, perché si accede all'attraversamento post-ordine dalla fine, quindi è necessario accedere per primo al sottoalbero destro root.right = helper(indice 1, in_right) root.left = helper(in_left, indice - 1) restituisce radice # Crea una tabella hash di coppie chiave-valore (elemento, pedice). idx_map = {val:idx per idx, val in enumerate (in ordine)} return helper(0, len(inordine) - 1)
2. Versione semplice
Soluzione di classe: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: def funzione_ricorre(inordine): x = postorder.pop() # Elimina ogni volta l'elemento più a sinistra dell'elenco dell'ordine precedente node = TreeNode(x) # Usa questo elemento per generare un nodo idx = inorder.index(x) # Trova l'indice dell'elemento nella lista inorder left_l = inorder[:idx] # Usa questo elemento per dividere la lista in ordine destra_l = ordine[idx 1:] #Il sottoalbero destro deve essere costruito per primo, perché si accede all'attraversamento post-ordine dalla fine, quindi è necessario accedere per primo al sottoalbero destro node.right = recur_func(right_l) se right_l altrimenti Nessuno node.left = recur_func(left_l) se left_l altrimenti Nessuno # Esplora fino alla foglia più a sinistra in basso, quindi ritorna strato per strato dal basso verso l'alto. nodo di ritorno se non è postordinato o non ordinato: return None # Test vuoto restituisce funzione_ricorrenza(in ordine)
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero
Complessità dello spazio: O(n), oltre allo spazio O(n) richiesto per restituire la risposta, dobbiamo anche usare lo spazio O(n) per memorizzare la mappa hash e O(h) (dove h è l'altezza dell'albero) lo spazio rappresenta lo spazio dello stack durante la ricorsione
2.Iterazione
1. Idee
1. Se inverti l'attraversamento in ordine, otterrai un attraversamento in ordine inverso, ovvero attraverserai ogni volta il figlio destro, quindi attraverserai il nodo radice e infine attraverserai il figlio sinistro. Se inverti l'attraversamento post-ordine, otterrai l'attraversamento pre-ordine inverso, ovvero attraverserai ogni volta il nodo radice, quindi attraverserai il figlio destro e infine attraverserai il figlio sinistro.
2. La differenza rispetto alla domanda precedente è: tutti i figli di sinistra diventano figli di destra, tutti i figli di destra diventano figli di sinistra e l'attraversamento dell'ordine in avanti viene cambiato in attraversamento dell'ordine inverso.
2.Codice
Soluzione di classe: def buildTree(self, inorder: Lista[int], postorder: Lista[int]) -> TreeNode: se non postordina: ritorno Nessuno root = TreeNode(postordine[-1]) pila = [radice] inorderIndex = -1 #Il nodo finale raggiunto dal nodo corrente che si sposta continuamente verso destra, coerente con l'attraversamento in ordine for i in range(len(postorder)-2, -1,-1):#Attraversa l'attraversamento post-ordine a partire dal penultimo nodo postorderVal = postorder[i] nodo = stack[-1] #Il nodo superiore dello stack if node.val != inorder[inorderIndex]: #Quando il nodo superiore dello stack non è uguale, è il figlio destro node.right = TreeNode(postorderVal) #Costruisce direttamente il figlio destro stack.append(node.right) #Spingi il figlio destro nello stack else: #Quando i nodi superiori dello stack sono uguali, è stato attraversato fino al punto più a destra. Deve essere estratto dallo stack e quindi attraversato fino al figlio sinistro di un determinato nodo. while stack e stack[-1].val == inorder[inorderIndex]: node = stack.pop() #L'elemento in cima allo stack viene estratto dallo stack inorderIndex -= 1 #Sostituisci l'elemento più a destra node.left = TreeNode(postorderVal) #Quando i due sono diversi, viene trovato il nodo genitore dell'elemento corrente stack.append(node.left) #Spinge anche l'elemento corrente nello stack restituisce radice
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero
Complessità dello spazio: O(n). Oltre allo spazio O(n) richiesto per la risposta restituita, dobbiamo anche utilizzare lo spazio O(h) (dove h è l'altezza dell'albero) per memorizzare lo stack.
124. Somma massima del percorso nell'albero binario
1. Ricorsione
0.Titolo
Un percorso è definito come una sequenza che parte da qualsiasi nodo dell'albero, si collega lungo il nodo genitore-nodo figlio e raggiunge qualsiasi nodo. Lo stesso nodo appare al massimo una volta in una sequenza di percorsi. Il percorso contiene almeno un nodo e non passa necessariamente attraverso il nodo radice. La somma del percorso è la somma dei valori di ciascun nodo del percorso. Fornisce la radice del nodo radice di un albero binario e restituisce la somma del percorso massimo
1. Idee
1. Innanzitutto, considera l'implementazione di una funzione semplificata maxGain(node), che calcola il valore del contributo massimo di un nodo nell'albero binario. Nello specifico, cerca il nodo come punto iniziale nel sottoalbero con il nodo come nodo radice. un percorso che massimizza la somma dei valori dei nodi sul percorso
2. Nello specifico, la funzione viene calcolata come segue. Il valore massimo del contributo di un nodo vuoto è pari a 00. Il valore di contributo massimo di un nodo non vuoto è uguale alla somma del valore del nodo e del valore di contributo massimo nei suoi nodi figli (per i nodi foglia, il valore di contributo massimo è uguale al valore del nodo
3. Il processo di calcolo sopra è un processo ricorsivo Pertanto, chiamando la funzione maxGain sul nodo radice, è possibile ottenere il valore massimo del contributo di ciascun nodo.
4. Dopo aver ottenuto il valore massimo del contributo di ciascun nodo secondo la funzione maxGain, come ottenere la somma massima del percorso dell'albero binario? Per un nodo in un albero binario, la somma massima del percorso del nodo dipende dal valore del nodo e dal valore di contributo massimo dei nodi figlio sinistro e destro del nodo. Se il valore di contributo massimo del nodo figlio è positivo, è incluso nella somma massima dei percorsi del nodo, altrimenti non sarà incluso nella somma massima dei percorsi del nodo. Mantenere una variabile globale maxSum per memorizzare la somma massima del percorso e aggiornare il valore di maxSum durante il processo ricorsivo. Il valore finale di maxSum è la somma massima del percorso nell'albero binario.
2.Codice
Soluzione di classe: def __init__(self): self.maxSum = float("-inf") #Il valore massimo viene inizializzato su infinito negativo def maxPathSum(self, root: TreeNode) -> int: def maxGain(node): #Ottiene il valore massimo del contributo del nodo se non nodo: restituire 0 # Calcola ricorsivamente il valore massimo del contributo dei nodi figli sinistro e destro # Solo quando il valore del contributo massimo è maggiore di 0, verrà selezionato il nodo figlio corrispondente Guadagnosinistra = max(Guadagnomax(nodo.sinistra), 0) Guadagnodestro = max(Guadagnomax(nodo.destra), 0) # Prima ottieni il valore massimo del percorso con il nodo corrente come nodo radice, quindi restituisci il valore massimo del contributo del nodo #La somma massima del percorso di un nodo dipende dal valore del nodo e dal valore massimo del contributo dei nodi figli sinistro e destro del nodo priceNewpath = node.val leftGain rightGain self.maxSum = max(self.maxSum, prezzoNuovopercorso) # Aggiorna la risposta # Restituisce il valore massimo del contributo del nodo, che è determinato dal maggiore tra il valore del nodo e il sottoalbero sinistro/sottoalbero destro restituisce nodo.val max(guadagnosinistro, guadagnodestro) guadagno massimo(radice) restituisce self.maxSum
3. Complessità
Complessità temporale: O(N)O(N), dove NN è il numero di nodi nell'albero binario. Non più di 2 visite a ciascun nodo
Complessità spaziale: O(N)O(N), dove NN è il numero di nodi nell'albero binario. La complessità dello spazio dipende principalmente dal numero di livelli di chiamata ricorsiva. Il numero massimo di livelli è pari all'altezza dell'albero binario. Nel peggiore dei casi, l'altezza dell'albero binario è pari al numero di nodi dell'albero binario .
654. Albero binario massimo
1. Ricorsione
0.Titolo
Dato un array di numeri interi senza elementi duplicati. Un albero binario massimo costruito direttamente e ricorsivamente da questo array è definito come segue: La radice dell'albero binario è l'elemento più grande nell'array nums. Il sottoalbero sinistro è il più grande albero binario costruito ricorsivamente attraverso la parte sinistra del valore massimo dell'array. Il sottoalbero destro è il più grande albero binario costruito ricorsivamente attraverso la parte destra del valore massimo dell'array. Restituisce l'albero binario più grande costruito con i numeri dell'array specificati
1. Idee
Ancora una trilogia 1. Condizione di terminazione della ricorsione: quando gli array del sottoalbero sinistro e del sottoalbero destro sono entrambi vuoti 2. Cosa fa questa ricorsione: elimina il valore massimo dell'array, divide l'array in array sinistro e destro in base al valore massimo, quindi esegue l'assegnazione ricorsiva 3. Cosa viene restituito: il nodo radice del sottoalbero
2.Codice
Soluzione di classe: def buildMaximumBinaryTree(self, nums: List[int]) -> TreeNode: se non sono numeri: restituisci Nessuno # Applicabile quando sono presenti elementi ripetuti # max_v, m_i = float(-inf), 0 # per i, v in enumerate(nums): # se v>max_v: #v_massimo = v #m_i = io max_v = max(numeri) m_i = numeri.indice(max_v) radice = TreeNode(max_v) root.left = self.constructMaximumBinaryTree(nums[:m_i]) root.right = self.constructMaximumBinaryTree(nums[m_i 1:]) restituisce radice
3. Complessità
Complessità temporale: O(n^2), il costrutto del metodo viene chiamato n volte in totale. Ogni volta che cerchi ricorsivamente il nodo radice, devi attraversare tutti gli elementi nell'intervallo dell'indice corrente per trovare il valore massimo. In generale, la complessità di ogni attraversamento è O(logn) e la complessità totale è O(nlogn). Nel peggiore dei casi, l'array numsnums viene ordinato e la complessità totale è O(n^2)
Complessità spaziale: O(n)O(n). La profondità della chiamata ricorsiva è nn. In media, la profondità della chiamata ricorsiva per un array di lunghezza n è O(logn)
2. Stack monotonicamente decrescente
1. Pensieri
1. Costruisci una pila decrescente e inseriscila nella pila in sequenza Se l'elemento in cima alla pila è più piccolo dell'elemento corrente, l'elemento in cima alla pila viene estratto dalla pila.
2. Confronta la dimensione dell'elemento in alto dopo essere stato estratto dallo stack con la dimensione dell'elemento corrente. Se è più grande dell'elemento corrente, l'elemento corrente viene utilizzato come nodo genitore e l'elemento estratto viene utilizzato come nodo figlio sinistro; se l'elemento superiore dello stack è più piccolo dell'elemento corrente, l'elemento superiore dello stack viene utilizzato come nodo genitore e lo estrae dallo stack. L'elemento precedentemente estratto viene utilizzato come figlio destro e continua finché il nodo corrente non viene inserito nello stack.
3. Fino alla fine dell'ultimo elemento, viene formato uno stack decrescente e lo stack viene estratto in sequenza vengono utilizzati come figli giusti. L'ultimo estratto dallo stack è il nodo radice .
Esempio
Prendiamo come esempio il caso di test, una sequenza di input: [3, 2, 1, 6, 0, 5]. Configura uno stack ausiliario per archiviare da grandi a piccoli. Il processo è il seguente: Premi prima 3 2 è più piccolo di 3 e viene messo in pila 1 è più piccolo di 2, messo in pila 6 è maggiore di 1, quindi 1 e 1 devono essere visualizzati. Scegli l'elemento più piccolo tra 2 e 6 come nodo genitore, quindi 2 è selezionato sul lato destro di 2, rendendo 1 il nodo figlio destro di 2. Dopo aver visualizzato 1, 6 è ancora più grande di 2. Allo stesso modo, 2 deve sceglierne uno tra 3 e 6 come nodo genitore. 3 è minore di 6, quindi 3 è selezionato 2 è a destra di 3, quindi 2 è il figlio destro di 3. Allo stesso modo, fai apparire 3, lascia che 3 sia il nodo figlio sinistro di 6 Premi 6 Metti 0 nello stack Quando 5 viene inserito nello stack, è più grande di 0. Per inserire 0, seleziona 5 come nodo genitore e 0 è il figlio sinistro di 5. Viene visualizzato 5, con 6 a sinistra come nodo principale di 5 Finalmente appare 6, che è il nodo radice
2. Codice Java
public TreeNode buildMaximumBinaryTree(int[] nums) { TreeNode root = null, nodo = null; Stack LinkedList<TreeNode> = new LinkedList<>(); for (int i = 0; i < numeri.lunghezza; i) { nodo = nuovo TreeNode(nums[i]); //Costruisce uno stack decrescente Quando l'elemento superiore dello stack è più piccolo dell'elemento corrente, l'elemento superiore dello stack verrà estratto dallo stack. while (!stack.isEmpty() && stack.peek().val < node.val) { root = stack.pop();//L'elemento superiore dello stack corrente, il valore più piccolo //Confronta la dimensione della parte superiore dello stack dopo l'estrazione con la dimensione dell'elemento corrente. Se è più grande dell'elemento corrente, l'elemento corrente verrà utilizzato come nodo genitore. if (stack.isEmpty() || stack.peek().val > node.val) { nodo.sinistra = radice; } else {//Se è inferiore all'elemento corrente, l'elemento superiore dello stack verrà utilizzato come nodo principale stack.peek().right = root; } } stack.push(node);//Spinge l'elemento corrente nello stack } // Lo stack decrementale è completamente formato e può essere estratto come figlio destro. while (!stack.isEmpty()) { radice = stack.pop(); if (!stack.isEmpty()) { stack.peek().right = root; } } return root;//L'ultima cosa che esce dallo stack è root, ritorna a questo nodo }
3. Complessità
Complessità temporale: O(n), Complessità spaziale: O(n)
998.Albero binario massimo II×
1. Inserimento della traversa
0.Titolo
Definizione massima di albero: un albero in cui il valore di ciascun nodo è maggiore di qualsiasi altro valore nel suo sottoalbero dà il nodo radice radice dell'albero più grande. Come la domanda precedente, l'albero dato è costruito ricorsivamente dalla lista A, non ci viene dato A direttamente, solo un nodo radice root, assumendo che B sia una copia di A con il valore val aggiunto alla fine. I dati della domanda garantiscono che i valori in B siano diversi. Costrutto di ritorno(B)
1. Pensieri
Ogni volta che viene confrontato con il nodo testa, se è più piccolo, viene controllato il suo nodo destro. Aggiornamento: 1) Il nodo esistente diventa il nodo sinistro del nuovo nodo, 2) il nuovo nodo diventa il nodo destro del nodo genitore
2.Codice
def insertIntoMaxTree(self, root: TreeNode, val: int) -> TreeNode: # dummy variabile fittizia, il suo figlio destro punta al nodo radice fittizio = TreeNode(0) manichino.destra = radice # search Se il valore corrente del nodo radice è maggiore di val, continua a interrogare il suo figlio destro p, c = manichino, manichino.giusto mentre c e c.val > val: p, c = c, c.destra # insert In questo momento, val è maggiore del nodo radice, val viene utilizzato come figlio destro del nodo radice precedente e il nodo radice corrente viene utilizzato come figlio sinistro di val n = NodoAlbero(val) p.destra = n n.sinistra = c return dummy.right #dummy non è cambiato, il suo figlio destro punta al nodo radice
3. Complessità
2. Ricorsione
1. Pensieri
La stessa elaborazione viene eseguita quando è maggiore del nodo radice. Quando è inferiore al nodo radice, il figlio destro del nodo radice viene elaborato in modo ricorsivo.
2.Codice
def insertIntoMaxTree(self, root: TreeNode, val: int) -> TreeNode: se root è None: # Condizione di terminazione ricorsiva returnTreeNode(val) if val > root.val: #Come gestire val maggiore del nodo radice corrente tmp = TreeNode(val) tmp.sinistra = radice restituire tmp # Elabora ricorsivamente il figlio destro del nodo radice root.right = self.insertIntoMaxTree(root.right, val) return root # Valore restituito ricorsivo, nodo root
3. Complessità
sottoargomento
sottoargomento
110. Determinare un albero binario bilanciato
1. Dall'alto verso il basso
0.Titolo
Dato un albero binario, determinare se si tratta di un albero binario con altezza bilanciata. In questa domanda, un albero binario con altezza bilanciata è definito come: il valore assoluto della differenza di altezza tra i sottoalberi sinistro e destro di ciascun nodo di un albero binario non supera 1
1. Idee
1. Definire la funzione altezza, che viene utilizzata per calcolare l'altezza di qualsiasi nodo p nell'albero binario.
2. Similmente all'attraversamento in preordine di un albero binario, ovvero, per il nodo attualmente attraversato, calcolare prima l'altezza dei sottoalberi sinistro e destro. Se la differenza di altezza tra i sottoalberi sinistro e destro non supera 1, allora attraversa ricorsivamente rispettivamente i sottonodi sinistro e destro e determina se i sottoalberi sinistro e destro sono bilanciati. Questo è un processo ricorsivo top-down
2.Codice
Soluzione di classe: #Top-down, simile all'attraversamento del preordine, calcolerà ripetutamente l'altezza dei sottoalberi sinistro e destro def isBalanced(self, root: TreeNode) -> bool: def altezza(root: TreeNode) -> int: se non root: restituire 0 return max(altezza(root.sinistra), altezza(root.right)) 1 se non root: restituire Vero return abs(altezza(radice.sinistra) - altezza(radice.destra)) <= 1 e self.isBalanced(root.left) e self.isBalanced(root.right)
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero binario. Utilizzando la ricorsione dal basso verso l'alto, l'altezza di calcolo di ciascun nodo e il giudizio se è bilanciato devono essere elaborati solo una volta. Nel peggiore dei casi, tutti i nodi dell'albero binario devono essere attraversati, quindi la complessità temporale è O(. N)
Complessità spaziale: O(n), dove n è il numero di nodi nell'albero binario. La complessità dello spazio dipende principalmente dal numero di livelli di chiamate ricorsive. Il numero di livelli di chiamate ricorsive non supererà nn.
2. Dal basso verso l'alto
1. Idee
1. Poiché il metodo uno è ricorsivo top-down, la funzione \texttt{height}height verrà chiamata ripetutamente per lo stesso nodo, con conseguente elevata complessità temporale. Se viene utilizzato l'approccio bottom-up, la funzione \texttt{height}height verrà chiamata solo una volta per ciascun nodo.
2. L'approccio ricorsivo dal basso verso l'alto è simile all'attraversamento post-ordine. Per il nodo attualmente attraversato, determinare prima in modo ricorsivo se i suoi sottoalberi sinistro e destro sono bilanciati, quindi determinare se il sottoalbero con radice nel nodo corrente è bilanciato. Se un sottoalbero è bilanciato, viene restituita la sua altezza (l'altezza deve essere un numero intero non negativo), altrimenti viene restituito -1−1. Se è presente un sottoalbero sbilanciato, l'intero albero binario deve essere sbilanciato
2.Codice
Soluzione di classe: #Bottom-up, simile all'attraversamento post-ordine, determinare prima i sottoalberi sinistro e destro, quindi determinare il nodo radice, assicurandosi che l'altezza di ciascun nodo venga calcolata solo una volta def isBalanced(self, root: TreeNode) -> bool: def altezza(root: TreeNode) -> int: se non root: restituire 0 altezzasinistra = altezza(root.sinistra) altezzadestra = altezza(radice.destra) se leftHeight == -1 o rightHeight == -1 o abs(leftHeight - rightHeight) > 1: ritorno -1 altro: return max(altezzasinistra, altezzadestra) 1 restituisce altezza(radice) >= 0
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero binario. Utilizzando la ricorsione dal basso verso l'alto, l'altezza di calcolo di ciascun nodo e il giudizio se è bilanciato devono essere elaborati solo una volta. Nel peggiore dei casi, tutti i nodi dell'albero binario devono essere attraversati, quindi la complessità temporale è O(. N)
Complessità spaziale: O(n), dove n è il numero di nodi nell'albero binario. La complessità dello spazio dipende principalmente dal numero di livelli di chiamate ricorsive. Il numero di livelli di chiamate ricorsive non supererà nn.
111.Profondità minima dell'albero binario
1. Innanzitutto la profondità
0.Titolo
Dato un albero binario, determinarne la profondità minima. La profondità minima è il numero di nodi sul percorso più breve dal nodo radice al nodo foglia più vicino
1. Idee
1. La chiave di questa domanda è capire la condizione finale ricorsiva La definizione di nodo foglia è che quando sia il figlio sinistro che quello destro sono nulli, viene chiamato nodo foglia. Quando i figli sinistro e destro del nodo radice sono vuoti, restituisce 1 Quando uno dei figli sinistro e destro del nodo radice è vuoto, restituisce la profondità del nodo figlio che non è vuoto. Quando entrambi i figli sinistro e destro del nodo radice sono vuoti, restituiscono i valori del nodo della profondità minore dei figli sinistro e destro.
2.Codice
Soluzione di classe: def minDepth(self, root: TreeNode) -> int: se non root: restituire 0 #Quando entrambi i sottoalberi sinistro e destro sono vuoti, si tratta di un nodo foglia e la distanza di ritorno è 1 se non root.left e non root.right: ritorno 1 profondità_min = 10**9 se root.left: #Il sottoalbero sinistro esiste, confronta la distanza minima del sottoalbero sinistro con la distanza minima corrente profondità_min = min(self.profonditàmin(root.sinistra), profondità_min) se root.destra: profondità_min = min(self.profonditàmin(root.right), profondità_min) restituisce min_profondità 1
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero. Visita ogni nodo una volta
Complessità spaziale: O(h), dove h è l'altezza dell'albero. La complessità dello spazio dipende principalmente dal sovraccarico dello spazio dello stack durante la ricorsione. Nel peggiore dei casi, l'albero appare a forma di catena e la complessità dello spazio è O(n). In media, l’altezza dell’albero è positivamente correlata al logaritmo del numero di nodi e la complessità spaziale è O(logN)
2.Prima di tutto l'ampiezza
1. Idee
Quando troviamo un nodo foglia, restituiamo direttamente la profondità del nodo foglia. La natura della ricerca in ampiezza garantisce che la profondità del primo nodo foglia cercato sia la più piccola.
2.Codice
Soluzione di classe: def minDepth(self, root: TreeNode) -> int: se non root: restituire 0 que = collezioni.deque([(root, 1)]) mentre que: nodo, profondità = que.popleft() #Il primo nodo foglia deve essere il nodo foglia più vicino se non node.left e non node.right: profondità di ritorno se nodo.sinistra: que.append((nodo.sinistra, profondità 1)) se nodo.destra: que.append((nodo.destra, profondità 1)) restituire 0
3. Complessità
Complessità temporale: O(n), dove n è il numero di nodi nell'albero. Visita ogni nodo una volta
Complessità spaziale: O(n), dove n è il numero di nodi nell'albero. La complessità dello spazio dipende principalmente dal sovraccarico della coda. Il numero di elementi nella coda non supererà il numero di nodi nell'albero.
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.Prima di tutto l'ampiezza
0.frequenza
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. Prima la profondità
0.frequenza
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.Doppio puntatore
0.frequenza
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. Ordinamento
0.frequenza
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.Metodo di backtracking
0.frequenza
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. Tabella hash
0.frequenza
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.Impila
0.frequenza
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. Programmazione dinamica
509. Numeri di Fibonacci
1. Programmazione dinamica
0.Titolo
I numeri di Fibonacci, solitamente rappresentati da F(n), formano una sequenza chiamata sequenza di Fibonacci. La sequenza inizia con 0 e 1 e ogni numero successivo è la somma dei due numeri precedenti.
1. Idee
1. Determinare il significato dell'array dp e dei pedici
La definizione di dp[i] è: il valore di Fibonacci dell'i-esimo numero è dp[i]
2. Determinare la formula di ricorsione
Equazione di transizione di stato dp[i] = dp[i - 1] dp[i - 2]
3.Come inizializzare l'array dp
La domanda ci fornisce anche direttamente come inizializzare dp[0] = 0;dp[1] = 1;
4. Determinare l'ordine di attraversamento
dp[i] dipende da dp[i - 1] e dp[i - 2], quindi l'ordine di attraversamento deve essere dalla parte anteriore a quella posteriore.
5. Derivazione dell'array dp con esempi
Quando N è 10, l'array dp dovrebbe essere la seguente sequenza: 0 1 1 2 3 5 8 13 21 34 55 Se il codice viene scritto e il risultato risulta errato, stampare l'array dp per vedere se è coerente con la sequenza che abbiamo derivato.
2.Codice
Soluzione di classe: def fib(self, n: int) -> int: se n < 2: ritorno n # Scorri l'idea dell'array, ottimizza la complessità temporale p, q, r = 0, 0, 1 # Non è necessario mantenere l'intera sequenza, è possibile mantenere solo 3 valori per i in range(2, n 1): p, q = q, r r = pq ritorno r
3. Complessità
Complessità temporale: O(n) Complessità spaziale: O(1)
2. Esponenziazione rapida di matrici
1. Idee
1. La complessità temporale del metodo 1 è O(n)O(n). L'utilizzo del metodo di esponenziazione rapida della matrice può ridurre la complessità temporale
2. Relazione di ricorrenza
3. Finché possiamo calcolare rapidamente l'ennesima potenza della matrice MM, possiamo ottenere il valore di F(n)F(n). Se M^n viene calcolato direttamente, la complessità temporale è O(n)O(n). È possibile definire la moltiplicazione della matrice e quindi utilizzare l'algoritmo di potenza veloce per accelerare il calcolo di M^n qui.
2.Codice
Soluzione di classe: def fib(self, n: int) -> int: se n < 2: ritorno n q = [[1, 1], [1, 0]] res = self.matrice_pow(q, n - 1) ritorna res[0][0] # Matrice elevata all'ennesima potenza, risolta rapidamente utilizzando il metodo di bisezione def matrice_pow(self, a: Lista[Lista[int]], n: int) -> Lista[Lista[int]]: ret = [[1, 0], [0, 1]] mentre n > 0: se n & 1: # n/2 resto 1 ret = self.matrice_moltiplica(ret, a) n >>= 1 # n/2, trova l'ennesima potenza della matrice con il metodo di bisezione a = self.matrice_moltiplica(a, a) ritorno ret #Moltiplicazione di matrici def matrice_moltiplica(self, a: Lista[Lista[int]], b: Lista[Lista[int]]) -> Lista[Lista[int]]: c = [[0, 0], [0, 0]] per i nell'intervallo(2): per j nell'intervallo(2): c[i] [j] = a[i] [0] * b[0] [j] a[i] [1] * b[1] [j] ritorno c
3. Complessità
Complessità temporale: O(logn) Complessità spaziale: O(1)
3. Formula generale
1. Idee
1. I numeri di Fibonacci F(n)F(n) sono ricorsioni lineari omogenee Secondo l'equazione di ricorsione F(n)=F(n−1) F(n−2), tali caratteristiche possono essere scritte Equazione: x^2. =x1
2.
3. Sostituendo le condizioni iniziali F(0)=0, F(1)=1, otteniamo
4. Pertanto, la formula generale dei numeri di Fibonacci è la seguente:
2.Codice
Soluzione di classe: def fib(self, n: int) -> int: sqrt5 = 5**0.5 # radice numero 5 fibN = ((1 quadrato5) / 2) ** n - ((1 - quadrato5) / 2) ** n giro di ritorno(fibN/quadrato5)
3. Complessità
La complessità temporale e spaziale della funzione pow utilizzata nel codice è legata al set di istruzioni supportato dalla CPU. Non la analizzeremo in modo approfondito in questa sede.
70. Sali le scale
1. Programmazione dinamica
0.Titolo
Supponiamo che tu stia salendo le scale. Ci vogliono n gradini per raggiungere la cima dell'edificio. Puoi salire 1 o 2 gradini alla volta. In quanti modi diversi puoi salire in cima a un edificio?
1. Idee
0.Guida
C'è un modo per salire le scale fino al primo piano e ci sono due modi per salire le scale fino al secondo piano. Quindi fai altri due gradini sulla prima rampa di scale per raggiungere il terzo piano, e un altro gradino sulla seconda rampa di scale per raggiungere il terzo piano. Quindi lo stato delle scale fino al terzo piano si può dedurre dallo stato delle scale fino al secondo piano e dallo stato delle scale fino al primo piano, quindi si può pensare ad una programmazione dinamica.
1. Determinare il significato dell'array dp e dei pedici
dp[i]: Ci sono modi dp[i] per salire alla i-esima scala
2. Determinare la formula di ricorsione
Dalla definizione di dp[i] si vede che dp[i] può essere derivato in due direzioni. Il primo è dp[i - 1]. Ci sono dp[i - 1] modi per salire le scale i-1, quindi saltare un gradino alla volta è dp[i]. C'è anche dp[i - 2]. Ci sono dp[i - 2] modi per salire le scale i-2. Quindi saltare due gradini in un unico gradino è dp[i]. Allora dp[i] è la somma di dp[i - 1] e dp[i - 2]! Quindi dp[i] = dp[i - 1] dp[i - 2]
3.Come inizializzare l'array dp
Quindi se i è 0, cosa dovrebbe essere dp[i] Ci sono molte spiegazioni per questo, ma fondamentalmente sono spiegate direttamente nella risposta.
Ad esempio, esiste un modo per sforzarsi di consolarsi e salire allo 0° piano. Anche non fare nulla è un modo: dp[0] = 1, che equivale a stare direttamente in cima all'edificio.
Pensavo che quando correvo al piano 0, il metodo era 0. Potevo fare solo uno o due passi alla volta. Tuttavia, il piano era 0. Stavo semplicemente in cima all'edificio, quindi non esisteva alcun metodo dp[0] dovrebbe essere 0.
La maggior parte dei motivi per cui dp[0] dovrebbe essere 1 sono in realtà perché se dp[0]=1, posso superare il problema partendo da 2 durante il processo di ricorsione e quindi fare affidamento sul risultato per spiegare dp[0] = 1
La domanda afferma che n è un numero intero positivo, ma non dice affatto che n sia 0. Pertanto, questa domanda non dovrebbe discutere l'inizializzazione di dp[0].
Il mio principio è: non considerare l'inizializzazione di dp[0], inizializzare solo dp[1] = 1, dp[2] = 2, quindi iniziare la ricorsione da i = 3, in modo che soddisfi la definizione di dp[i ]
4. Determinare l'ordine di attraversamento
Si vede dalla formula ricorsiva dp[i] = dp[i - 1] dp[i - 2] che l'ordine di attraversamento deve essere dal davanti al dietro.
5. Derivazione dell'array dp con esempi
Quando N è 10, l'array dp dovrebbe essere la seguente sequenza: 0 1 1 2 3 5 8 13 21 34 55
2.Codice
Soluzione di classe: def salireScale(self, n: int) -> int: # Scorri l'idea dell'array, ottimizza la complessità temporale se n <= 1: restituisce n dp = [0] * (n 1) # Quando è necessario utilizzare esplicitamente gli indici degli elenchi, è necessario creare un elenco dp[1], dp[2] = 1, 2 # Non è necessario mantenere l'intera sequenza, basta mantenere 2 valori Usa p e q direttamente senza creare per i in range(3, n 1): somma = dp[1] dp[2] dp[1], dp[2] = dp[2], somma ritorno dp[2]
3. Complessità
Complessità temporale: O(n) Complessità spaziale: O(1)
2. Esponenziazione rapida di matrici
Uguale a 509
3. Formula generale
Uguale a 509
tema
Domande classiche da intervista
15,
Nozioni di base sugli algoritmi
1. Complessità temporale
1.Definizione
La complessità temporale è una funzione che descrive qualitativamente il tempo di esecuzione dell'algoritmo
2. Cos'è Big O?
Big O viene utilizzato per rappresentare il limite superiore. Quando viene utilizzato come limite superiore del tempo di esecuzione nel caso peggiore dell'algoritmo, è il limite superiore del tempo di esecuzione per qualsiasi input di dati a cui fa riferimento la complessità temporale dell'algoritmo in generale
3. Differenze nelle diverse dimensioni dei dati
Poiché Big O è la complessità temporale mostrata quando la grandezza dei dati attraversa un punto e la grandezza dei dati è molto grande. Questa quantità di dati è la quantità di dati in cui il coefficiente costante non gioca più un ruolo decisivo, quindi chiamiamo complessità temporale The i coefficienti a termine costante vengono omessi perché la dimensione predefinita dei dati è generalmente sufficientemente grande. Sulla base di questo fatto, una classificazione della complessità temporale dell'algoritmo fornita è la seguente:
O(1) ordine costante < O(logn) ordine logaritmico < O(n) ordine lineare < O(n^2) ordine quadrato < O(n^3) (ordine cubico) < O(2^n) (ordine esponenziale )
4.Qual è la base di log in O(logn)?
Ma collettivamente diciamo logn, cioè, trascurando la descrizione della base, il logaritmo di n in base 2 = il logaritmo di 10 in base 2 * il logaritmo di n in base 10, e il logaritmo di 10 in base 2 è un costante che può essere ignorata
Riepilogo delle domande
0.Interessante
Un passaggio di codifica è feroce come una tigre, sconfiggendo il 500% in sottomissione
Una serie di idee è sbocciata dalle risate e le candidature hanno superato l'80%
1. Non capisco
1.Albero
145. Attraversamento postordine Attraversamento di Morris
105. Perché non è possibile utilizzare la mappatura degli indici nel metodo conciso?
2. Frequenza delle interviste
1.Ricerca 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.Prima di tutto l'ampiezza
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. Tabella 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. Metodo del backtracking
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. Elenco collegato
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. Ordina
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. Prima la profondità
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.Albero
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.Array
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.Doppi puntatori
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.Impila
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 corde
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,
sottoargomento
Domande simili
1.La somma di due numeri
1,15,
Relativo al codice
1. Stile del codice
1. Principi fondamentali
1.Rientro
È preferibile utilizzare 4 spazi. Al momento, quasi tutti gli IDE convertono le schede in 4 spazi per impostazione predefinita, quindi non ci sono grossi problemi.
2. Lunghezza massima della linea
79 caratteri, sarebbe meglio usare le barre rovesciate per le interruzioni di riga
3.Importa
1.Formatta
L'importazione si trova all'inizio del file, dopo il commento del file. L'importazione è solitamente un'importazione a riga singola o da ... importa
2. Sequenza
1. Importazione della libreria standard 2. Importazioni rilevanti da terzi 3. Importazioni di applicazioni/librerie locali specifiche Inserire una riga vuota tra ciascun gruppo di importazione. Le importazioni assolute sono consigliate perché sono più leggibili; è possibile utilizzare importazioni relative esplicite invece di importazioni assolute quando si ha a che fare con layout di pacchetti complessi, che sono troppo dettagliati
3. Attenzione
1. Sulla base dell'esperienza pratica, si consiglia di eliminare tutte le importazioni non necessarie
2. Importa questa parte, che può essere perfettamente risolta tramite la libreria Python isort (vscode utilizza isort per impostazione predefinita)
3. Quando from .. import ... supera il limite di lunghezza della riga, inizia una nuova riga: --sl/--force-single-line-imports
4. Forza l'ordinamento per nome del pacchetto: --fss/--force-sort-within-sections
Configurato in vscode come
4. Commenti
Evita incoerenze tra commenti e codice! !, il che è ancora più esasperante di nessun commento. Rispettare principalmente i seguenti punti: 1. Quando si modifica il codice, modificare prima i commenti; 2. I commenti dovrebbero essere frasi complete. Pertanto, la prima lettera della prima parola deve essere maiuscola, a meno che la prima parola non sia un identificatore che inizia con una lettera minuscola; 3. Non è necessario che i commenti brevi finiscano con un punto, ma i commenti con frasi complete devono terminare con un punto; 4. Ogni riga di commento inizia con un # e uno spazio; 5. I commenti sui blocchi devono utilizzare lo stesso livello di rientro; 6. I commenti in linea devono essere separati dal codice da almeno due spazi; 7. Cerca di rendere il tuo codice "parlabile" ed evita commenti non necessari.
5. Stringhe di documenti docstrings
Scrivi docstring per tutti i moduli, funzioni, classi e metodi pubblici
Per le docstring, CLion ha un buon supporto e vscode può implementare Python Docstring Generator tramite plug-in.
6. Convenzione di denominazione
Funzioni, variabili e proprietà sono scritte in lettere minuscole, con trattini bassi tra le parole, ad esempio lowercase_underscore. Le classi e le eccezioni dovrebbero essere denominate con la prima lettera di ogni parola in maiuscolo (camello alto), come CapitalizedWord. Gli attributi dell'istanza protetta dovrebbero iniziare con un singolo carattere di sottolineatura. Le proprietà dell'istanza privata dovrebbero iniziare con due caratteri di sottolineatura. Le costanti a livello di modulo devono essere scritte in maiuscolo, con trattini bassi tra le parole. Il primo parametro del metodo di istanza nella classe dovrebbe essere denominato self, che rappresenta l'oggetto stesso. Il primo parametro del metodo della classe (metodo cls) dovrebbe essere denominato cls, indicando la classe stessa
2. Presta attenzione ai dettagli
0.Strumenti
Utilizzando lo strumento di formattazione del codice Python yapf, puoi risolvere automaticamente alcuni problemi di formattazione dettagliati. In combinazione con isort, puoi completare la formattazione o il controllo del formato del codice Python completamente tramite script. Oppure configura l'IDE per eseguire automaticamente la formattazione del codice durante la modifica e il salvataggio. Questa è una buona pratica. Puoi installare la libreria PEP8 e impostarla in pycharm, in modo che l'IDE possa aiutarti a organizzare il codice nello stile PEP8.
1. Avvolgimento del codice
I ritorni a capo dovrebbero precedere gli operatori binari
2. Riga vuota
Ci sono due righe vuote tra la funzione di livello superiore e la definizione della classe. C'è una riga vuota tra le definizioni di funzione all'interno della classe
3. Virgolette
Le virgolette doppie e le virgolette singole sono la stessa cosa, ma è meglio seguire uno stile a cui sono abituato a usare le virgolette singole perché: Non è necessario tenere premuto il tasto Maiusc durante la scrittura del codice per migliorare l'efficienza Alcune stringhe del linguaggio devono utilizzare virgolette doppie e Python non ha bisogno di aggiungere la barra rovesciata durante l'elaborazione.
4. Spazio
Utilizza lo spazio per il rientro invece del tasto tab. Utilizza quattro spazi per ogni livello di rientro relativo alla sintassi. Per le espressioni lunghe che si estendono su più righe, tutte tranne la prima riga dovrebbero essere rientrate di 4 spazi sopra il livello normale. Quando si utilizzano pedici per recuperare elementi di elenco, chiamare funzioni o assegnare valori ad argomenti di parole chiave, non aggiungere spazi attorno ad essi. Quando si assegna un valore a una variabile, è necessario scrivere uno spazio a sinistra e a destra del simbolo di assegnazione e solo uno
Immediatamente all'interno di parentesi quadre, parentesi quadre o parentesi graffe Immediatamente prima di una virgola, punto e virgola o due punti, deve esserci uno spazio dopo. Immediatamente prima della parentesi di apertura di un elenco di parametri di funzione Immediatamente prima della parentesi di apertura di un'operazione di indice o di suddivisione in porzioni Inserisci sempre 1 spazio su entrambi i lati dei seguenti operatori binari: assegnazione (=), assegnazione incrementale (=, -=, ecc.), confronto (==, <, >, !=, <>, <=, > = , in, non, in, è, non è), Booleano (e, o, non) Inserisci degli spazi attorno agli operatori matematici Se utilizzato per specificare argomenti di parole chiave o valori di argomenti predefiniti, non utilizzare spazi attorno a = return magic(r=real, i=imag)
Aggiungi gli spazi necessari, ma evita gli spazi non necessari. Evita sempre gli spazi bianchi finali, inclusi eventuali caratteri invisibili. Pertanto, se il tuo IDE supporta la visualizzazione di tutti i caratteri invisibili, attivalo! Allo stesso tempo, se l'IDE supporta l'eliminazione del contenuto vuoto alla fine della riga, abilitalo anche tu! yapf può aiutarci a risolvere questa parte. Dobbiamo solo formattare il codice dopo averlo scritto.
5. Dichiarazione composta
Non è consigliabile includere più istruzioni in una riga
6. Virgola finale
Quando gli elementi dell'elenco, i parametri e gli elementi importati potrebbero continuare ad aumentare in futuro, lasciare una virgola finale è una buona scelta. L'uso comune è che ogni elemento sia su una propria riga, con una virgola alla fine, e un tag di chiusura sia scritto sulla riga successiva dopo l'ultimo elemento. Se tutti gli elementi sono sulla stessa riga, non è necessario farlo
7. Risolvere i problemi rilevati dal linter
Usa flake8 per controllare il codice Python e modificare tutti gli errori e gli avvisi controllati a meno che non ci siano buone ragioni.
2. Codici comunemente usati
1. Dizionario
1. Contare il numero di occorrenze
conta[parola] = conta.get(parola,0) 1
2. Elenco
1. Elenca l'ordinamento delle parole chiave specificate
items.sort(key=lambda x:x[1], reverse=True)# Ordina il secondo elemento in ordine inverso
2. Converti ciascun elemento nell'elenco di stringhe in un elenco di numeri
lista(mappa(eval,lista))
3. Invertire tutti gli elementi nell'elenco
ris[::-1]
4. Scambia due numeri nell'elenco
res[i],res[j] = res[j],res[i] #Non sono richieste variabili intermedie
5. Assegnare un elenco di lunghezza fissa
G = [0]*(n 1) #Lista di lunghezza n 1
6. Trova l'elemento più grande nell'intervallo [i, j] nell'elenco arr
max(arr[i:j 1])
7. Utilizzare questo elemento per dividere l'elenco in ordine
sinistra_l = ordine[:idx] destra_l = ordine[idx 1:]
3. Ciclo/giudizio
1. Quando è necessario determinare solo il numero di loop e non è necessario ottenere il valore
for _ in range(len(coda)):
2.Abbreviazione di if...else
node.left = recur_func(left_l) se left_l altrimenti Nessuno
3. Ciclo inverso
for i in range(len(postorder)-2, -1,-1)
4. Ottieni elementi e pedici allo stesso tempo
for i, v in enumerate(nums):
5. Attraversa più elenchi contemporaneamente e restituisce più valori
for net, opt, l_his in zip(nets, Optimizers, Loss_ his):
Senza zip, verrà emesso un solo valore ogni volta
6. Attraversa più elenchi e restituisce un valore
per l'etichetta in ax.get_xticklabels() ax.get_yticklabels():
4. Mappatura
1. Converti rapidamente le strutture attraversabili in mappature corrispondenti ai pedici
indice = {elemento: i per i, elemento in enumerate (in ordine)}
Può anche essere ottenuto utilizzando list.index(x), ma la lista deve essere attraversata ogni volta e il tempo è O(n). Quanto sopra è O(1).
3. Metodi comunemente utilizzati
1. Viene fornito con il sistema
0.Classificazione
1.Conversione del tipo
1.int(x)
1. Quando il tipo a virgola mobile viene convertito in un tipo intero, la parte decimale viene scartata direttamente.
2.float(x)
3.str(x)
1.ordinato(num)
1. Ordina gli elementi specificati
2.map(funzione,elenco)
1. Applicare la funzione del primo parametro a ciascun elemento del secondo parametro
mappa(valutazione,elenco)
3.len(i)
1. Ottenere la lunghezza, che può essere di qualsiasi tipo
4.enumerare()
1. Combina un oggetto dati attraversabile (come una lista, una tupla o una stringa) in una sequenza di indici ed elenca contemporaneamente il pedice dei dati e i dati. Generalmente utilizzato nei cicli for: for i, elemento in enumerate(seq) :.
2.enumerate(sequence, [start=0]), il pedice inizia da 0, restituisce l'oggetto enumerate (enumerazione)
5.tipo(x)
1. Determinare il tipo di variabile x, applicabile a qualsiasi tipo di dati
2. Se è necessario utilizzare la variabile tipo come condizione nel giudizio condizionale, è possibile utilizzare la funzione type() per il confronto diretto.
se tipo(n) == tipo(123):
2.Elenco[]
1. Quando viene utilizzato lo stack
1. Spingere nella pila
stack.append()
2. Esci dallo stack
stack.pop()
Pop l'ultimo elemento
3. Restituisce l'elemento superiore dello stack
Non esiste un metodo di visualizzazione nell'elenco. Puoi solo prima visualizzarlo e poi aggiungerlo.
2. Quando utilizzato in coda
1. Unisciti alla squadra
coda.append()
2.Lascia la squadra
coda.pop(0)
Il primo elemento da rimuovere dalla coda
3. Ottieni l'indice dell'elemento
m_i = numeri.indice(max_v)
3. Decoda della coda
1. La coda a doppia estremità viene utilizzata come coda
1. Unisciti alla squadra
coda.append()
2.Lascia la squadra
coda.popleft()
4. Errori/differenze comuni
1. Formato della domanda
1.IndentationError: previsto un blocco rientrato
Si è verificato un problema con il rientro. L'errore più comune è mescolare i tasti Tab e Spazio per ottenere il rientro del codice.
Un altro motivo comune per l'errore sopra riportato è che non è presente il rientro della prima riga. Ad esempio, quando si scrive un'istruzione if, aggiungere due punti dopo di essa. Se si modifica direttamente la riga, molti editor di codice rientreranno automaticamente la prima riga. Tuttavia, alcuni editor di codice potrebbero non avere questa funzione. In questo caso, è meglio sviluppare un'abitudine. Si prega di non premere la barra spaziatrice più volte di seguito. Si consiglia di premere semplicemente il tasto Tab.
problema 1.pycharm
1. Come eliminare la richiesta che il certificato del server non è attendibile in pycharm
Fare clic su File > Impostazioni > Strumenti > Certificati server > Accetta automaticamente certificati non attendibili
1.abilità di pycharm
1. Importa file Python nel progetto corrente
Copia direttamente il file corrispondente nel file manager nella directory corrispondente al progetto corrente e apparirà direttamente in pycharm. Se il problema della versione non si verifica, comprimi tu stesso la directory del progetto corrente e poi espandila di nuovo.
2. Errore di installazione della riga di comando
0. Per esaminare il tipo di errore, assicurati di guardare l'errore sulla riga sopra l'ultima linea di divisione.
Altri errori
1."nessun modulo denominato XX"
Man mano che il livello di sviluppo di tutti migliora e la complessità del programma aumenta, nel programma verranno utilizzati sempre più moduli e librerie di terze parti. Il motivo di questo errore è che la libreria "XX" non è installata, pip install ww
1.errore
1.Errore: comando errato con stato di uscita 1
Scarica il pacchetto di installazione di terze parti corrispondente e accedi alla directory di download per installare il nome completo del file scaricato.
2.errore: è richiesto Microsoft Visual C 14.0
Installa Microsoft Visual C 14.0, il blog ha l'indirizzo di download visualcppbuildtools_full
3.errore: comando non valido 'bdist_wheel'
rotella di installazione pip3
2.Errore attributo Errore di proprietà
0. Soluzione generale
Se viene richiesto quale file del modulo presenta un errore, trova questo modulo, eliminalo e sostituiscilo con lo stesso file di un amico che può essere eseguito.
1.AttributeError: il modulo 'xxx' non ha l'attributo 'xxx'
1. Il nome del file è in conflitto con le parole chiave di Python e simili.
2. Due file py si importano a vicenda, provocando l'eliminazione di uno di essi.
2.AttributeError: il modulo 'six' non ha l'attributo 'main'
Il problema della versione pip 10.0 non ha main(), È necessario eseguire il downgrade della versione: python -m pip install –upgrade pip==9.0.1
Alcune API sono cambiate dopo Pip v10, con conseguente incompatibilità tra la vecchia e la nuova versione, che influisce sulla nostra installazione e aggiornamento dei pacchetti. Basta aggiornare l'IDE e il gioco è fatto! Durante l'aggiornamento, l'IDE ci ha fornito anche suggerimenti amichevoli. PyCharm -> Aiuto -> Controlla aggiornamenti
Non aggiornare la versione crackata, altrimenti le informazioni di attivazione non saranno più valide.
3.AttributeError: il modulo 'async io' non ha l'attributo 'run'
Hai chiamato un file asyncio py Se il controllo non è il primo, devi controllare la tua versione di Python perché Python3.7 e versioni successive supportano solo il metodo run. 1 Aggiorna la versione di Python 2 run viene riscritta come segue loop = asyncio.get_event_loop() risultato = loop.run_until_complete(coro)
4.AttributeError: il modulo 'asyncio.constants' non ha l'attributo '_SendfileMode'
Sostituisci i file delle costanti in asyncio
3.Erroretipo
1. "TypeError: l'oggetto 'tuple' non può essere interpretato come un numero intero"
t=('a','b','c') per i nell'intervallo(t):
Tipico problema di errore di tipo. Nel codice precedente, la funzione range() prevede che il parametro in entrata sia un numero intero (intero), ma il parametro in entrata è una tupla (tupla). il numero di tuple può essere di tipo intero len(t) Ad esempio, modificare range(t) nel codice precedente in range(len(t))
2. "TypeError: l'oggetto 'str' non supporta l'assegnazione di elementi"
Causato dal tentativo di modificare il valore di una stringa, che è un tipo di dati immutabile
s[3] = 'a' diventa s = s[:3] 'a' s[4:]
3. "TypeError: impossibile convertire implicitamente l'oggetto 'int' in str"
Causato dal tentativo di concatenare un valore non stringa con una stringa, basta eseguire il cast del valore non stringa in una stringa con str()
4. "TypeError: tipo non hashable: 'list'
Quando si utilizza la funzione set per costruire un insieme, gli elementi nell'elenco di parametri fornito non possono contenere elenchi come parametri.
5.TypeError: non è possibile utilizzare un modello di stringa su un oggetto simile a byte
Quando si passa da python2 a python3, si incontreranno inevitabilmente alcuni problemi. Le stringhe Unicode in python3 sono nel formato predefinito (tipo str) e le stringhe con codifica ASCII (tipo byte) Il tipo bytes contiene valori byte e in realtà non lo è un carattere. String, tipo array di byte python3 e bytearray) deve essere preceduto dall'operatore b o B; in python2 è il contrario, la stringa codificata ASCII è l'impostazione predefinita e la stringa Unicode deve essere preceduta dall'operatore u o U.
import chardet #È necessario importare questo modulo e rilevare il formato di codifica codifica_tipo = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica di conseguenza e assegnalo all'identificatore originale (variabile)
4.IOErrore
1. "IOErrore: file non aperto per la scrittura"
>>> f=aperto ("ciao.py") >>> f.write ("prova")
La causa dell'errore è che il parametro della modalità di lettura-scrittura mode non viene aggiunto ai parametri in entrata di open("hello.py"), il che significa che il modo predefinito per aprire il file è di sola lettura.
La soluzione è cambiare la modalità modalità in modalità scrittura autorizzazione w, f = open("ciao. py", "w ")
5.Errore di sintassi Errori grammaticali
1.SyntaxError: sintassi non valida”
Causato dalla dimenticanza di aggiungere i due punti alla fine di istruzioni come if, elif, else, for, while, class e def
Utilizzo errato di "=" invece di "==". Nei programmi Python, "=" è un operatore di assegnazione e "==" è un'operazione di confronto uguale.
6.Errore di decodifica Unicode Errore di interpretazione della codifica
Il codec 1.'gbk' non può decodificare i byte
Nella maggior parte dei casi ciò accade perché il file non è codificato UTF8 (ad esempio, potrebbe essere codificato GBK) e il sistema utilizza la decodifica UTF8 per impostazione predefinita. La soluzione è passare al metodo di decodifica corrispondente: con open('acl-metadata.txt','rb') come dati:
open(percorso, '-modalità-', codifica='UTF-8') ovvero open(percorso nome file, modalità lettura-scrittura, codifica)
Modalità di lettura e scrittura comunemente utilizzate
7.ErroreValore
1.troppi valori da scompattare
Quando si chiama una funzione, non ci sono abbastanza variabili per accettare il valore restituito.
8.Errore
1.WinError 1455] Il file di paging è troppo piccolo e l'operazione non può essere completata.
1. Riavvia Pycharm (praticamente inutile)
2. Imposta num_works su 0 (forse inutile)
3. Aumenta la dimensione del file di paging (risolvi completamente il problema)
9.Errore di importazione
1.Caricamento DLL non riuscito: il file di paging è troppo piccolo e l'operazione non può essere completata.
1. Non solo è in esecuzione un progetto, ma è in esecuzione anche il programma Python di un altro progetto, basta spegnerlo.
2. Il sistema operativo Windows non supporta il funzionamento multiprocesso di Python. La rete neurale utilizza più processi nel caricamento del set di dati, quindi imposta il parametro num_workers in DataLoader su 0.
Caricamento 1.DLL non riuscito: il sistema operativo non può eseguire %1
0. Recentemente, mentre stavo eseguendo un progetto scrapy, il framework scrapy installato ha segnalato improvvisamente un errore e sono stato colto di sorpresa.
1. Poiché non è difficile installare Scrapy in Anaconda, non è così semplice ed efficiente come reinstallarlo per trovare una soluzione.
2. Non riesco a disinstallarlo completamente utilizzando semplicemente il comando conda rimuovi scrapy. Il motivo potrebbe essere che ho installato scrapy due volte utilizzando rispettivamente pip e conda. I lettori possono provare sia i comandi di disinstallazione pip che conda.
pip disinstalla scrapy conda rimuovi lo scrapy
Reinstallare pip install scrapy
errore di classe
1. Problemi di proprietà dell'ereditarietà multipla delle classi
Abbiamo modificato solo A.x, perché è stato modificato anche C.x? Nei programmi Python, le variabili di classe vengono trattate internamente come dizionari, che seguono l'ordine di risoluzione del metodo comunemente citato (MRO). Quindi nel codice sopra, poiché l'attributo x nella classe C non viene trovato, cercherà la sua classe base (sebbene Python supporti l'ereditarietà multipla, c'è solo A nell'esempio sopra). In altre parole, la classe C non ha un proprio attributo x, che è indipendente da A. Pertanto, C.x è in realtà un riferimento ad A.x
errore di ambito
1. Variabili globali e variabili locali
1. La variabile locale x non ha valore iniziale e la variabile esterna X non può essere introdotta internamente.
2. Diverse operazioni sulle liste
Fool non ha assegnato un valore a lst, ma pazzo2 lo ha fatto. Sai, lst = [5] è l'abbreviazione di lst = lst [5] e stiamo cercando di assegnare un valore a lst (Python lo tratta come una variabile locale). Inoltre, la nostra assegnazione a lst è basata su lst stesso (che ancora una volta viene trattato come una variabile locale da Python), ma non è stato ancora definito, quindi si verifica un errore! Quindi qui dobbiamo distinguere tra l’uso di variabili locali e variabili esterne.
2.1 Aggiornamento Python2 Errore Python3
1.print diventa print()
1. Nella versione Python 2, print viene utilizzato come istruzione. Nella versione Python 3, print appare come una funzione. Nella versione Python 3, tutto il contenuto print deve essere racchiuso tra parentesi.
2.raw_Input diventa input
Nella versione Python 2, la funzionalità di input è implementata tramite raw_input. Nella versione Python 3, viene implementato tramite input
3. Problemi con gli interi e divisione
1. "TypeError: l'oggetto 'float* non può essere interpretato come un numero intero"
2. In Python 3, int e long sono unificati nel tipo int. Int rappresenta un numero intero di qualsiasi precisione. Il tipo long è scomparso in Python 3 e anche il suffisso L è stato deprecato quando si utilizza int supera la dimensione dell'intero locale , non causerà quindi l'eccezione OverflowError
3. Nelle versioni precedenti di Python 2, se il parametro è int o long, verrà restituito il risultato della divisione arrotondato per difetto (floor) e se il parametro è float o complesso, verrà restituito il risultato equivalente con una buona approssimazione del risultato dopo la divisione
4. "/" in Python 3 restituisce sempre un numero in virgola mobile, che significa sempre una divisione verso il basso. Basta cambiare "/" in "//" per ottenere il risultato della divisione intera
4. Importante aggiornamento della gestione delle eccezioni
1. Nei programmi Python 2, il formato per rilevare le eccezioni è il seguente: tranne Eccezione, identificatore
tranne ValueError, e: # Python 2 gestisce singole eccezioni tranne (ValueError, TypeError), e: # Python 2 gestisce più eccezioni
2. Nei programmi Python 3, il formato per rilevare le eccezioni è il seguente: eccetto Exception come identificatore
tranne ValueError as e: # Python3 gestisce una singola eccezione tranne (ValueError, TypeError) as e: # Python3 gestisce più eccezioni
3. Nei programmi Python 2, il formato per lanciare eccezioni è il seguente: raise Exception, args
4. Nei programmi Python 3, il formato per lanciare eccezioni è il seguente: raise Exception(args)
raise ValueError, e # metodo Python 2.x raise ValueError(e) # Metodo Python 3.x
5.xrange() diventa range()
"NameError: il nome 'xrange' non è definitow"
6. La ricarica non può essere utilizzata direttamente
"il nome 'reload' non è definito e AttributeError: il modulo 'sys' non ha att"
importa importlib importlib.reload(sys)
7. Niente più tipi Unicode
"python unicode non è definito"
In Python 3, il tipo Unicode non esiste più ed è sostituito dal nuovo tipo str. Il tipo str originale in Python 2 è stato sostituito da byte in Python 3
8.has_key è stato abbandonato
"AttributeError: l'oggetto 'dieta' non ha l'attributo 'has_key' "
has_key è stato abbandonato in Python 3. Il metodo di modifica consiste nell'utilizzare in invece di has_key.
9.urllib2 è stato sostituito da urllib.request
"lmportError: nessun modulo denominato urllib2"
La soluzione è modificare urllib2 in urllib.request
10. Problemi di codifica
TypeError: non è possibile utilizzare un modello di stringa su un oggetto simile a byte
Quando si passa da python2 a python3, si incontreranno inevitabilmente alcuni problemi. Le stringhe Unicode in python3 sono nel formato predefinito (tipo str) e le stringhe con codifica ASCII (tipo byte) Il tipo bytes contiene valori byte e in realtà non lo è un carattere. String, tipo array di byte python3 e bytearray) deve essere preceduto dall'operatore b o B; in python2 è il contrario, la stringa codificata ASCII è l'impostazione predefinita e la stringa Unicode deve essere preceduta dall'operatore u o U.
import chardet #È necessario importare questo modulo e rilevare il formato di codifica codifica_tipo = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decodifica di conseguenza e assegnalo all'identificatore originale (variabile)
2.1 Comandi della riga del prompt dei comandi
1. Directory delle operazioni
1.cd cambia la sottodirectory corrente, puoi copiare direttamente il percorso e inserirlo in una volta sola
2. Il comando CD non può modificare il disco corrente. CD.. ritorna alla directory precedente CD\ significa tornare alla directory del disco corrente. Quando CD non ha parametri, viene visualizzato il nome della directory corrente.
3.d: modifica la posizione del disco
2.Relativo a Python
1.pip
0.Chiedi aiuto
pip aiuto
0. Cambia la sorgente pip
1. Uso temporaneo
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple nome del pacchetto
2. Imposta come predefinito
set di configurazione pip global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Dopo averlo impostato come predefinito, le future librerie di installazione verranno scaricate dalla sorgente Tsinghua e non sarà necessario aggiungere l'URL della sorgente mirror.
3. Indirizzo di origine del mirror principale
1. Installare la libreria specificata
nome del pacchetto di installazione pip
2. Installare la versione specificata della libreria specificata
pip installa nome_pacchetto==1.1.2
3. Controllare quali librerie sono installate
elenco pip
4. Visualizza le informazioni specifiche della biblioteca
pip mostra -f nome del pacchetto
5.Dove è installato pip?
pip -V
6. Libreria di installazione batch
pip install -r d:\\requirements.txt Scrivi direttamente nome pacchetto == numero di versione nel file
7. Installare la libreria utilizzando il file wheel
1. Trova il file .whl della libreria corrispondente sul sito web sottostante, cerca con Ctrl F e presta attenzione alla versione corrispondente. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. Nella cartella in cui si trova .whl, premere il tasto Maiusc e fare clic con il pulsante destro del mouse per aprire la finestra CMD o PowerShell (Oppure inserisci questa cartella tramite la riga di comando)
3. Immettere il comando: pip install matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Disinstallare la libreria
pip disinstalla nome_pacchetto
9.Aggiornamento della libreria
pip install --upgrade nome_pacchetto
10. Salvare l'elenco delle librerie nel file specificato
pip freeze > nomefile.txt
11. Controlla le librerie che devono essere aggiornate
elenco pip -o
12. Verifica eventuali problemi di compatibilità
Verifica se la libreria installata ha dipendenze compatibili pip check nome-pacchetto
13. Scarica la libreria in locale
Scarica la libreria in un file locale specificato e salvala in formato whl pip download nome_pacchetto -d "Percorso file da salvare"
2. Comprimere il programma in un file eseguibile
pyinstaller -F nomefile.py
3. Scrittura del codice
1. Nessun formato
1. Senza i, può essere scritto solo come i =1
2. Formati diversi
1. Classe
1. Parametri
0. Quando si definisce una classe, il primo parametro deve essere self e lo stesso vale per tutti i metodi Quando si chiamano i membri di questa classe, è necessario utilizzare i membri.
1. Per i parametri nella classe, scrivi prima il nome della variabile, aggiungi i due punti, quindi scrivi il nome del tipo, separato da virgole
2. Una volta completata la definizione della classe, è possibile aggiungere -> nome del tipo alla fine per indicare il tipo di valore restituito.
2. Chiama
1. Per richiamarsi per la ricorsione in una classe, è necessario utilizzare la funzione self.self (non è necessario aggiungere self nei parametri)
2. Quando si utilizza una classe, non è necessario creare una nuova classe, basta usarla direttamente root = TreeNode(max_num)
3.Metodo
1._Inizia con un carattere di sottolineatura per definire il metodo di protezione
2.__Inizia con due caratteri di sottolineatura per definire i metodi privati
sottoargomento
2. Numeri
1. Espressione di più e meno infinito
float("inf"), float("-inf")
3. Simboli
1. In Python / significa divisione normale con resto, // significa divisione intera senza resto.
2. Non! in Python è rappresentato da not
3.Il quadrato in Python è **
4. Dichiarazione
1. È necessario aggiungere i due punti dopo tutte le istruzioni relative alle parole chiave: tranne return
2. Non è necessario aggiungere () alle condizioni nei cicli, nei giudizi e in altre istruzioni simili e non ci sono {} nei blocchi di istruzioni. Utilizzare il rientro per rappresentare rigorosamente il formato.
5. Commenti
1. Commento a riga singola n.
2. Commenti su più righe ''' o """
4.Codice
1. Elenco
1. Quando è necessario utilizzare esplicitamente gli indici degli elenchi, è necessario utilizzare G = [0]*(n 1) # per creare un elenco con una lunghezza di n 1, altrimenti l'indice sarà fuori limite.
2. Quando si crea un elenco bidimensionale a lunghezza fissa, se *n fallisce, provare a utilizzare un ciclo
dp = [[float('inf') for _ in range(n)] for _ in range(n)]
3. Se il valore restituito dalla funzione è un elenco, ma viene ricevuta solo una variabile, aggiungere una virgola
linea, = ax.plot(x, np.sin(x))
5.Programmazione Python
1. Problema relativo al file
1. Quando si importano altri progetti che richiedono un file, utilizzare il percorso assoluto del file.
2. File di esecuzione della riga di comando
Nome file Python.py
3. Risorse mirror del pacchetto di terze parti
Quando usi pycharm per scaricare pacchetti di terze parti, aggiungilo in Gestisci repository http://mirrors.aliyun.com/pypi/simple/, elimina l'URL originale
6.Tasti di scelta rapida Pycharm
1. Commento (aggiungi/elimina)
Ctrl/
Commenti a riga singola#
2.Codice spostamento a destra
Tab
3. Codice spostamento a sinistra
Scheda Maiusc
4. Rientro automatico
Ctrl alt I
5. Corri
CTRL MAIUSC F10
6.Formattazione standard PEP8
Ctrl-alt L
È anche il tasto di scelta rapida per il blocco QQ. Annulla l'impostazione in QQ.
6.1 Soluzione rapida
alt invio e premere invio
7. Copia una riga/più righe/parte selezionata
CTRL D
8. Cancella una/più righe
Ctrl Y
9.Trova
CTRLF
9.1 Ricerca globale
Ctrl Maiusc F
10.Sostituzione
CTRL R
10.1 Sostituzione globale
Ctrl Maiusc R
11.Spostare il cursore sulla riga successiva
turno Invio
12.Clic del cursore su più righe
alt clic con il pulsante sinistro del mouse
13. Passa al punto di interruzione successivo
alt F9
14.Cancellazione
CTRLZ
14.1 Anti-cancellazione
Ctrl-Shift Z
15. Copia il codice della classe genitore
Ctrl o
16. Selezionare il blocco parola/codice
Ctrl W
17. Visualizza rapidamente i documenti (informazioni sul codice)
CTRLQ
18. Inserire una linea verso il basso in qualsiasi posizione
entrare nel turno
19. Inserisci una riga verso l'alto in qualsiasi posizione
Ctrl-Alt Invio
20. Visualizza la vista del progetto
alt1
21. Visualizza la vista della struttura
alt7
22. Entra rapidamente nel codice
Ctrl clic sinistro
23. Visualizza rapidamente la cronologia
alt sinistra (ritorno)/tasto destro (avanti)
24. Visualizza rapidamente diversi metodi
alt su/giù
25. Cambia visualizzazione
Scheda Ctrl
26. Visualizza i file di risorse
cambiare due volte
27. Scopri dove viene chiamato il metodo
Ctrl alt H Fare doppio clic per determinare la posizione
28.Visualizza la classe genitore
Ctrl U
29. Visualizza la relazione di eredità
Ctrl H
Pagina 7.pycharm
0.Barra dei menu
1.Finestra di visualizzazione
1.Barra di navigazione della barra di navigazione
2.Refactoring Refactoring
3.Strumenti
sottoargomento
Controllo della versione 4.VCS
1. Eseguire il debug del codice
1. Fai clic davanti al codice per inserire un punto di interruzione, fai clic sul crawler per eseguire il debug e fai clic sulla casella accanto al crawler per terminare il debug
2. Fare clic sull'icona ↘ per passare al punto di interruzione successivo e sarà possibile osservare continuamente il valore della variabile.
2.impostazioni delle impostazioni
1.aspetto e comportamento, interfaccia e comportamento
1.aspetto generale dello stile
1.Tema a tema
1.Tema nero Darcula
2.Tema ad alto contrasto ad alto contrasto
3.Tema intelligente intelligente
2.carattere personalizzato carattere personalizzato
2.impostazioni di sistemaimpostazioni di sistema
1.aggiornamento
Il rilevamento automatico degli aggiornamenti può essere disattivato
2.Tasti di scelta rapida della mappa dei tasti
1. È possibile effettuare la ricerca direttamente in base alla visualizzazione del sistema (nota di commento)
3.editor modifica solo l'area
1. Carattere del codice carattere (dimensione regolabile)
2. Combinazione colori dell'area codice combinazione colori
3. Stile codice stile codice
0.Python può apportare modifiche ad ogni dettaglio
1.Pitone
1.Numero di rientro
2. Impostazione dello spazio spaziale
3. Avvolgimento e bretelle
1. Avvolgimento rigido con il numero massimo di codici in una riga
4.Righe vuote righe vuote
4.Ispezioni
1.PEP 8 è un codice standard, non un errore grammaticale. Cerca di mantenerlo il più standard possibile.
2. È possibile impostare quale contenuto viene controllato e anche impostare la severità del controllo in Gravità.
5.Modelli di file e codice Modelli di file e codice
1. Aggiungi informazioni in Python.Script che verranno visualizzate ogni volta che viene creato un nuovo file, Scopri quali informazioni puoi aggiungere online
6.Codifica file codifica file
1.UTF-8 predefinito
7.Modelli dinamici di Live Templates (facili da usare e padroneggiare)
0. Puoi fare clic sul segno più per aggiungere tu stesso il modello e assicurarti di impostare la posizione di utilizzo.
1.per ciclo
Scrivi iter per selezionare un ciclo e continua a premere Invio per scrivere il codice
2. Scrivere una lista utilizzando un ciclo for
Basta scrivere compl
4.Plugin
5.Progetto
1.Project Interpret interprete di progetto
1. Puoi gestire e aggiungere librerie di terze parti (fai clic sul segno più e cerca)
2. È possibile impostare diversi interpreti per il progetto corrente
3.Gestione del progetto
1. Fare clic con il pulsante destro del mouse sul file e selezionare Mostra in Explorer per aprire direttamente il percorso del file.
2.Nuovo
1.Archivio
Possono essere vari altri file, non necessariamente file Python.
2.Nuovo file Scratch
Crea file temporanei, equivalenti a fogli di carta, utilizzati per testare parte del codice
3.Directory
Crea una nuova cartella di livello inferiore
4.File Python
Rispetto alle cartelle ordinarie, ci sono più file Python di inizializzazione vuoti.
5. Quando crei un nuovo file HTML, puoi fare clic con il pulsante destro del mouse su di esso e aprirlo in un browser per vedere l'effetto di visualizzazione.
4. Pagina dei risultati del codice
1. Terminale terminale
1. È uguale al CMD di sistema. Puoi installare il pacchetto direttamente qui e utilizzare il comando dos.
2.Console della console Python
Puoi scrivere direttamente il codice Python, eseguirlo in modo interattivo e modificarlo riga per riga.
3.DA FARE
È equivalente a un promemoria. Scrivi TODO ('') nel punto di codice, in modo da poterlo trovare rapidamente e continuare a lavorare. Può essere utilizzato per la cooperazione reciproca.
4. L'avatar all'estrema destra
È possibile regolare la severità del controllo del codice. La modalità di risparmio energetico equivale a posizionare la freccia all'estrema sinistra. Tutti gli errori grammaticali non verranno controllati.
5.Ambiente virtuale
1.
2. L'ambiente virtuale viene creato perché nello sviluppo effettivo è necessario utilizzare contemporaneamente diverse versioni di interpreti Python e diverse versioni della stessa libreria. Pertanto è necessario creare un ambiente virtuale per isolare l'ambiente di progetto da altri ambienti (ambiente di sistema, altri ambienti virtuali)
3. Esistono tre modi per creare un ambiente virtuale in PyCharm, virtualen, conda e pipen
4. Virtualen può essere immaginato come la creazione di una copia isolata dell'ambiente di sistema corrente. L'interprete utilizzato è lo stesso installato (copia).
5. Conda seleziona una versione specifica di Python in base alle tue esigenze, quindi scarica la versione pertinente da Internet e crea un nuovo ambiente diverso dall'ambiente di sistema. Anche l'interprete utilizzato è diverso da quello installato.
6. Pipen è simile a virtualen. Crea anche una copia basata sull'ambiente di sistema esistente, ma pipen utilizza Pipfile invece del require.txt di virtualen per la gestione delle dipendenze, il che è più conveniente.
6.Interrelazione
7.SVN
1. Quando scarichi dal sito ufficiale, seleziona la versione inglese 1.10, che è diversa dalla versione cinese.
2. Quando installi TortoiseSVN.msi, assicurati di controllare gli strumenti da riga di comando
3. Una serie di file svn.exe apparirà nella directory bin installata.
4. Configura in pycharm e trova il file svn.exe nella directory bin in setting/version control/subversion
5. Fare clic con il pulsante destro del mouse sul file modificato nella cartella per visualizzare le modifiche
6. Fai clic con il pulsante destro del mouse sul progetto e verrà visualizzato un nuovo collegamento di sovversione. Puoi inviare commit e annullare tutte le modifiche ripristinate.
8. Plug-in facili da usare
1. Promotore chiave X
Insegnarti quale operazione di scelta rapida dovresti utilizzare per migliorare l'efficienza in questa operazione? Ricordati che al momento non hai impostato un tasto di scelta rapida per questa operazione. Che ne dici di impostarne uno rapidamente?
2.Tester Regex
Puoi testare le espressioni regolari. Fai clic sul piccolo pulsante rettangolare in basso a sinistra dell'interfaccia PyCharm per trovare l'opzione Regex Tester.
3.PEP8 automatico
pip installa autopep8, quindi importa questo strumento in PyCharm (Setting-Tools-External Tools). Le impostazioni specifiche sono le seguenti
4.CodiceGlance
Barra di scorrimento per la funzione di anteprima del codice
5.Profilo in PyCharm
Fare clic su Esegui -> Profilo "Programma" per eseguire l'analisi delle prestazioni del codice Fare clic sull'interfaccia del grafico delle chiamate per visualizzare in modo intuitivo il rapporto di chiamata diretta, il tempo di esecuzione e la percentuale di tempo di ciascuna funzione.
Analizzatore 6.Json
Controllo spesso se una stringa JSON è legale. In passato, il mio approccio era quello di aprire il sito Web online https://tool.lu/json/ e abbellirlo direttamente per verificare che solo il formato JSON sia corretto e legale , esiste un plug-in in PyCharm appositamente per farlo
7.Evidenziare la coppia di staffe
Colore della parentesi evidenziato nell'editor
8. Coloratore per staffe annidate
Mostra i colori per diverse coppie di parentesi
9.Ispeziona il codice in PyCharm
Fare clic sulla cartella del progetto, quindi fare clic con il pulsante destro del mouse e selezionare Ispeziona codice per attivare l'ispezione statica
5. Frammenti di codice comuni
1.Ingresso
1. Ottenere input dall'utente di lunghezza variabile
def getNum(): #Ottiene l'input dell'utente di lunghezza variabile numeri = [] iNumStr = input("Inserisci un numero (premi Invio per uscire): ") while iNumStr != "": nums.append(eval(iNumStr)) iNumStr = input("Inserisci un numero (premi Invio per uscire): ") restituire i numeri
2.Testo
1. Denoising e normalizzazione del testo inglese
def getText(): txt = open("frazione.txt", "r").read() txt = txt.lower() #Converti tutto in lettere minuscole for ch in '!"#$%&()* ,-./:;<=>?@[\\]^_'{|}~': txt = txt.replace(ch, " ") #Sostituisci i caratteri speciali nel testo con spazi restituire il testo
2. Conta la frequenza delle parole nel testo inglese
frazioneTxt = getText() parole = amleTxt.split() #Separa il testo con spazi e convertilo in una lista counts = {} #Crea un nuovo dizionario per parola in parole: #Conta la frequenza di ogni parola, il valore predefinito è 0 conta[parola] = conta.get(parola,0) 1 items = list(counts.items()) #Converti il dizionario in list items.sort(key=lambda x:x[1], reverse=True)# Ordina il secondo elemento in ordine inverso per i nell'intervallo(20): parola, conteggio = elementi[i] print ("{0:<10}{1:>5}".format(parola, conteggio))
3. Statistiche sulla frequenza delle parole del testo cinese
importare jieba txt = open("treregni.txt", "r", codifica='utf-8').read() parole = jieba.lcut(txt) conta = {} per parola in parole: se len(parola) == 1: Continua altro: conta[parola] = conta.get(parola,0) 1 elementi = lista(conta.items()) #Converti in lista per ordinare items.sort(key=lambda x:x[1], reverse=True) per i nell'intervallo(15): parola, conteggio = elementi[i] print ("{0:<10}{1:>5}".format(parola, conteggio))
3.Serie
1. Trova il valore massimo nell'array
1. Gli elementi vengono ripetuti
max_v, m_i = float(-inf), 0 #Combina un oggetto dati attraversabile (come una lista, una tupla o una stringa) in una sequenza di indici, Elenca i pedici dei dati e i dati allo stesso tempo for i, v in enumerate(nums): se v > max_v: max_v = v m_i = io
2. Nessun elemento ripetuto
max_v = max(numeri) m_i = numeri.indice(max_v)
2. Dicotomia
Soluzione di classe: def searchInsert(self, nums: List[int], target: int) -> int: sinistra, destra = 0, len(nums) #Utilizza l'intervallo chiuso sinistro e aperto destro [sinistra, destra) while left < right: # Apre a destra, quindi non può esserci =, l'intervallo non esiste mid = left (destra - sinistra)//2 # Previene l'overflow, //Indica la divisione intera if nums[mid] < target: # Il punto medio è inferiore al valore target Sul lato destro si possono ottenere posizioni uguali sinistra = metà 1 # Chiuso a sinistra, quindi 1 altro: destra = metà # Apri a destra, il vero punto finale destro è metà-1 return left # Al termine dell'algoritmo, è garantito che left = right e il ritorno sarà lo stesso per tutti.
4.Matplotlib
1. Sposta le coordinate su (0,0)
ascia = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('dati', 0)) ax.yaxis.set_ticks_position('sinistra') ax.spines['sinistra'].set_position(('dati', 0))
matematica
1. Calcola la media
def media(numeri): #Calcola la media s = 0,0 per num in numeri: s = s numero return s/len(numeri)
2. Calcola la varianza
def dev(numeri, media): #Calcola la varianza sdev = 0,0 per num in numeri: sdev = sdev (num - media)**2 return pow(sdev / (len(numeri)-1), 0.5)
3. Calcola la mediana
def mediana(numeri): #Calcola la mediana ordinati(numeri) dimensione = lunghezza(numeri) se dimensione % 2 == 0: med = (numeri[dimensione//2-1] numeri[dimensione//2])/2 altro: med = numeri[dimensione//2] restituire il medico
6. Il codice viene eseguito localmente
Infatti, basta definire una funzione principale, costruire un caso d'uso di input, quindi definire una variabile di soluzione e chiamare la funzione minCostClimbingStairs.
0. Abilità di scrittura del codice
1.Formatta
1. Vuoi scrivere più righe in una riga
Aggiungi un punto e virgola alla fine di ogni riga;
2. Una riga è troppo lunga e voglio avvolgerla in una nuova riga.
Basta aggiungere una barra \ alla fine di questa riga
Relativo all'algoritmo
pensiero classico
0.Problemi riscontrati frequentemente
1. Prevenire il traboccamento
1. Quando si calcola il prodotto di molti numeri, per evitare l'overflow, è possibile prendere il logaritmo del prodotto e trasformarlo sotto forma di addizione.
1.Struttura dei dati
1.Array
0.Base
1. Finché vedi che l'array fornito nella domanda dell'intervista è un array ordinato, puoi pensare se puoi utilizzare il metodo della dicotomia
2. Gli elementi dell'array sono continui nell'indirizzo di memoria Un elemento dell'array non può essere cancellato singolarmente, ma solo sovrascritto.
1. Lo stesso elemento dell'array non può essere attraversato due volte.
for (int i = 0; i < numeri.lunghezza; i ) { for (int j = i 1; j < numeri.lunghezza; j ) {
2. Problema della matrice circolare
Quando c'è un vincolo che la testa e la coda non possono essere allo stesso tempo, scomponi la matrice circolare in diversi problemi di matrice ordinaria e trova il valore massimo
3. Problema dell'intervallo dicotomico
1. Sinistra chiusa e destra chiusa [sinistra, destra]
int middle = left ((destra - sinistra) / 2);// previene l'overflow, equivalente a (sinistra destra)/2
while (sinistra <= destra) { // Se sinistra==destra, l'intervallo [sinistra, destra] è ancora valido
if (nums[medio] > target) { destra = centro - 1; // il bersaglio è nell'intervallo a sinistra, quindi [sinistra, centro - 1]
} else if (nums[middle] < target) { sinistra = centro 1; // il bersaglio è nell'intervallo giusto, quindi [centro 1, destra]
2. Chiudi a sinistra e apri a destra [sinistra, destra)
while (sinistra < destra) { // Perché quando sinistra == destra, [sinistra, destra) è uno spazio non valido
if (nums[medio] > target) { destra = centro; // il bersaglio è nell'intervallo sinistro, in [sinistra, centro)
} else if (nums[middle] < target) { sinistra = centro 1; // il bersaglio è nell'intervallo destro, in [centro 1, destra)
2. Tabella hash
0. Finché si tratta di contare il numero di occorrenze di un determinato numero/valore, utilizzare una tabella hash
1. La tabella hash contiene il numero corrispondente di un certo numero e non è essa stessa
for (int i = 0; i < numeri.lunghezza; i ) { complemento int = target - nums[i]; if (map.containsKey(complemento) && map.get(complemento) != i)
3. Elenco collegato
1. Aggiungi i numeri in due elenchi collegati
1. In caso di inversione dell'ordine: l'eventuale carry issue dell'ultima aggiunta deve essere considerata separatamente.
2. In sequenza in avanti: invertire l'elenco collegato/utilizzare la struttura dei dati dello stack per ottenere l'inversione
2. Trova la mediana di una lista ordinata concatenata singolarmente (a sinistra chiusa e a destra aperta)
Supponiamo che l'endpoint sinistro dell'elenco collegato corrente sia a sinistra, l'endpoint destro sia a destra e che la relazione di inclusione sia "chiusa a sinistra, aperta a destra". L'elenco collegato fornito è un elenco collegato unidirezionale. È molto facile accedervi elementi successivi, ma non può accedere direttamente agli elementi precedenti. Pertanto, dopo aver trovato il nodo mediano a metà dell'elenco collegato, se si imposta la relazione "sinistra chiusa, destra aperta", è possibile utilizzare direttamente (sinistra, metà) e (mid.next, destra) per rappresentare la lista corrispondente a i sottoalberi sinistro e destro Non è necessario mid.pre e l'elenco iniziale può anche essere convenientemente rappresentato da (head, null)
4.Personaggi
1. Registra se ciascun carattere appare
Set hash: Set<Carattere> occ = new HashSet<Carattere>();
5. Numeri
1. Riportare l'acquisizione per sommare due numeri a una cifra
int somma = riporto x y; int riporto = somma/10;
2. Inversione dell'intero
Per "estrarre" e "spingere" i numeri senza l'aiuto di uno stack/array ausiliario, possiamo usare la matematica, eliminare prima l'ultima cifra, quindi dividere per 10 per rimuovere l'ultima cifra, invertire il numero e continuare a moltiplicarsi dopo 10 , aggiungi l'ultima cifra eliminata e determina prima se andrà in overflow.
6.Albero
1. Trova i nodi precursori
Fai un passo a sinistra, poi continua a camminare a destra finché non puoi andare oltre
predecessore = root.sinistra; while (predecessore.destra!= null && predecessore.destra!= root) { predecessore = predecessore.destra; }
7. Raccolta
1. Rimuovi gli elementi duplicati dall'elenco
s = set(ls); lt = lista(i)
8. Tupla
1. Se non si desidera che i dati vengano modificati dal programma, convertirli in un tipo tupla
lt = tupla(ls)
2. Algoritmo classico
1.Doppio puntatore
0. Comunemente utilizzato in matrici ed elenchi collegati
1. Quando è necessario enumerare due elementi in un array, se si scopre che all'aumentare del primo elemento, il secondo elemento diminuisce, è possibile utilizzare il metodo del doppio puntatore per spostare il secondo puntatore dalla fine dell'array. Inizia l'attraversamento mentre assicurando che il secondo puntatore sia maggiore del primo puntatore, riducendo la complessità temporale dell'enumerazione da O(N^2) a O(N)
2. Quando il risultato della ricerca rientra in un determinato intervallo, i doppi puntatori vengono utilizzati per cambiare continuamente, in modo simile al meccanismo della finestra scorrevole.
2. Metodo del puntatore veloce e lento
Inizialmente, sia il puntatore fast fast che il puntatore slow slow puntano all'endpoint sinistro a sinistra dell'elenco collegato. Mentre spostiamo velocemente il puntatore veloce a destra due volte, spostiamo il puntatore lento a destra una volta finché il puntatore veloce non raggiunge il confine (cioè, il puntatore veloce raggiunge l'estremità destra o il nodo successivo del puntatore veloce è quello destro punto finale). In questo momento, l'elemento corrispondente all'indicatore lento è la mediana
3. Programmazione dinamica
1. Il numero richiesto può essere ottenuto tramite alcune operazioni tramite il numero richiesto precedente ed è possibile trovare l'equazione di trasferimento dinamico
2.Condizioni
Se un problema presenta molti sottoproblemi sovrapposti, la programmazione dinamica è la più efficace.
3. Cinque passaggi
1. Determinare il significato dell'array dp (tabella dp) e degli indici 2. Determinare la formula di ricorsione 3.Come inizializzare l'array dp 4. Determinare l'ordine di attraversamento 5. Derivazione dell'array dp con esempi
Perché determinare prima la formula di ricorsione e poi considerare l'inizializzazione? Perché in alcuni casi la formula ricorsiva determina come inizializzare l'array dp
4.Come eseguire il debug
1. Stampa l'array dp e vedi se viene dedotto secondo le tue idee.
2. Prima di scrivere il codice, assicurati di simulare la situazione specifica del trasferimento di stato sull'array dp e assicurati che il risultato finale sia quello desiderato.
5.Scorri la matrice
Quando l'equazione ricorsiva è correlata solo a pochi numeri adiacenti, è possibile utilizzare una matrice mobile per ottimizzare la complessità dello spazio su O(1)
4. Ricorsione
0. Trilogia ricorsiva
Condizione di terminazione della ricorsione, cosa fa questa ricorsione e cosa restituisce
3. Tecniche comunemente utilizzate
1. Uso intelligente degli indici degli array
1.Applicazione
L'indice di un array è un array implicitamente utile, specialmente quando si contano alcuni numeri (trattando il valore dell'array corrispondente come l'indice temp[arr[i]] del nuovo array) o per determinare se compaiono alcuni numeri interi
2.Esempi
1. Quando ti forniamo una stringa di lettere e ti chiediamo di determinare il numero di volte in cui queste lettere appaiono, possiamo usare queste lettere come pedici Durante l'attraversamento, se la lettera a viene attraversata, allora arr[a] può essere aumentata di 1. Cioè, arr[a]. Grazie a questo uso intelligente dei pedici, non abbiamo bisogno di giudicare lettera per lettera.
2. Ti vengono forniti n array di numeri interi non ordinati arr e l'intervallo di valori di questi numeri interi è compreso tra 0 e 20. È necessario ordinare questi n numeri da piccolo a grande con complessità temporale O(n). ordine e utilizzare il valore corrispondente come indice dell'array. Se questo numero è apparso in precedenza, aggiungere 1 all'array corrispondente.
2. Usa il resto con abilità
1.Applicazione
Quando si attraversa l'array, verrà eseguita una valutazione di fuori limite. Se il pedice è quasi fuori limite, lo imposteremo su 0 e lo attraverseremo nuovamente. Soprattutto in alcuni array ad anello, come le code implementate con array pos = (pos 1) % N
3. Usa abilmente i doppi puntatori
1.Applicazione
Per i puntatori doppi è particolarmente utile quando si pongono domande su elenchi collegati singolarmente.
2.Esempi
1. Determina se una lista concatenata singolarmente ha un ciclo
Imposta un puntatore lento e un puntatore veloce per attraversare l'elenco collegato. Il puntatore lento si sposta di un nodo alla volta, mentre il puntatore veloce si sposta di due nodi alla volta. Se non è presente alcun ciclo nell'elenco collegato, il puntatore veloce attraverserà per primo l'elenco. Se è presente un ciclo, lo farà il puntatore veloce incontrare il puntatore lento durante la seconda traversata.
2. Come trovare il nodo centrale dell'elenco collegato in un attraversamento
Lo stesso vale per impostare un puntatore veloce e uno lento. Quello lento si muove di un nodo alla volta, mentre quello veloce ne muove due. Quando si attraversa l'elenco collegato, una volta completata la corsa veloce del puntatore, il puntatore lento raggiunge appena il punto medio
3. Il nodo k-esimo dall'ultimo in un elenco collegato singolarmente
Imposta due puntatori, uno dei quali sposta prima k nodi. Successivamente entrambi i puntatori si muovono alla stessa velocità. Quando il primo puntatore spostato completa la traversata, il secondo puntatore si trova esattamente al kesimo nodo dal basso.
sottoargomento
4. Utilizzare abilmente le operazioni di turno
1.Applicazione
1. A volte quando eseguiamo operazioni di divisione o moltiplicazione, come n/2, n/4, n/8, possiamo utilizzare il metodo di spostamento per eseguire l'operazione. Attraverso l'operazione di spostamento, la velocità di esecuzione sarà più veloce
2. Esistono anche alcune operazioni come & (e) e | (o), che possono anche velocizzare l'operazione.
2.Esempi
1. Per determinare se un numero è dispari, sarà molto più veloce utilizzare l'operazione AND.
5. Imposta la posizione della sentinella
1.Applicazione
1. Nelle questioni correlate alle liste collegate, spesso impostiamo un puntatore head e questo puntatore head non memorizza alcun dato valido. Solo per comodità di funzionamento, possiamo chiamare questo puntatore head bit sentinella.
2. Quando si utilizza un array, è anche possibile impostare una sentinella, utilizzando arr[0] come sentinella.
2.Esempi
1. Quando vogliamo eliminare il primo nodo, se non è impostato un bit sentinella, l'operazione sarà diversa dall'operazione di eliminazione del secondo nodo. Ma abbiamo predisposto una sentinella, quindi eliminare il primo nodo ed eliminare il secondo nodo sono gli stessi nel funzionamento, senza esprimere giudizi aggiuntivi. Naturalmente lo stesso vale quando si inseriscono i nodi
2. Quando vuoi giudicare se due elementi adiacenti sono uguali, impostare una sentinella non ti preoccuperà dei problemi transfrontalieri puoi direttamente arr[i] == arr[i-1]. Non aver paura di oltrepassare il confine quando i = 0
6. Alcune ottimizzazioni relative alla ricorsione
1. Considerare la preservazione dello stato per problemi che possono essere ricorsivi.
1. Quando utilizziamo la ricorsione per risolvere un problema, è facile calcolare ripetutamente lo stesso sottoproblema. In questo momento dobbiamo considerare la conservazione dello stato per evitare calcoli ripetuti.
2.Esempi
0. Una rana può saltare 1 gradino o 2 gradini alla volta. Scopri in quanti modi la rana può saltare su una scala a n livelli.
1. Questo problema può essere facilmente risolto utilizzando la ricorsione. Supponiamo che f(n) rappresenti il numero totale di passi per n passi, allora f(n) = f(n-1) f(n - 2)
2. La condizione finale della ricorsione è quando 0 <= n <= 2, f(n) = n, è facile scrivere codice ricorsivo
3. Tuttavia, per i problemi che possono essere risolti utilizzando la ricorsione, dobbiamo considerare se ci sono molti calcoli ripetuti. Ovviamente, per la ricorsione di f(n) = f(n-1) f(n-2), ci sono molti calcoli ripetuti.
4. Questa volta dobbiamo considerare la preservazione dello Stato. Ad esempio, puoi utilizzare hashMap per salvare. Ovviamente puoi anche utilizzare un array. In questo momento puoi utilizzare gli indici dell'array come abbiamo detto sopra. Quando arr[n] = 0, significa che n non è stato calcolato. Quando arr[n] != 0, significa che f(n) è stato calcolato. A questo punto, il valore calcolato può essere restituito direttamente.
5. In questo modo l'efficienza dell'algoritmo può essere notevolmente migliorata. Alcuni chiamano questo tipo di conservazione dello stato anche il metodo memo.
2. Pensa dal basso verso l’alto
1. Per i problemi ricorsivi, di solito si procede dall'alto verso il basso finché la ricorsione non raggiunge il fondo, quindi si restituisce il valore strato per strato.
2. Tuttavia, a volte quando n è relativamente grande, come quando n = 10000, è necessario eseguire la ricorsione verso il basso di 10000 livelli fino a n <= 2 prima di restituire lentamente il risultato. Se n è troppo grande, lo spazio dello stack potrebbe non esserlo Abbastanza
3. Per questa situazione, possiamo effettivamente considerare un approccio dal basso verso l'alto.
4. Questo approccio dal basso verso l'alto è anche chiamato ricorsione
3. Vantaggi rispetto ad altre lingue
1.Array
1. I parametri sono array parziali
In Python, i parametri possono restituire direttamente parte dell'array nums[:i]. Non è necessario riprogettare un metodo per intercettare l'array in base al pedice come in Java.
2. Ottieni elementi e pedici allo stesso tempo
for i, v in enumerate(nums):
Strutture dati/algoritmi di uso comune
1. Elenco collegato
2.Impila
1. Stack monotono
1.Definizione
Uno stack in cui gli elementi nello stack aumentano o diminuiscono in modo monotono. Uno stack monotono può essere utilizzato solo nella parte superiore dello stack.
2. Natura
1. Gli elementi nello stack monotono sono monotoni.
2. Prima che gli elementi vengano aggiunti allo stack, tutti gli elementi che distruggono la monotonia dello stack verranno eliminati dalla cima dello stack.
3. Usa lo stack monotono per trovare l'elemento e attraversa a sinistra fino al primo elemento che è più piccolo di esso (stack incrementale)/trova l'elemento e attraversa a sinistra fino al primo elemento che è più grande di esso.
3. Coda
4.Albero
1.BST: albero di ricerca binaria Albero di ordinamento binario
1.Definizione
1. Le chiavi di tutti i nodi del sottoalbero sinistro sono inferiori al nodo radice
2. Le parole chiave di tutti i nodi del sottoalbero destro sono maggiori del nodo radice
3. I sottoalberi sinistro e destro sono ciascuno un albero di ordinamento binario.
L'attraversamento in ordine può ottenere una sequenza ordinata crescente
2.AVL: albero binario bilanciato
1.Definizione
1. Albero binario bilanciato: il valore assoluto della differenza di altezza tra i sottoalberi sinistro e destro di qualsiasi nodo non supera 1
2. Fattore di equilibrio: la differenza di altezza tra i sottoalberi sinistro e destro del nodo -1,0,1
3.mct: albero di copertura minimo
1.Definizione
Ogni albero che consiste solo degli archi di G e contiene tutti i vertici di G è chiamato albero di copertura di G.
5. Figura
1. Terminologia
1. Cluster (sottografo completo)
Insieme di punti: c'è un bordo che collega due punti qualsiasi.
1.1 Insieme indipendente dal punto
Insieme di punti: tra due punti qualsiasi non esiste alcun bordo
2. Grafico di Hamilton Hamilton
Un grafo non orientato va da un punto iniziale specificato a un punto finale specificato, passando per tutti gli altri nodi una sola volta. Un percorso hamiltoniano chiuso è chiamato ciclo hamiltoniano, mentre un percorso contenente tutti i vertici del grafico è chiamato percorso hamiltoniano.
6. Algoritmo
1.BFS: Prima ricerca in ampiezza
1.Definizione
Un algoritmo di attraversamento gerarchico simile a un albero binario, che dà priorità al primo nodo scoperto.
2.DFS: prima ricerca in profondità
1.Definizione
Similmente all'attraversamento preordinato di un albero, viene data priorità all'ultimo nodo scoperto.
buon senso
1.Quante operazioni al secondo?
2. Complessità temporale di algoritmi ricorsivi
Numero di ricorsioni * Numero di operazioni in ciascuna ricorsione
3. Complessità spaziale di algoritmi ricorsivi
Profondità di ricorsione * complessità spaziale di ciascuna ricorsione
Focus sulla struttura dei dati
Albero
1.BST: albero di ricerca binaria
1. La ricerca di nodi a partire dal nodo radice è in realtà un processo di ricerca binaria, quindi BST è anche chiamato albero di ricerca binario.
2. L'attraversamento in ordine della BST può ottenere una sequenza ordinata
3. La posizione di inserimento deve essere la posizione dell'anta, che non causerà la regolazione della struttura dell'albero.
4. CurrentNode=null non può essere utilizzato per eliminare i nodi in Java.
currentNode viene passato attraverso i parametri del metodo, quindi currentNode=null non causerà la distruzione dell'oggetto, perché l'oggetto è ancora mantenuto da un riferimento esterno al metodo e dovrebbe essere distrutto attraverso il nodo genitore.
5. Chiedi informazioni sulle intenzioni della BST durante l’intervista
In generale, il semplice BST non viene utilizzato nella pratica ingegneristica perché presenta un difetto fatale: la struttura ad albero viene facilmente sbilanciata. Considera un caso estremo, se la sequenza di valori chiave per costruire il BST è ordinata, inserisci i nodi uno per uno e il il BST risultante è in realtà un elenco collegato e la complessità del tempo di operazione della query dell'elenco collegato non può raggiungere o(logN), il che viola l'intento progettuale originale del BST. Anche se questa situazione estrema non si verifica, anche la struttura BST diventerà gradualmente sbilanciato nel processo di inserimento ed eliminazione continua di nodi, e la struttura ad albero diventerà sempre più inclinata. Questo squilibrio della struttura diventerà sempre più sfavorevole per le operazioni di query.
Soluzione: utilizzare BST con proprietà autobilancianti, come AVL e albero rosso-nero
sottoargomento
sottoargomento
sottoargomento
2. Albero rosso-nero RBT
1.Definizione
①Ogni nodo ha un colore, nero o rosso ②Il nodo radice è nero ③Ogni nodo foglia (nodo vuoto NIL) è nero ④Se un nodo è rosso, i suoi nodi figli devono essere neri ⑤Tutti i percorsi da qualsiasi nodo a ciascun nodo foglia del nodo contengono lo stesso numero di nodi neri.
2.Illustrazione
3. Proprietà dell'equilibrio
Proprietà ⑤, garantisce che tra tutti i percorsi che iniziano da qualsiasi nodo al suo nodo foglia, la lunghezza del percorso più lungo non supererà il doppio della lunghezza del percorso più breve. Pertanto, l'albero rosso-nero è un albero binario relativamente vicino all'equilibrio.
Inoltre, la proprietà ⑤ indica chiaramente che il numero di livelli di nodi neri nei sottoalberi sinistro e destro di ciascun nodo è uguale, quindi i nodi neri dell'albero rosso-nero sono perfettamente bilanciati.
6. La differenza tra inserimento e cancellazione
Per il nodo inserito, la posizione inserita deve essere un nodo foglia, quindi durante la regolazione non ci saranno problemi a questo livello, quindi regola dalla relazione tra il nodo genitore e lo zio Per eliminare i nodi, la posizione di regolazione non è necessariamente il nodo foglia, quindi durante la regolazione potrebbero esserci problemi su questo livello stesso, quindi la relazione tra il nodo e i suoi fratelli verrà regolata da ora in poi.
punti di conoscenza labuladuo
0.Serie da leggere
1. Pensiero quadro per l'apprendimento di algoritmi e la risoluzione di domande
1. Metodo di archiviazione della struttura dei dati
1. Esistono solo due tipi: array (archiviazione sequenziale) e elenco collegato (archiviazione collegata)
2. Esistono anche varie strutture di dati come tabelle hash, stack, code, heap, alberi, grafici, ecc., che appartengono tutte alla "sovrastruttura", mentre gli array e gli elenchi collegati sono la "base strutturale". quelle diverse strutture di dati. Le loro origini sono operazioni speciali su elenchi o array collegati e le API sono semplicemente diverse.
3. Introduzione alle varie strutture
1. Le due strutture dati "queue" e "stack" possono essere implementate utilizzando elenchi concatenati o array. Se lo implementi con un array, devi affrontare il problema dell'espansione e della contrazione; se lo implementi con una lista concatenata, non hai questo problema, ma hai bisogno di più spazio di memoria per memorizzare i puntatori dei nodi.
2. Due metodi di rappresentazione del "grafico", l'elenco di adiacenza è un elenco collegato e la matrice di adiacenza è un array bidimensionale. La matrice di adiacenza determina rapidamente la connettività e può eseguire operazioni sulla matrice per risolvere alcuni problemi, ma richiede molto spazio se il grafico è scarso. Le liste di adiacenza risparmiano spazio, ma molte operazioni non sono sicuramente efficienti quanto le matrici di adiacenza.
3. La "tabella hash" mappa le chiavi su un array di grandi dimensioni tramite una funzione hash. E come metodo per risolvere i conflitti di hash, il metodo zip richiede caratteristiche di elenco concatenato, che è semplice da utilizzare, ma richiede spazio aggiuntivo per memorizzare i puntatori; il metodo di sondaggio lineare richiede caratteristiche di array per facilitare l'indirizzamento continuo e non richiede spazio di archiviazione per i puntatori , ma l'operazione è un po' complicata
4. L'"albero", implementato con un array, è un "heap", poiché l'"heap" è un albero binario completo. L'utilizzo di un array per l'archiviazione non richiede puntatori ai nodi e l'operazione è relativamente semplice utilizzando un elenco collegato implementarlo è un "albero" molto comune, perché non è necessariamente un albero binario completo, quindi non è adatto per l'archiviazione di array. Per questo motivo, sono stati derivati vari progetti ingegnosi basati su questa struttura ad "albero" di elenchi concatenati, come alberi di ricerca binari, alberi AVL, alberi rosso-neri, alberi a intervalli, alberi B, ecc., per affrontare diversi problemi.
4. Vantaggi e svantaggi
1. Poiché gli array sono compatti e di archiviazione continua, è possibile accedervi in modo casuale, gli elementi corrispondenti possono essere trovati rapidamente tramite gli indici e lo spazio di archiviazione viene relativamente risparmiato. Ma a causa della memorizzazione continua, lo spazio di memoria deve essere allocato in una sola volta. Pertanto, se l'array vuole espandersi, deve riallocare uno spazio più grande e quindi copiare lì tutti i dati se si desidera Durante l'inserimento e l'eliminazione al centro dell'array, tutti i dati successivi devono essere spostati ogni volta per mantenere la continuità. La complessità temporale è O(N).
2. Poiché gli elementi di un elenco collegato non sono continui, ma si basano su puntatori per puntare alla posizione dell'elemento successivo, non vi è alcun problema di espansione dell'array se si conosce il predecessore e il successore di un determinato elemento, è possibile eliminarlo l'elemento o inserire un nuovo elemento utilizzando il puntatore Complessità temporale O(1). Tuttavia, poiché lo spazio di archiviazione non è continuo, non è possibile calcolare l'indirizzo dell'elemento corrispondente in base a un indice, quindi l'accesso casuale non è possibile e poiché ciascun elemento deve memorizzare un puntatore alla posizione dell'elemento precedente e precedente, esso consumerà relativamente più spazio di archiviazione.
2. Operazioni fondamentali delle strutture dati
1. L'operazione di base non è altro che un accesso trasversale. Per essere più specifici, è: aggiungere, eliminare, controllare e modificare
2. Dal livello più alto, ci sono solo due forme di attraversamento e accesso a varie strutture dati: lineare e non lineare.
3. Lineare è rappresentato dall'iterazione for/ while e non lineare è rappresentato dalla ricorsione.
4. Alcune traversate tipiche
1.Array
void traverse(int[] arr) { for (int i = 0; i < arr.lunghezza; i ) { // Itera su arr[i] } }
2. Elenco collegato
/* Nodo base di liste collegate singolarmente */ classe ElencoNodo { valore intero; ListNode successivo; } traversata del vuoto(testa ListNode) { for (ListNode p = testa; p != null; p = p.successivo) { //Accedi iterativamente a p.val } } traversata del vuoto(testa ListNode) { // Accede ricorsivamente a head.val traversa(head.next) }
3. Albero binario
/* Nodo base dell'albero binario */ classeAlberoNodo { valore intero; TreeNode sinistra, destra; } attraversamento vuoto(radice TreeNode) { traversa(root.left) traversa(root.right) }
4.Albero N-ario
/* Nodo base dell'albero N-ario */ classeAlberoNodo { valore intero; TreeNode[] figli; } attraversamento vuoto(radice TreeNode) { per (Figlio TreeNode: root.children) traversa(bambino); }
5. Il cosiddetto quadro è una routine. Indipendentemente da aggiunte, eliminazioni o modifiche, questi codici sono una struttura che non può mai essere separata. È possibile utilizzare questa struttura come struttura e aggiungere semplicemente codici al framework in base a problemi specifici.
3. Guida alla scrittura delle domande sugli algoritmi
1. Spazzola prima l'albero binario, spazzola prima l'albero binario, spazzola prima l'albero binario!
2. Gli alberi binari sono i più facili da coltivare nel pensiero quadro e la maggior parte delle tecniche algoritmiche sono essenzialmente problemi di attraversamento degli alberi.
3. Non sottovalutare queste poche righe di codice non funzionante Quasi tutti i problemi dell'albero binario possono essere risolti utilizzando questo framework.
attraversamento vuoto(radice TreeNode) { // Attraversamento del preordine traversa(root.left) // Attraversamento in ordine traversa(root.right) // Attraversamento postordine }
4. Se non sai come iniziare o hai paura delle domande, potresti anche iniziare con l'albero binario. Le prime 10 domande potrebbero essere un po' scomode, fai altre 20 domande basate sul quadro, e forse tu avrà una certa comprensione; finisci l'intero argomento prima di farlo Se guardi indietro all'argomento delle regole di divisione e conquista, scoprirai che qualsiasi problema che coinvolge la ricorsione è un problema di albero.
5. Cosa devo fare se non riesco a comprendere così tanti codici? Estraendo direttamente il framework, puoi vedere l'idea centrale: infatti, molti problemi di programmazione dinamica implicano l'attraversamento di un albero, se hai familiarità con le operazioni di attraversamento degli alberi, saprai almeno come convertire le idee in codice, e lo saprai anche tu sapere come estrarne gli altri. L'idea centrale della soluzione
2. Quadro di routine per la risoluzione dei problemi di programmazione dinamica
1.Concetti di base
1.Modulo
La forma generale del problema di programmazione dinamica consiste nel trovare il valore ottimale. La programmazione dinamica è in realtà un metodo di ottimizzazione nella ricerca operativa, ma è più comunemente utilizzata nei problemi informatici. Ad esempio, ti chiede di trovare la sottosequenza crescente più lunga e la distanza di modifica minima.
2. Questioni fondamentali
Il problema principale è l’esaurimento. Poiché richiediamo il valore migliore, dobbiamo enumerare in modo esaustivo tutte le possibili risposte e quindi trovare tra queste il valore migliore.
3.Tre elementi
1. Sottoproblemi sovrapposti
0. Esistono "sottoproblemi sovrapposti" in questo tipo di problema. Se il problema è la forza bruta, l'efficienza sarà estremamente inefficiente. Pertanto, è necessario un "memo" o una "tabella DP" per ottimizzare il processo esaustivo ed evitarlo calcoli inutili.
2. Sottostruttura ottimale
0. Il problema di programmazione dinamica deve avere una "sottostruttura ottimale", in modo che il valore ottimo del problema originale possa essere ottenuto attraverso il valore ottimo del sottoproblema.
3. Equazione di transizione di stato
0. I problemi possono essere in continua evoluzione e non è facile enumerare in modo esaustivo tutte le soluzioni fattibili. Solo elencando la corretta "equazione di transizione di stato" possiamo enumerarle correttamente in modo esaustivo.
1. Quadro di pensiero
Chiarire il caso base -> chiarire lo "stato" -> chiarire la "selezione" -> definire il significato dell'array/funzione dp
#Inizializza il caso base dp[0][0][...] = base # Esegue il trasferimento di stato per lo stato 1 in tutti i valori dello stato 1: per lo stato 2 in tutti i valori dello stato 2: per... dp[stato 1][stato 2][...] = trova il valore massimo (seleziona 1, seleziona 2...)
2. Sequenza di Fibonacci
1. Ricorsione violenta
1.Codice
int fib(int N) { if (N == 1 || N == 2) restituisce 1; restituire fib(N - 1) fib(N - 2); }
2. Albero ricorsivo
1. Ogni volta che incontri un problema che richiede la ricorsione, è meglio disegnare un albero di ricorsione. Questo ti sarà di grande aiuto per analizzare la complessità dell'algoritmo e trovare le ragioni dell'inefficienza dell'algoritmo.
2.Immagini
3. Complessità temporale di algoritmi ricorsivi
0. Moltiplicare il numero di sottoproblemi per il tempo necessario per risolvere un sottoproblema
1. Innanzitutto calcolare il numero di sottoproblemi, ovvero il numero totale di nodi nell'albero di ricorsione. Ovviamente il numero totale di nodi dell'albero binario è esponenziale, quindi il numero di sottoproblemi è O(2^n)
2. Quindi calcola il tempo per risolvere un sottoproblema. In questo algoritmo non esiste un ciclo, solo f(n - 1) f(n - 2) un'operazione di addizione, il tempo è O(1).
3. La complessità temporale di questo algoritmo è la moltiplicazione dei due, ovvero O(2^n), livello esponenziale, esplosione
4. Osservando l'albero di ricorsione, è ovvio che si trova la ragione dell'inefficienza dell'algoritmo: ci sono molti calcoli ripetuti.
5. Questa è la prima proprietà dei problemi di programmazione dinamica: sottoproblemi sovrapposti. Successivamente, proveremo a risolvere questo problema
2. Soluzione ricorsiva con memo
1. Poiché il motivo che richiede tempo sono i calcoli ripetuti, possiamo creare un "memo". Non tornare indietro dopo aver calcolato la risposta a una determinata sotto-domanda, annotarla nel "memo" prima di tornare ogni volta incontri una sotto-domanda, controlla prima il problema in "Memo". Se trovi che il problema è stato risolto in precedenza, prendi semplicemente la risposta e usala invece di perdere tempo a fare calcoli.
2. Generalmente, come "memo" viene utilizzato un array. Naturalmente è anche possibile utilizzare una tabella hash (dizionario).
3.Codice
int fib(int N) { se (N < 1) restituisce 0; //Il promemoria è tutto inizializzato a 0 vettore<int>memo(N 1, 0); // Esegue la ricorsione con memo aiutante per la restituzione(memo, N); } int helper(vettore<int>& memo, int n) { // caso base if (n == 1 || n == 2) restituisce 1; //Già calcolato if (memo[n] != 0) return memo[n]; memo[n] = helper(memo, n - 1) helper(memo, n - 2); promemoria di ritorno[n]; }
4. Albero ricorsivo
Infatti, l'algoritmo ricorsivo con "memo" trasforma un albero ricorsivo con enorme ridondanza in un grafo ricorsivo senza ridondanza attraverso il "pruning", che riduce notevolmente il numero di sottoproblemi (ovvero il numero di nodi ricorsivi nel grafo)
5. Complessità
Non vi è alcun calcolo ridondante in questo algoritmo, il numero di sottoproblemi è O(n) e la complessità temporale di questo algoritmo è O(n). Rispetto agli algoritmi violenti, si tratta di un attacco di riduzione della dimensione
6.Confronto con la programmazione dinamica
L'efficienza della soluzione ricorsiva con memo è la stessa della soluzione di programmazione dinamica iterativa, tuttavia questo metodo è chiamato "top-down" e la programmazione dinamica è chiamata "bottom-up".
7. Dall'alto verso il basso
L'albero di ricorsione (o immagine) disegnato si estende dall'alto verso il basso, partendo da un problema originale più ampio come f(20), e gradualmente scompone la scala verso il basso fino a f(1) e f(2). Questi due casi base restituiscono quindi il risposte strato dopo strato.
8. Dal basso verso l'alto
Basta iniziare dal basso, il problema più semplice e più piccolo, con dimensioni f(1) e f(2), e spingersi verso l'alto fino a raggiungere la risposta desiderata f(20). Questa è l'idea della programmazione dinamica. ed è per questo che la programmazione dinamica Planning generalmente si allontana dalla ricorsione e completa invece i calcoli tramite l'iterazione del ciclo
3.Soluzione iterativa dell'array dp
1. Pensieri
Prendendo spunto dal "memo" del passaggio precedente, possiamo separare questo "memo" in una tabella, chiamiamola tabella DP. Non sarebbe carino completare i calcoli "dal basso verso l'alto" su questa tabella?
2.Codice
int fib(int N) { vettore<int> dp(N 1, 0); // caso base dp[1] = dp[2] = 1; for (int i = 3; i <= N; i ) dp[i] = dp[i - 1] dp[i - 2]; ritorna dp[N]; }
3. Equazione di transizione di stato
1. È il fulcro della risoluzione dei problemi. Ed è facile scoprire che, in effetti, l’equazione di transizione di stato rappresenta direttamente la soluzione della forza bruta
2. Non disprezzare le soluzioni violente La cosa più difficile riguardo ai problemi di programmazione dinamica è scrivere questa soluzione violenta, cioè l’equazione di transizione dello stato. Finché scrivi una soluzione di forza bruta, il metodo di ottimizzazione non è altro che l'utilizzo di un memo o di una tabella DP, non c'è alcun mistero.
4. Compressione dello stato
1. Lo stato corrente è correlato solo ai due stati precedenti. In effetti, non è necessaria una tabella DP così lunga per memorizzare tutti gli stati. È sufficiente trovare un modo per memorizzare i due stati precedenti. Pertanto, può essere ulteriormente ottimizzato per ridurre la complessità spaziale a O(1)
2.Codice
int fib(int n) { se (n == 2 || n == 1) ritorno 1; int precedente = 1, corrente = 1; for (int i = 3; i <= n; i ) { somma int = prev curr; prec = corrente; valuta = somma; } valuta di ritorno; }
3. Questa tecnica è la cosiddetta "compressione dello stato". Se scopriamo che ogni trasferimento di stato richiede solo una parte della tabella DP, allora possiamo provare a utilizzare la compressione dello stato per ridurre la dimensione della tabella DP e registrare solo quella. dati necessari In generale, è Comprimere una tabella DP bidimensionale in una dimensione, ovvero comprimere la complessità dello spazio da O(n^2) a O(n).
3. Il problema della riscossione del resto
0.Domanda
Ti vengono date k monete con valore nominale di c1, c2...ck. La quantità di ciascuna moneta è illimitata. Quindi ti viene data una somma totale di denaro. Ti chiedo quante monete ti servono almeno per recuperare tale importo. Se è impossibile recuperare l'importo, l'algoritmo restituisce -1
1. Ricorsione violenta
1. Innanzitutto, questo problema è un problema di programmazione dinamica perché ha una "sottostruttura ottimale". Per rispettare la "sottostruttura ottimale", i sottoproblemi devono essere indipendenti l'uno dall'altro.
2. Tornando al problema della riscossione del resto, perché si dice che sia in linea con la sottostruttura ottimale? Ad esempio, se vuoi trovare il numero minimo di monete quando importo = 11 (domanda originale), se conosci il numero minimo di monete quando importo = 10 (sottodomanda), devi solo aggiungere uno alla risposta a la sotto-domanda (scegli un'altra moneta da 1 valore nominale) è la risposta alla domanda originale. Poiché il numero di monete è illimitato, non esiste alcun controllo reciproco tra i sottoproblemi ed essi sono indipendenti l’uno dall’altro.
3. Quattro passi principali
1. Determinare il caso base Questo è molto semplice Ovviamente, quando l'importo target è 0, l'algoritmo restituisce 0.
2. Determinare lo "stato", cioè le variabili che cambieranno nel problema originale e nei sottoproblemi. Poiché il numero di monete è infinito e anche la denominazione della moneta è data dalla domanda, solo l'importo target continuerà ad avvicinarsi al caso base, quindi l'unico "stato" è l'importo dell'importo target
3. Determinare la "scelta", ovvero il comportamento che provoca il cambiamento dello "stato". Perché l'importo target cambia? Perché stai scegliendo le monete Ogni volta che scegli una moneta, equivale a ridurre l'importo target. Quindi il valore nominale di tutte le monete è la tua "scelta"
4. Chiarire la definizione di funzione/array dp. Ciò di cui stiamo parlando qui è una soluzione top-down, quindi ci sarà una funzione dp ricorsiva. In generale, il parametro della funzione è l'importo che cambierà durante la transizione di stato, che è lo "stato" menzionato sopra; il valore restituito dalla funzione è la quantità che la domanda ci richiede di calcolare. Per quanto riguarda questa domanda, esiste un solo stato, che è "importo target". La domanda richiede di calcolare il numero minimo di monete necessarie per raggiungere l'importo target. Quindi possiamo definire la funzione dp in questo modo: La definizione di dp(n): inserisci un importo target n e restituisci il numero minimo di monete per raggiungere l'importo target n
4. Pseudocodice
def cambiomoneta(monete: Lista[int], importo: int): # Definizione: per recuperare l'importo n sono necessarie almeno dp(n) monete def dp(n): # Fai una scelta e scegli il risultato che richiede meno monete. per moneta in monete: res = min(res, 1 dp(n - moneta)) ritorno ris #Il risultato finale richiesto dalla domanda è dp(importo) ritorno dp(importo)
5.Codice
def cambiomoneta(monete: Lista[int], importo: int): def dp(n): # caso base se n == 0: restituisce 0 se n < 0: restituisce -1 # Trova il valore minimo, quindi inizializzalo su infinito positivo res = float('INF') per moneta in monete: sottoproblema = dp(n - moneta) # Il sottoproblema non ha soluzione, saltalo se sottoproblema == -1: continua res = min(res, 1 sottoproblema) restituisce res se res != float('INF') altrimenti -1 ritorno dp(importo)
6.Equazione di transizione di stato
7. Albero ricorsivo
8. Complessità
Il numero totale di sottoproblemi è il numero di nodi dell'albero ricorsivo. Questo è difficile da vedere. È O(n^k). Ogni sottoproblema contiene un ciclo for con complessità O(k). Quindi la complessità temporale totale è O(k * n^k), livello esponenziale
2. Ricorsione con promemoria
1.Codice
def cambiomoneta(monete: Lista[int], importo: int): #promemoria promemoria = dict() def dp(n): # Controlla il promemoria per evitare il doppio conteggio if n nel memo: return memo[n] # caso base se n == 0: restituisce 0 se n < 0: restituisce -1 res = float('INF') per moneta in monete: sottoproblema = dp(n - moneta) se sottoproblema == -1: continua res = min(res, 1 sottoproblema) # Registra nel promemoria memo[n] = res if res != float('INF') else -1 promemoria di reso[n] ritorno dp(importo)
2. Complessità
Ovviamente, il "memo" riduce notevolmente il numero di sottoproblemi ed elimina completamente la ridondanza dei sottoproblemi, quindi il numero totale di sottoproblemi non supererà la quantità di denaro n, cioè il numero di sottoproblemi è O(n). Il tempo necessario per affrontare un sottoproblema rimane invariato ed è ancora O(k), quindi la complessità temporale totale è O(kn)
3.Soluzione iterativa dell'array dp
1.Definizione di array dp
La funzione dp si riflette nei parametri della funzione, mentre l'array dp si riflette nell'indice dell'array: La definizione di array dp: quando l'importo target è i, sono necessarie almeno monete dp[i] per raccoglierlo
2.Codice
int coinChange(vettore<int>& monete, int importo) { //La dimensione dell'array è pari a 1 e anche il valore iniziale è pari a 1 vettore<int> dp(importo 1, importo 1); // caso base dp[0] = 0; // Il ciclo for esterno attraversa tutti i valori di tutti gli stati for (int i = 0; i < dp.size(); i ) { //Il ciclo for interno trova il valore minimo di tutte le scelte for (int moneta: monete) { // Il sottoproblema non ha soluzione, saltalo se (i - moneta < 0) continua; dp[i] = min(dp[i], 1 dp[i - moneta]); } } return (dp[importo] == importo 1) ? -1 : dp[importo]; }
3. Processo
4.Dettagli
Perché l'array dp è inizializzato sull'importo 1? Poiché il numero di monete che compongono l'importo può essere uguale al massimo all'importo (vengono utilizzate tutte le monete con un valore nominale di 1 yuan), quindi inizializzarlo sull'importo 1 è equivale a inizializzarlo all'infinito positivo, il che facilita il successivo valore minimo
4. Riepilogo
1. In realtà non esistono trucchi magici per i computer per risolvere i problemi. La sua unica soluzione è esaurire in modo esaustivo tutte le possibilità. La progettazione di algoritmi non è altro che pensare prima a "come fare in modo esaustivo" e poi perseguire "come fare in modo esaustivo in modo esaustivo"
2. Elencare l'equazione di trasferimento dinamico significa risolvere il problema di "come farlo in modo esaustivo". Il motivo per cui è difficile è che, in primo luogo, molti calcoli esaustivi devono essere implementati in modo ricorsivo e, in secondo luogo, perché lo spazio di soluzione di alcuni problemi è complesso e non è facile completare il calcolo esaustivo.
3. I promemoria e le tabelle DP mirano a perseguire "come esaustivamente esaustivo". L’idea di scambiare spazio con tempo è l’unico modo per ridurre la complessità del tempo. Inoltre, lasciami chiedere, quali altri trucchi puoi fare?