0Pricing
Coding Interview Prep · Lezione

Subarray a somma massima e subarray a prodotto massimo

Applichi l'algoritmo di Kadane a maximum-sum-subarray e lo estenda per tenere traccia sia del massimo sia del minimo nella variante del prodotto

Subarray a somma massima e subarray a prodotto massimo è una lezione Coding Interview Prep gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Coding Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Coding Interview Prep include 4 lezioni in totale.

Problema del sottoarray a somma massima

Il problema Maximum Subarray consiste nel trovare il sottoarray contiguo all'interno di un array unidimensionale di numeri che abbia la somma più grande. Ad esempio, in [-2, 1, -3, 4, -1, 2, 1, -5, 4], il sottoarray [4, -1, 2, 1] produce la somma massima, pari a 6. Un approccio a forza bruta O(n²) verifica tutti i sottoarray, mentre l'algoritmo di Kadane risolve il problema in O(n).

nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
# Brute force: O(n^2)
max_sum = float('-inf')
for i in range(len(nums)):
    curr = 0
    for j in range(i, len(nums)):
        curr += nums[j]
        max_sum = max(max_sum, curr)
print(max_sum)  # 6

Intuizione alla base dell'algoritmo di Kadane

L'algoritmo di Kadane attraversa l'array una sola volta, mantenendo una current_sum progressiva. A ogni elemento occorre decidere: conviene estendere il sottoarray esistente oppure iniziare un nuovo sottoarray da questo elemento? Se current_sum diventa negativo, danneggerebbe qualsiasi sottoarray futuro, quindi si ricomincia. La ricorrenza è current_sum = max(num, current_sum + num).

def max_subarray(nums):
    max_sum = current_sum = nums[0]
    for num in nums[1:]:
        # Extend or start fresh?
        current_sum = max(num, current_sum + num)
        max_sum = max(max_sum, current_sum)
    return max_sum

nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(max_subarray(nums))  # 6

Esecuzione passo per passo dell'algoritmo di Kadane

Vediamo passo per passo l'algoritmo di Kadane su [-2, 1, -3, 4, -1, 2, 1, -5, 4]: si inizia con curr=-2, max=-2. Con 1: curr=max(1,-2+1)=1, max=1. Con -3: curr=max(-3,1-3)=-2, max=1. Con 4: curr=max(4,-2+4)=4, max=4. Con -1: curr=3, max=4. Con 2: curr=5, max=5. Con 1: curr=6, max=6. Con -5: curr=1. Con 4: curr=5, max=6. L'algoritmo identifica correttamente come ottimale il sottoarray che termina all'indice 6.

def max_subarray_trace(nums):
    curr = max_sum = nums[0]
    for i, num in enumerate(nums[1:], 1):
        new_curr = max(num, curr + num)
        max_sum = max(max_sum, new_curr)
        print(f'i={i}, num={num}, curr: {curr}->{new_curr}, max={max_sum}')
        curr = new_curr
    return max_sum

max_subarray_trace([-2, 1, -3, 4, -1, 2, 1, -5, 4])

Restituire il sottoarray effettivo

Se durante il colloquio Le viene chiesto di restituire il sottoarray stesso, e non soltanto la somma, deve tenere traccia degli indici iniziale e finale. Quando ricomincia, perché num > current_sum + num, aggiorni un temp_start. Quando aggiorna max_sum, salvi temp_start come start e l'indice corrente come end. Questo aggiunge un sovraccarico O(1) allo stesso algoritmo O(n).

def max_subarray_indices(nums):
    max_sum = curr = nums[0]
    start = end = temp_start = 0
    for i in range(1, len(nums)):
        if nums[i] > curr + nums[i]:
            curr = nums[i]
            temp_start = i
        else:
            curr += nums[i]
        if curr > max_sum:
            max_sum = curr
            start, end = temp_start, i
    return max_sum, nums[start:end+1]

print(max_subarray_indices([-2, 1, -3, 4, -1, 2, 1, -5, 4]))
# (6, [4, -1, 2, 1])

Problema del sottoarray a prodotto massimo

