Come puoi trovare tutte le parentesi sbilanciate in una stringa in tempo lineare con memoria costante?


11

Mi è stato dato il seguente problema durante un'intervista:

Fornisce una stringa che contiene una combinazione di parentesi (non parentesi o parentesi graffe - solo parentesi) con altri caratteri alfanumerici, identifica tutte le parentesi che non hanno parentesi corrispondenti.

Ad esempio, nella stringa ") (ab))", gli indici 0 e 5 contengono parentesi che non hanno parentesi corrispondenti.

Ho presentato una soluzione O (n) funzionante usando la memoria O (n), usando uno stack e passando attraverso la stringa una volta aggiunta le parentesi allo stack e rimuovendole dallo stack ogni volta che ho incontrato una parentesi chiusa e la parte superiore dello stack contenuta un'apertura di apertura.

Successivamente, l'intervistatore ha notato che il problema potrebbe essere risolto in tempo lineare con memoria costante (come in, nessun utilizzo di memoria aggiuntivo oltre a quello occupato dall'input).

Ho chiesto come e lei ha detto qualcosa sull'andare attraverso la stringa una volta da sinistra identificando tutte le parentesi aperte, e poi una seconda volta da destra identificando tutte le parentesi chiuse .... o forse era il contrario. Non capivo davvero e non volevo chiederle di trattenermi a mano.

Qualcuno può chiarire la soluzione che ha suggerito?


1
Potremmo aver bisogno di alcuni chiarimenti da parte tua prima. Le prime parentesi o le seconde parentesi in "(()" sono considerate sbilanciate? Le ultime parentesi o la penultima parentesi in "())" sono considerate sbilanciate? O è sufficiente identificare una serie di parentesi con una cardinalità minima tale da rimuoverle lasciando in equilibrio le restanti parentesi? O qualcos'altro? O è questa parte dell'intervista in modo che una risposta possa semplicemente presentare delle specifiche giustificabili?
John L.

Direi che non importa, per te. Rimuovere qualsiasi set che lasci il resto in equilibrio.
temporaneo

5
Quindi rimuovili tutti; P
Veedrac il

@Veedrac, ovviamente (come sai) il poster ha dimenticato la parola 'minimal' in "Rimuovi qualsiasi set minimo ..."
LSpice,

Non l'ho "dimenticato" di per sé, ma piuttosto l'ho lasciato fuori perché non mi è sembrata una specifica importante poiché c'è solo un set che può essere rimosso per renderlo bilanciato, oltre a "tutti" che sta ovviamente sconfiggendo lo scopo dell'esercizio.
temporaneo

Risposte:


17

Poiché questo proviene da un background di programmazione e non da un esercizio teorico di informatica, presumo che ci vuole memoria per memorizzare un indice nella stringa. In informatica teorica, ciò significherebbe usare il modello RAM; con le macchine Turing non potresti farlo e avresti bisogno della memoria per memorizzare un indice in una stringa di lunghezza .O(1)Θ(log(n))n

Puoi mantenere il principio di base dell'algoritmo che hai usato. Hai perso un'opportunità per l'ottimizzazione della memoria.

usando una pila e passando attraverso la stringa una volta aggiungendo le parentesi alla pila e rimuovendole dalla pila ogni volta che ho incontrato una parentesi chiusa e la parte superiore della pila conteneva una parentesi aperta

