DSA Interview Prep · Lezione

Ricerca binaria su array ruotati e non ordinati

Risolva search-in-rotated-sorted-array e find-minimum-in-rotated-array decidendo quale metà sia ordinata a ogni passaggio

Lezione 2 di 413 passaggi

Ricerca binaria su array ruotati e non ordinati è una lezione DSA 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 DSA Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DSA Interview Prep include 4 lezioni in totale.

Che cos'è un array ordinato ruotato?

Un array ordinato ruotato è un array ordinato che è stato tagliato in corrispondenza di un pivot e le cui due parti sono state scambiate. Ad esempio, [4, 5, 6, 7, 0, 1, 2] è l'array ordinato [0,1,2,4,5,6,7] ruotato all'indice 4. La ricerca binaria standard non funziona in questo caso perché l'array non è più ordinato globalmente.

L'idea fondamentale è che almeno una metà dell'array è sempre ordinata dopo una qualsiasi rotazione. La ricerca binaria deve identificare quale metà è ordinata prima di decidere come spostare i limiti.

# A rotated sorted array — one half is always sorted
arr = [4, 5, 6, 7, 0, 1, 2]
# Left half [4,5,6,7] is sorted
# Right half [0,1,2] is also sorted
# But left[0]=4 > right[-1]=2 => rotation happened in left-to-right crossing

Identificare la metà ordinata

Dopo aver calcolato mid, confronti arr[lo] con arr[mid]. Se arr[lo] <= arr[mid], la metà sinistra è ordinata; altrimenti è ordinata la metà destra. Una volta stabilito quale metà è ordinata, può verificare se il target rientra in quell'intervallo ordinato e restringere di conseguenza la ricerca.

Questo albero decisionale consente di scartare esattamente metà dell'array a ogni passaggio, mantenendo la complessità O(log n) anche con un array ruotato.

