Maximum Product Subarray
Moltiplica tra loro gli elementi di un sottoarray di nums e restituisci il prodotto più grande ottenibile da qualsiasi sottoarray. Un sottoarray contiene elementi consecutivi, senza intervalli, e non è mai vuoto, quindi anche un array di valori negativi ha una risposta. Sono consentiti zeri e valori negativi.
Funzione
- numsinteger-array
- gli interi da moltiplicare, ciascuno compreso tra -10 e 10
- Restituisceinteger
- il prodotto più grande di un sottovettore non vuoto
Vincoli
1 ≤ nums.length ≤ 2 × 104-10 ≤ nums[i] ≤ 10- Il prodotto di ogni sottovettore rientra in un intero con segno a 32 bit.
Esempi
- Input
- nums = [2, -5, 3, -2, 0, 4]
- Output
- 60
- Spiegazione
- I due numeri negativi si annullano:
2 × -5 × 3 × -2 = 60. Lo 0 divide l'array in due, e il 4 dall'altra parte non può superare 60.
- Input
- nums = [-2, 3, -4, -1]
- Output
- 24
- Spiegazione
- L'intero array si moltiplica dando -24 perché contiene tre numeri negativi. Escludendo l'ultimo elemento ne restano due:
-2 × 3 × -4 = 24, che è maggiore del 12 che ottieni escludendo il primo elemento.
- Input
- nums = [-6, 0, -7]
- Output
- 0
- Spiegazione
- Un sottoarray che evita lo 0 è un singolo numero negativo, mentre un sottoarray che contiene lo 0 ha come prodotto 0. La scelta migliore è il sottoarray
[0].
Per approfondire
Come indicheresti anche dove inizia e finisce il sottoarray migliore, sempre in tempo O(n) e con spazio O(1)?
Suggerimenti
Aprili uno alla volta. Ognuno rivela un po’ di più.
Con le somme, conviene scartare un totale progressivo che scende sotto zero. Con i prodotti, un prodotto molto negativo può diventare il più grande nel momento in cui arriva un altro numero negativo. Cosa dovresti ricordare oltre al prodotto più grande finora?
Osserva i sottoarray che terminano all’indice corrente. Il prodotto più grande tra questi è il numero corrente da solo, oppure il numero moltiplicato per il prodotto più grande che termina un passo prima, oppure il numero moltiplicato per il prodotto più piccolo che termina un passo prima.
Scorri l'array mantenendo
highelow, il prodotto più grande e quello più piccolo di un sottoarray che termina all'indice corrente. Per ogni nuovo numero, calcola prima entrambi i prodotti candidati a partire dai vecchi valori, poi aggiorna la coppia e ricorda il migliorhighche hai visto.
Soluzione
Moltiplicare per un numero negativo inverte l’ordine dei prodotti, quindi il prodotto più piccolo finora può diventare il più grande al passaggio successivo, mentre uno zero azzera tutti i prodotti. Memorizza due valori a ogni indice invece di uno: il prodotto più grande e quello più piccolo di un sottoarray che termina lì; basta un solo passaggio.
Moltiplica ogni sottovettore a partire da ciascun punto iniziale
Corretto, ma non termina sui test più grandi
Intuizione
Fissa un indice iniziale e sposta la fine verso destra, moltiplicando un product progressivo per ogni nuovo elemento. Confrontarlo con il migliore dopo ogni passaggio copre ogni sottovettore una sola volta. Inizia best da nums[0], perché la risposta può essere negativa.
Ci sono circa n²/2 coppie di indici iniziali e finali, una moltiplicazione ciascuna: 2 × 10^8 per 20.000 elementi.
Algoritmo
- Imposta
bestsunums[0]. - Per ogni
start, impostaproductsu 1. - Sposta
enddastartfino all'ultimo indice e moltiplicaproductpernums[end]. - Se
productè maggiore dibest, memorizzalo. - Restituisci
best.
def maxProduct(nums):
n = len(nums)
best = nums[0]
for start in range(n):
product = 1
for end in range(start, n):
# product is now the product of nums[start..end]
product *= nums[end]
if product > best:
best = product
return bestScansiona da entrambe le estremità, ricominciando dagli zeri
Intuizione
Gli zeri dividono l'array in segmenti senza zeri. All'interno di un segmento ogni valore ha modulo almeno 1, quindi un sottarray più lungo non ha mai un prodotto in valore assoluto più piccolo; cambia solo il segno. Con un numero pari di valori negativi, il segmento intero è la scelta migliore. Con un numero dispari, bisogna eliminare un valore negativo e il taglio migliore esclude il primo valore negativo e tutto ciò che lo precede, oppure l'ultimo valore negativo e tutto ciò che lo segue. In entrambi i casi si mantiene un suffisso o un prefisso del segmento.
Una scansione da sinistra con un prodotto progressivo controlla ogni prefisso, reimpostando il prodotto a 1 dopo uno 0. Una scansione da destra controlla ogni suffisso. Su [-2, 3, -4, -1] la scansione da sinistra calcola -2, -6, 24 e -24; quella da destra -1, 4, 12 e -24, quindi la risposta è 24. L'argomento richiede che ogni valore diverso da zero abbia modulo almeno 1, condizione che vale per gli interi.
Algoritmo
- Imposta
bestsunums[0]eproductsu 1. - Procedi da sinistra a destra: moltiplica
productper ogni elemento e mantieni il maggiore trabesteproduct. - Ogni volta che
productdiventa 0, reimpostalo a 1 in modo che la scansione successiva riparta da zero. - Imposta di nuovo
productsu 1 e procedi nello stesso modo da destra a sinistra. - Restituisci
best.
def maxProduct(nums):
best = nums[0]
# Left to right: the product of every prefix of each zero-free run
product = 1
for num in nums:
product *= num
best = max(best, product)
if product == 0:
product = 1 # a zero ends the run, the next one starts fresh
# Right to left: the product of every suffix of each zero-free run
product = 1
for num in reversed(nums):
product *= num
best = max(best, product)
if product == 0:
product = 1
return bestTraccia il prodotto più grande e quello più piccolo che terminano qui
Intuizione
Ogni sottoarray che termina all'indice i è nums[i] da solo, oppure nums[i] moltiplicato per un sottoarray che termina a i-1. Quando nums[i] è negativo, il prodotto più grande tra questi si ottiene dal prodotto più piccolo che termina a i-1. Quindi tieni entrambi: high e low, i prodotti più grande e più piccolo di un sottoarray che termina all'indice corrente. Il nuovo high è il più grande tra num, high × num e low × num, mentre il nuovo low è il più piccolo. Considerare num da solo è ciò che riavvia la sequenza dopo uno zero.
Nel primo esempio low è -30 quando arriva -2, e -30 × -2 = 60 diventa high, la risposta.
Algoritmo
- Imposta
high,lowebestsunums[0]. - Per ogni elemento successivo
num, calcolaa = high × numeb = low × num. - Imposta
highsul più grande elowsul più piccolo tranum,aeb. - Imposta
bestsul più grande trabestehigh. - Restituisci
best.
def maxProduct(nums):
# high and low: the largest and smallest product of a subarray ending at the current index
high = low = best = nums[0]
for num in nums[1:]:
# Multiply both old values first: a negative num swaps their roles
a = high * num
b = low * num
high = max(num, a, b)
low = min(num, a, b)
best = max(best, high)
return best
Trappole e casi limite
- Tenere traccia solo del prodotto più grande. Nel primo esempio restituisce 4 invece di 60, perché scarta il -30 che il -2 trasforma in 60.
- Calcolare il nuovo
lowa partire dahighgià aggiornato. Salvahigh × numelow × numprima di modificare uno dei due. - Inizializzare
besta 0 o 1.[-3]deve restituire -3, perché il sottoarray non può essere vuoto. - Eseguire solo il passaggio da sinistra a destra del metodo a due passaggi. Su
[-2, 3, 4]non vede mai un valore superiore a -2, ma il suffisso[3, 4]dà 12. - In JavaScript, TypeScript e Lua, 0 moltiplicato per un numero negativo è
-0, che viene stampato con il segno meno. Aggiungi 0 al risultato prima di restituirlo.
Domande frequenti3
Qual è la complessità temporale del sottarray a prodotto massimo?
Tenere traccia del prodotto più grande e di quello più piccolo che terminano a ciascun indice richiede un tempo O(n) e uno spazio O(1). Il metodo a due passate con prefissi e suffissi ha lo stesso costo. Moltiplicare ogni sottovettore a partire da ciascun punto iniziale richiede un tempo O(n²).
Perché serve il prodotto minimo nel sottarray a prodotto massimo?
Un numero negativo trasforma il prodotto più negativo che termina all’indice precedente in quello più grande. Conserva solo il prodotto più grande e quel candidato non ci sarà più quando ti servirà. In [3, -2, -4], il prodotto più piccolo prima di -4 è -6, e -6 × -4 = 24 è la risposta.
In che modo Maximum Product Subarray è diverso da Maximum Subarray?
Per le somme, un totale progressivo negativo è sempre svantaggioso, quindi basta un solo valore per indice. Per i prodotti, un prodotto progressivo negativo può diventare il migliore dopo un altro numero negativo, mentre zero azzera tutto. Perciò, nella versione per i prodotti, si tengono il prodotto più grande e quello più piccolo a ogni indice.
Problemi simili
Problemi che usano le stesse idee. Risolverne due o tre è ciò che fissa uno schema.
def maxProduct(nums):
# Scrivi il codice quiTest #1
Test #2
Test #3
Argomento
nums = [2, -5, 3, -2, 0, 4]
Output atteso
60