Cosa contiene questo stack? Non conterrà mai ()(una parentesi di apertura seguita da una parentesi di chiusura), poiché ogni volta che )appare fai apparire il pop (invece di spingere ). Quindi la pila ha sempre la forma )…)(…(: un gruppo di parentesi chiuse seguite da un gruppo di parentesi aperte.

Non hai bisogno di uno stack per rappresentarlo. Basta ricordare il numero di parentesi chiuse e il numero di parentesi aperte.

Se si elabora la stringa da sinistra a destra, utilizzando questi due contatori, ciò che si ottiene alla fine è il numero di parentesi di chiusura non corrispondenti e il numero di parentesi di apertura non corrispondenti.

Se si desidera riportare le posizioni delle parentesi non corrispondenti alla fine, è necessario ricordare la posizione di ciascuna parentesi. Ciò richiederebbe memoria nel caso peggiore. Ma non è necessario attendere fino alla fine per produrre output. Non appena trovi una parentesi di chiusura non corrispondente, sai che non è corrispondente, quindi esegui l'output ora. E poi non userai il numero di parentesi di chiusura non corrispondenti per qualsiasi cosa, quindi tieni semplicemente un contatore di parentesi di apertura senza pari.Θ(n)

In breve: elabora la stringa da sinistra a destra. Mantenere un contatore di parentesi di apertura senza pari. Se vedi una parentesi aperta, incrementa il contatore. Se vedi una parentesi chiusa e il contatore è diverso da zero, decrementa il contatore. Se viene visualizzata una parentesi di chiusura e il contatore è zero, generare l'indice corrente come parentesi di chiusura non corrispondente.

Il valore finale del contatore è il numero di parentesi aperte non corrispondenti, ma questo non ti dà la loro posizione. Si noti che il problema è simmetrico. Per elencare le posizioni delle parentesi di apertura non corrispondenti, basta eseguire l'algoritmo nella direzione opposta.

Esercizio 1: annotalo in una notazione formale (matematica, pseudocodice o il tuo linguaggio di programmazione preferito).

Esercizio 2: convinciti che questo è lo stesso algoritmo di Apass.Jack , spiegato in modo diverso.


Oh molto bene Gilles, molto ben spiegato. Adesso capisco perfettamente. Sono passati parecchi anni da quando ho ricevuto una tua risposta su una delle mie domande.
temporaneo

"Se vuoi segnalare le posizioni delle parentesi non corrispondenti alla fine, dovrai ricordare la posizione di ciascuna parentesi." Non proprio. Il tempo lineare non significa passaggio singolo. Puoi fare un secondo passaggio per trovare eventuali parentesi nel lato non corrispondente e contrassegnarle.
Mooing Duck,

Per l'ultimo passaggio, non devi eseguirlo al contrario, puoi semplicemente contrassegnare l'ultima N "(" come disallineamenti.
Mooing Duck

1
@MooingDuck Non funziona. Es (().
orlp

Mentre mi piace molto questa risposta, qualcosa mi infastidisce. Quel qualcosa è "Devo in qualche modo ricordare la posizione E penso che il problema che ho con esso sia: come si fa a" emettere l'indice corrente "senza consumare memoria (o un contesto abbastanza specifico in cui i tuoi output sono consumati in modo tale che l'ordine con le tue uscite non ha importanza.
Édouard

8

Poiché possiamo semplicemente ignorare tutti i caratteri alfanumerici, supponiamo che la stringa contenga solo parentesi da ora in poi. Come nella domanda, esiste solo un tipo di parentesi, "()".

Se continuiamo a rimuovere le parentesi bilanciate fino a quando non è possibile rimuovere più parentesi bilanciate, tutte le parentesi rimanenti devono apparire come ")) ...) ((... (", che sono tutte parentesi sbilanciate. Questa osservazione suggerisce che dovremmo trovare prima quel punto di svolta , prima del quale abbiamo solo parentesi di chiusura sbilanciate e dopo di che abbiamo solo parentesi di apertura sbilanciate.

Ecco l'algoritmo. In breve, calcola prima la svolta. Quindi emette parentesi extra di chiusura, scansionando la stringa dall'inizio a destra fino alla svolta. Simmetricamente, genera parentesi di apertura extra, scansionando dall'estremità a sinistra fino alla svolta.


Lascia che strsia la stringa come una matrice di caratteri, la cui dimensione è .n

Inizializza turning_point=0, maximum_count=0, count=0. Per ciascuna ida 0a n-1fare quanto segue.

  1. Se str[i] = ')', aggiungi 1 a count; altrimenti, sottrai 1.
  2. Se count > maximum_count, impostare turning_point=ie maximum_count=count.

Ora turning_pointè l'indice della svolta.

Ripristina maximum_count=0, count=0. Per ciascuna ida 0a turning_pointfare quanto segue.

  1. Se str[i] = ')', aggiungi 1 a count; altrimenti, sottrai 1.
  2. Se count > maximum_countimpostato maximum_count = count. Output icome indice di una parentesi di chiusura sbilanciata.

Ripristina maximum_count=0, count=0. Per ciascuno ida n-1a turning_point+1verso il basso, procedi come segue.

  1. Se str[j] = '(', aggiungi 1 a count; altrimenti, sottrai 1.
  2. Se count > maximum_countimpostato maximum_count = count. Output icome indice di una parentesi di apertura non bilanciata.

È chiaro che l'algoritmo funziona in tempo e memoria ausiliaria e memoria di output, dove è il numero di parentesi sbilanciate.O(n)O(1)O(u)u


Se analizziamo l'algoritmo sopra, vedremo che, in realtà, non abbiamo bisogno di trovare e usare affatto il punto di svolta. La bella osservazione che tutte le parentesi di chiusura sbilanciate si verificano prima di tutte le parentesi di apertura sbilanciate può essere ignorata sebbene interessante.

Ecco il codice in Python .

Basta premere "Esegui" per vedere diversi risultati del test.


Esercizio 1. Mostra che l'algoritmo sopra riportato produrrà una serie di parentesi con la cardinalità minima in modo tale che le parentesi rimanenti siano bilanciate.

Problema 1. Possiamo generalizzare l'algoritmo al caso quando la stringa contiene due tipi di parentesi come "() []"? Dobbiamo determinare come riconoscere e trattare la nuova situazione, il caso interleaving, "([)]".


Lol, esercizio 1 e problema 1, carino. La logica dell'algoritmo che hai descritto è sorprendentemente difficile da visualizzare. Dovrei codificarlo domani per ottenerlo.
temporaneo

Sembra che mi sia sfuggita la spiegazione piuttosto ovvia ma più importante. La logica è, in effetti, molto semplice. Innanzitutto, produciamo ogni parentesi di apertura aggiuntiva. Una volta superato il punto di svolta, produciamo ogni ulteriore parentesi di chiusura. Fatto.
John L.

Trovare parentesi di apertura non bilanciate non è corretto. Vale a dire se il tuo arr è "())", p è 2 e p + 1 non rientra nel limite arr. Solo un'idea: per trovare parentesi di apertura sbilanciate potresti invertire arr e usare parte dell'algoritmo per trovare parentesi di chiusura sbilanciate (ovviamente, con indici adattati al contrario).
OzrenTkalcecKrznaric,

p+1

Mi ci è voluto un po 'per capirlo, ma mi piace, è abbastanza intelligente .. e funziona almeno per ogni caso che ho pensato
dquijada
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.