Il problema Maximum Product Subarray è più complesso della variante basata sulla somma a causa dei numeri negativi. Il prodotto di due numeri negativi è positivo, quindi un prodotto molto negativo può diventare il massimo dopo essere stato moltiplicato per un altro numero negativo. Per [2, 3, -2, 4], la risposta è 6 ([2, 3]). Per [-2, 0, -1], la risposta è 0. A ogni passaggio è necessario tenere traccia sia del prodotto massimo sia di quello minimo.

nums = [2, 3, -2, 4]
# [2,3,-2,4]: products [2, 6, -12, -48]
# subarrays: [2]=2, [2,3]=6, [3]=3, etc.
# max is 6 from subarray [2,3]

nums2 = [-2, 3, -4]
# [-2]*3*[-4] = 24
# negative*negative=positive!
print('Expected:', 24)

Tenere traccia dei prodotti massimo e minimo

L'intuizione fondamentale è la seguente: in ogni posizione, il prodotto massimo corrente è uno tra num, max_so_far * num e min_so_far * num; quest'ultimo è utile quando un numero negativo trasforma il minimo in un massimo. Lo stesso vale per il minimo. Aggiorni entrambi cur_max e cur_min simultaneamente usando i valori precedenti, così da evitare di usare valori già aggiornati nello stesso passaggio.

def max_product(nums):
    max_prod = min_prod = result = nums[0]
    for num in nums[1:]:
        # All three candidates for new max
        candidates = (num, max_prod * num, min_prod * num)
        max_prod, min_prod = max(candidates), min(candidates)
        result = max(result, max_prod)
    return result

print(max_product([2, 3, -2, 4]))    # 6
print(max_product([-2, 3, -4]))      # 24
print(max_product([-2, 0, -1]))      # 0
print(max_product([-2]))             # -2

Perché min_prod è importante

Consideri [-3, -10, 5]. Dopo aver elaborato -3: max=-3, min=-3. Dopo -10: i candidati sono (-10, 30, 30) → max=30, min=-10. Dopo 5: i candidati sono (5, 150, -50) → max=150. Senza tenere traccia di min_prod, non rileverebbe l'inversione che si verifica quando un minimo molto negativo viene moltiplicato per un altro numero negativo. Calcoli sempre sia max sia min usando gli stessi valori precedenti, per evitare un bug dovuto alla lettura di valori obsoleti.

def max_product_traced(nums):
    max_p = min_p = result = nums[0]
    for num in nums[1:]:
        prev_max, prev_min = max_p, min_p
        max_p = max(num, prev_max * num, prev_min * num)
        min_p = min(num, prev_max * num, prev_min * num)
        result = max(result, max_p)
        print(f'num={num}: max_p={max_p}, min_p={min_p}')
    return result

max_product_traced([-3, -10, 5])
# max_p after -10: 30 (flip!)
# max_p after 5: 150

Gli zeri azzerano il prodotto

Uno zero nell'array azzera entrambi i prodotti progressivi, separando di fatto l'array in sottoarray indipendenti. Quando num = 0, sia max_prod * 0 = 0 sia min_prod * 0 = 0, quindi tutti e tre i candidati diventano 0 e il risultato massimo precedente viene preservato. Non è necessario alcun codice per casi speciali: la formula generale gestisce gli zeri naturalmente.

def max_product(nums):
    max_p = min_p = result = nums[0]
    for num in nums[1:]:
        cands = (num, max_p * num, min_p * num)
        max_p, min_p = max(cands), min(cands)
        result = max(result, max_p)
    return result

# Zero splits array into independent subarrays
print(max_product([3, -1, 4, 0, 2, 5, -1]))   # 10 (2*5)
print(max_product([0, 2]))                       # 2
print(max_product([-1, 0, -2]))                  # 0

Alternativa: scansione del prodotto da sinistra a destra

Un approccio alternativo esegue una scansione da sinistra a destra e da destra a sinistra, reimpostando il prodotto progressivo a 1 quando incontra uno zero. Il sottoarray a prodotto massimo non attraversa mai uno zero, quindi, se un numero negativo produce un risultato sfavorevole in una direzione, la scansione inversa rileverà l'inversione. Questo approccio è elegante, ma il metodo di tracciamento di min/max è quello più comunemente richiesto nei colloqui.

