Is Subsequence
Ti vengono date due stringhe, s e t. Restituisci true se puoi trasformare t in s eliminando alcune delle sue lettere (eventualmente nessuna), mantenendo l’ordine delle lettere rimanenti; altrimenti restituisci false. Ad esempio, ace è una sottosequenza di abcde, ma aec non lo è.
Funzione
- sstring
- la stringa da cercare
- tstring
- la stringa da cui eliminare le lettere
- Restituisceboolean
- true se s può essere letto all'interno di t in ordine, eventualmente con delle lacune
Vincoli
1 ≤ s.length ≤ 3 × 1041 ≤ t.length ≤ 5 × 104setcontengono solo lettere minuscole inglesi.
Esempi
- Input
- s = "ace"t = "abcde"
- Output
- true
- Spiegazione
- Elimina
beddaabcdee rimaneace, nello stesso ordine.
- Input
- s = "aec"t = "abcde"
- Output
- false
- Spiegazione
tha tutte e tre le lettere, ma l’unicacsi trova prima dell’unicae. Dopo aver usato laeall’indice 4, non rimane alcunacalla sua destra.
- Input
- s = "moon"t = "monsoon"
- Output
- true
- Spiegazione
- Usa la
mall'indice 0, leoagli indici 1 e 4 e lanall'indice 6 dimonsoon. Le lettere in mezzo vengono eliminate.
+20 test nascosti all’invio
Per approfondire
Supponiamo che t resti invariata e che tu debba confrontare con essa un milione di stringhe diverse s. Come prepareresti t per rendere ogni controllo più veloce che rileggere ogni volta tutta t?
Suggerimenti
Aprili uno alla volta. Ognuno rivela un po’ di più.
Guarda la prima lettera di
s. Quale copia di essa intdovresti usare?Usa la copia più vicina all’inizio. Sceglierne una successiva può solo lasciare meno
tper il resto dis, quindi la scelta più vicina all’inizio non è mai peggiore.Mantieni un indice in
se uno int. Avanza intuna lettera alla volta, incrementa l’indice insa ogni corrispondenza e alla fine controlla se è arrivato alla fine dis.
Soluzione
Una sottosequenza può saltare lettere di t in qualsiasi punto, quindi potrebbe sembrare necessario provare molti modi per inserire s dentro t. Non è così. Abbinare ogni lettera di s nel primo punto possibile non è mai peggio di qualsiasi altra scelta e trasforma la ricerca in un'unica scansione da sinistra a destra con due puntatori.
Programmazione dinamica sui prefissi
Corretto, ma non termina sui test più grandi
Intuizione
Poni una domanda più semplice: le prime i lettere di s si possono trovare tra le prime j lettere di t? Chiama la risposta dp[i][j]. Se si possono trovare tra le lettere di t[:j-1], si possono trovare anche tra quelle di t[:j], perché puoi eliminare t[j-1]. Se s[i-1] è uguale a t[j-1], puoi usare anche quella lettera e, in tal caso, le prime i-1 lettere di s devono potersi trovare tra le lettere di t[:j-1]. Quindi dp[i][j] = dp[i][j-1] or (s[i-1] == t[j-1] and dp[i-1][j-1]), e il prefisso vuoto di s si può trovare ovunque.
La riga i legge solo la riga i-1, quindi bastano due righe di lunghezza m+1. La risposta è l’ultima cella dell’ultima riga.
È la stessa tabella che costruisci per la sottosequenza comune più lunga, ed è corretta, ma riempie tutte le celle. Con s di 25,000 lettere e t di 50,000, si tratta di 1.25 × 10^9 celle, molte più di quelle necessarie per un’unica scansione delle due stringhe.
Algoritmo
- Crea una riga
prevdim+1valori, tuttitrue: unasvuota si adatta a ogni prefisso dit. - Per ogni
ida 1 an, crea una rigacurconcur[0] = false. - Per ogni
jda 1 am, impostacur[j]sucur[j-1], oppure suprev[j-1]quandos[i-1]è uguale at[j-1]. - Sostituisci
prevconcur. - Restituisci
prev[m].
def isSubsequence(s, t):
n, m = len(s), len(t)
# prev[j]: the first i-1 letters of s fit inside t[:j]. An empty s fits anywhere.
prev = [True] * (m + 1)
for i in range(1, n + 1):
cur = [False] * (m + 1)
for j in range(1, m + 1):
cur[j] = cur[j - 1] or (s[i - 1] == t[j - 1] and prev[j - 1])
prev = cur
return prev[m]Due puntatori con abbinamento greedy
Intuizione
Leggi t da sinistra a destra e mantieni un puntatore i alla prossima lettera di s che devi ancora trovare. Quando t[j] è uguale a s[i], usala e fai avanzare i. In ogni caso, fai avanzare j. Se i raggiunge la fine di s, ogni lettera ha trovato il proprio posto nell'ordine corretto.
Perché è sicuro scegliere la prima corrispondenza? Supponiamo che una disposizione valida usi una copia successiva di s[i]. Sostituirla con la copia più vicina all'inizio mantiene l'ordine e lascia più caratteri di t a destra per il resto di s, quindi la scelta greedy non fa mai perdere una disposizione esistente. Per moon in monsoon, il puntatore prende la o all'indice 1, salta n e s, prende la o all'indice 4 e si ferma sulla n all'indice 6.
j visita ogni lettera di t una sola volta e i avanza soltanto, quindi il ciclo viene eseguito al massimo m volte. Gli bastano due indici come memoria.
Algoritmo
- Imposta
i = 0persej = 0pert. - Finché entrambi gli indici sono all'interno delle rispettive stringhe, confronta
s[i]cont[j]. - Se sono uguali, incrementa
i. - Incrementa
jin ogni caso. - Restituisci se
iè uguale alla lunghezza dis.
def isSubsequence(s, t):
i = j = 0
while i < len(s) and j < len(t):
if s[i] == t[j]:
i += 1
j += 1
return i == len(s)
Trappole e casi limite
Il ciclo a due puntatori è breve e i suoi bug si annidano nei casi limite.
- Cercare ogni lettera di
sovunque intinvece che dopo la corrispondenza precedente. Così si accettaaecinabcde, anche se l’ordine non è rispettato. - Usare due volte la stessa lettera.
noonnon è una sottosequenza dimoon:mooncontiene una solan, all’indice 3, e non può essere sia la prima sia l’ultima lettera dinoon. - Restituire se
jha raggiunto la fine dit. Spesso il ciclo termina lì, indipendentemente dal fatto chessia stata trovata; soloite lo dice. - Dimenticare che
spuò essere più lunga dit. Confrontandoabcconabsi deve restituirefalse, cosa che il ciclo fa purché si fermi quandottermina. - Leggere
s[i]dopo cheiha raggiunto la fine dis. In Python o Java questa lettura genera un’eccezione, quindi controllaiprima del confronto.
Domande frequenti4
Qual è la complessità temporale di Is Subsequence?
La soluzione con due puntatori ha una complessità temporale di O(n + m), dove n e m sono le lunghezze di s e t, e usa O(1) di memoria aggiuntiva. In pratica, il ciclo si interrompe dopo al massimo m passaggi. La tabella dei prefissi richiede O(n × m) di tempo.
Perché l’approccio greedy con due puntatori funziona per Is Subsequence?
Abbinare una lettera di s nel suo primo posto possibile in t lascia la parte rimanente di t più lunga possibile per le lettere restanti. Qualsiasi posizionamento che usa una copia successiva può essere modificato per usare quella precedente senza interrompere l’ordine, quindi, se esiste un posizionamento, quello greedy lo trova.
Come controlli rapidamente molte stringhe con lo stesso t?
Prepara t una volta: per ogni lettera, memorizza l’elenco ordinato degli indici in cui compare. Per posizionare s[i], esegui una ricerca binaria nell’elenco di quella lettera per trovare il primo indice successivo alla corrispondenza precedente. Ogni verifica costa quindi O(n log m) invece di O(m).
Qual è la differenza tra una sottosequenza e una sottostringa?
Una sottostringa è un blocco di lettere consecutive, mentre una sottosequenza può saltare delle lettere, purché l’ordine rimanga lo stesso. ace è una sottosequenza di abcde, ma non una sua sottostringa. Ogni sottostringa è una sottosequenza, ma non vale il contrario.
Problemi simili
Problemi che usano le stesse idee. Risolverne due o tre è ciò che fissa uno schema.
Python
def isSubsequence(s, t):
# Scrivi il codice quiCaso 1
Caso 2
Caso 3
Input
s = "ace" t = "abcde"
Atteso
true