def search_rotated(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return mid
        # Left half is sorted
        if nums[lo] <= nums[mid]:
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        # Right half is sorted
        else:
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

print(search_rotated([4, 5, 6, 7, 0, 1, 2], 0))  # 4
print(search_rotated([4, 5, 6, 7, 0, 1, 2], 3))  # -1

Seguire un esempio passo per passo

Seguiamo passo per passo search_rotated([4,5,6,7,0,1,2], 0). Inizialmente lo=0, hi=6, mid=3, arr[mid]=7. Il target 0 si trova nella metà sinistra ordinata [4..7]? No, quindi impostiamo lo=4. Ora lo=4, hi=6, mid=5, arr[mid]=1. La metà sinistra [0,1] è ordinata (arr[lo]=0 <= arr[mid]=1). 0 si trova in [0..1)? Sì, quindi impostiamo hi=4. Ora lo=4, hi=4, mid=4, arr[4]=0: trovato all'indice 4.

# Step-by-step trace
nums = [4, 5, 6, 7, 0, 1, 2]
target = 0
steps = []
lo, hi = 0, len(nums) - 1
while lo <= hi:
    mid = lo + (hi - lo) // 2
    steps.append(f'lo={lo} hi={hi} mid={mid} val={nums[mid]}')
    if nums[mid] == target:
        steps.append(f'Found at {mid}')
        break
    if nums[lo] <= nums[mid]:
        if nums[lo] <= target < nums[mid]:
            hi = mid - 1
        else:
            lo = mid + 1
    else:
        if nums[mid] < target <= nums[hi]:
            lo = mid + 1
        else:
            hi = mid - 1
for s in steps:
    print(s)

Gestire i duplicati nella rotazione

Quando l'array ordinato ruotato può contenere duplicati, ad esempio [1,3,1,1,1], la condizione nums[lo] == nums[mid] è ambigua: non è possibile stabilire quale metà sia ordinata. La soluzione sicura consiste nell'incrementare lo (oppure decrementare hi) di uno e riprovare. Nel caso peggiore, il tempo passa a O(n); è opportuno menzionarlo al selezionatore.

def search_rotated_with_dups(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return True
        # Ambiguous: shrink left boundary
        if nums[lo] == nums[mid] == nums[hi]:
            lo += 1
            hi -= 1
        elif nums[lo] <= nums[mid]:
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return False

print(search_rotated_with_dups([1, 3, 1, 1, 1], 3))  # True
print(search_rotated_with_dups([2, 2, 2, 0, 2], 0))  # True

Trovare il minimo in un array ordinato ruotato

Un problema correlato consiste nel trovare il minimo in un array ordinato ruotato senza cercare uno specifico target. Il minimo si trova sempre nella metà non ordinata. A ogni passaggio: se arr[mid] > arr[hi], il minimo si trova nella metà destra (lo = mid + 1); altrimenti si trova nella metà sinistra, includendo mid (hi = mid). Quando lo == hi, il minimo è stato trovato.

def find_min(nums):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1   # min is in right half
        else:
            hi = mid       # min is at mid or left of mid
    return nums[lo]

print(find_min([3, 4, 5, 1, 2]))   # 1
print(find_min([4, 5, 6, 7, 0, 1, 2]))  # 0
print(find_min([11, 13, 15, 17]))  # 11 (no rotation)

Perché arr[lo] <= arr[mid] rileva la metà sinistra ordinata

La condizione arr[lo] <= arr[mid] funziona perché, in un segmento ordinato (o ordinato senza rotazione), il primo elemento è sempre il più piccolo. Se arr[lo] <= arr[mid], all'interno di [lo..mid] non si è verificata alcuna rotazione, quindi quella metà è ordinata. L'uguaglianza gestisce il caso in cui lo == mid: un segmento di un solo elemento è ovviamente ordinato.

Al contrario, se arr[lo] > arr[mid], il pivot della rotazione deve trovarsi tra lo e mid; ciò significa che la metà destra [mid..hi] è il segmento ordinato contiguo.

# Visualise: detect which half is sorted
examples = [
    ([4, 5, 6, 7, 0, 1, 2], 0, 6),  # mid=3, val=7 => left sorted
    ([6, 7, 0, 1, 2, 4, 5], 0, 6),  # mid=3, val=1 => right sorted
]
for arr, lo, hi in examples:
    mid = lo + (hi - lo) // 2
    if arr[lo] <= arr[mid]:
        print(f'arr[{lo}]={arr[lo]} <= arr[{mid}]={arr[mid]}  => LEFT half sorted')
    else:
        print(f'arr[{lo}]={arr[lo]} >  arr[{mid}]={arr[mid]}  => RIGHT half sorted')

Analisi della complessità

La ricerca in un array ordinato ruotato con la ricerca binaria mantiene un tempo O(log n) e uno spazio O(1), perché a ogni iterazione dimezziamo comunque lo spazio di ricerca. L'unica differenza rispetto alla ricerca binaria classica è un controllo aggiuntivo a tempo costante per identificare quale metà è ordinata.

Con i duplicati, nel caso peggiore la complessità passa a O(n), perché potremmo incrementare lo di una sola posizione a ogni passaggio. Espliciti questo compromesso: dimostra che tiene conto dei casi limite oltre al normale percorso di esecuzione.

Analisi passo per passo di LeetCode 33

LeetCode 33 'Search in Rotated Sorted Array' è la forma canonica di questo problema. I vincoli garantiscono l'assenza di duplicati e un'unica rotazione. La soluzione è la funzione search_rotated che abbiamo scritto in precedenza. Punti chiave per il colloquio: dichiari sempre l'ipotesi di assenza di duplicati, verifichi le disuguaglianze con un esempio concreto sul confine e confermi che l'indice restituito sia corretto sia nei casi in cui l'elemento viene trovato sia in quelli in cui non viene trovato.

# LeetCode 33 — complete solution
def search(nums, target):
    lo, hi = 0, len(nums) - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] == target:
            return mid
        if nums[lo] <= nums[mid]:        # left half sorted
            if nums[lo] <= target < nums[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        else:                            # right half sorted
            if nums[mid] < target <= nums[hi]:
                lo = mid + 1
            else:
                hi = mid - 1
    return -1

# Tests
print(search([4,5,6,7,0,1,2], 0))   # 4
print(search([4,5,6,7,0,1,2], 3))   # -1
print(search([1], 0))               # -1

LeetCode 153: trovare il minimo senza duplicati

LeetCode 153 'Find Minimum in Rotated Sorted Array' chiede di trovare il minimo in assenza di duplicati. L'approccio consiste nel confrontare arr[mid] con arr[hi], non con arr[lo], per determinare da quale lato si trovi il minimo. Se arr[mid] > arr[hi], il minimo si trova a destra; altrimenti si trova in mid o a sinistra. Il procedimento converge al minimo in O(log n).

def findMin(nums):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1
        else:
            hi = mid
    return nums[lo]

print(findMin([3,4,5,1,2]))         # 1
print(findMin([4,5,6,7,0,1,2]))     # 0
print(findMin([11,13,15,17]))       # 11

Numero di rotazioni e indice del pivot

Una volta in grado di trovare l'elemento minimo, conosce anche il numero di rotazioni: l'indice del minimo corrisponde esattamente al numero di posizioni verso destra di cui è stato ruotato l'array. Ad esempio, in [4,5,6,7,0,1,2] il minimo si trova all'indice 4, quindi l'array è stato ruotato di 4 posizioni.

Conoscere il pivot consente di applicare la ricerca binaria standard trattando gli indici modulo n: real_idx = (mid + pivot) % n. Questa formulazione alternativa può semplificare il ragionamento quando si lavora con strutture indicizzate circolarmente.

def search_via_pivot(nums, target):
    n = len(nums)
    # Find pivot (index of minimum)
    lo, hi = 0, n - 1
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if nums[mid] > nums[hi]:
            lo = mid + 1
        else:
            hi = mid
    pivot = lo
    # Binary search with offset
    lo, hi = 0, n - 1
    while lo <= hi:
        mid = lo + (hi - lo) // 2
        real_mid = (mid + pivot) % n
        if nums[real_mid] == target:
            return real_mid
        elif nums[real_mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

print(search_via_pivot([4,5,6,7,0,1,2], 0))  # 4

Mettere insieme tutti i concetti

Quando si trova davanti a un problema sugli array ruotati durante un colloquio, segua questo albero decisionale. Per prima cosa stabilisca se deve trovare un target oppure trovare il minimo. Per trovare un target, utilizzi l'approccio basato sull'identificazione della metà ordinata. Per trovare il minimo, confronti mid con hi. Se sono possibili duplicati, menzioni il caso peggiore O(n) e aggiunga il fallback che restringe i confini.

Si eserciti tracciando il codice sui tre esempi classici: nessuna rotazione, una rotazione e una rotazione che porta il minimo all'ultima posizione.

Verifica rapida

Verifichi la comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep trattati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: un array ordinato ruotato contiene sempre almeno una metà ordinata, occorre confrontare arr[lo] con arr[mid] per identificare la metà ordinata prima di decidere dove cercare e per trovare il minimo si confrontano arr[mid] e arr[hi] così da individuare il pivot della rotazione. Nel prossimo argomento esploreremo le varianti della ricerca binaria lower bound e upper bound.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Ricerca binaria su array ruotati e non ordinati» è gratuita?

Sì — il testo completo di «Ricerca binaria su array ruotati e non ordinati» è 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 DSA Interview Prep, passa a CoddyKit PRO. Il corso DSA Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Ricerca binaria su array ruotati e non ordinati»?

Risolva search-in-rotated-sorted-array e find-minimum-in-rotated-array decidendo quale metà sia ordinata a ogni passaggio Eserciti DSA 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 DSA Interview Prep?

Non è richiesta alcuna esperienza precedente. DSA 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 «Ricerca binaria su array ruotati e non ordinati»?

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 DSA Interview Prep?

Sì. Ogni lezione DSA 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. Ricerca binaria classica: sinistra, destra, centro
  2. Ricerca binaria su array ruotati e non ordinati
  3. Limite inferiore e limite superiore
  4. Ricerca binaria nello spazio delle risposte
← Torna a DSA Interview Prep