def max_product_sweep(nums):
    result = max(nums)
    left = right = 1
    n = len(nums)
    for i in range(n):
        left *= nums[i]
        right *= nums[n - 1 - i]
        result = max(result, left, right)
        if left == 0: left = 1
        if right == 0: right = 1
    return result

print(max_product_sweep([2, 3, -2, 4]))   # 6
print(max_product_sweep([-2, 3, -4]))     # 24
print(max_product_sweep([-2, 0, -1]))     # 0

Kadane e prodotto: differenze principali

I sottoarray basati sulla somma e quelli basati sul prodotto differiscono in modi importanti. Per la somma, i numeri negativi sono sempre dannosi, quindi si ricomincia in modo greedy. Per il prodotto, due numeri negativi sono utili, quindi è necessario tenere traccia di entrambi gli estremi. Inoltre, gli zeri sono terminali per i prodotti, mentre sono solo moderatamente dannosi per le somme. Durante un colloquio, riconosca esplicitamente queste differenze e spieghi perché è necessario tenere traccia di min prima di scrivere il codice.

# Max Sum Subarray: O(n) time, O(1) space
def max_sum(nums):
    curr = result = nums[0]
    for n in nums[1:]:
        curr = max(n, curr + n)  # restart or extend
        result = max(result, curr)
    return result

# Max Product Subarray: O(n) time, O(1) space
def max_prod(nums):
    lo = hi = result = nums[0]
    for n in nums[1:]:
        lo, hi = min(n, lo*n, hi*n), max(n, lo*n, hi*n)
        result = max(result, hi)
    return result

print(max_sum([-2, 1, -3, 4, -1, 2, 1]))   # 6
print(max_prod([-2, 3, -4]))               # 24

Complessità e consigli per il colloquio

Sia l'algoritmo di Kadane, per la somma massima, sia il tracciamento di min/max, per il prodotto massimo, richiedono tempo O(n) e spazio O(1). Consigli importanti per il colloquio: (1) per la somma massima, menzioni l'alternativa divide et impera O(n log n) per dimostrare una conoscenza più ampia; (2) per il prodotto massimo, sottolinei che aggiorna min_prod e max_prod simultaneamente usando i valori precedenti, così da evitare dati obsoleti; (3) chiarisca sempre: l'array può essere vuoto? Il sottoarray deve essere non vuoto? Per convenzione, sì, deve essere non vuoto.

# Both run O(n) time, O(1) space
# Kadane handles: all negative (returns least negative)
# Product handles: zeros (resets naturally), negatives (tracks both extremes)

nums_all_neg = [-5, -2, -8]
print('Max sum (all neg):', max(max(nums_all_neg[0:1]),
      max(x for x in nums_all_neg)))  # -2
# Correct: return the maximum element when all are negative

Verifica rapida

Metta alla prova la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato: l'algoritmo di Kadane risolve il problema del sottoarray a somma massima in O(n), scegliendo a ogni elemento se estendere il sottoarray o ricominciare, il problema del sottoarray a prodotto massimo richiede di tenere traccia sia dei prodotti progressivi minimo e massimo a causa delle inversioni provocate dai numeri negativi e gli zeri reimpostano naturalmente il prodotto progressivo senza codice per casi speciali. Nella prossima lezione esamineremo il problema Word Break usando una tabella DP 1D.

Domande Frequenti

La lezione «Subarray a somma massima e subarray a prodotto massimo» è gratuita?

Sì — il testo completo di «Subarray a somma massima e subarray a prodotto massimo» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Coding Interview Prep, passa a CoddyKit PRO. Il corso Coding Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Subarray a somma massima e subarray a prodotto massimo»?

Applichi l'algoritmo di Kadane a maximum-sum-subarray e lo estenda per tenere traccia sia del massimo sia del minimo nella variante del prodotto Eserciti Coding Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Coding Interview Prep?

Non è richiesta alcuna esperienza precedente. Coding Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Subarray a somma massima e subarray a prodotto massimo»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Coding Interview Prep?

Sì. Ogni lezione Coding Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. House Robber: ricorrenza prendi o salta
  2. Subarray a somma massima e subarray a prodotto massimo
  3. Word Break e segmentazione delle stringhe
  4. Decode Ways e conteggio dei percorsi
← Torna a Coding Interview Prep