Wie können Sie alle unausgeglichenen Parens in einer Zeichenfolge in linearer Zeit mit konstantem Speicher finden?

11

Während eines Interviews bekam ich folgendes Problem:

Gibt eine Zeichenfolge an, die eine Mischung aus Parens (keine Klammern oder Klammern - nur Parens) mit anderen alphanumerischen Zeichen enthält. Identifizieren Sie alle Parens, die keine passenden Paren haben.

In der Zeichenfolge ") (ab))" enthalten die Indizes 0 und 5 beispielsweise Parens, die keine passenden Paren haben.

Ich schlug eine funktionierende O (n) -Lösung vor, die O (n) -Speicher verwendet, einen Stapel verwendet und die Zeichenfolge durchläuft, sobald ich dem Stapel Parens hinzufüge und sie vom Stapel entferne, wenn ich auf einen schließenden Paren stoße und die Oberseite des Stapels enthält ein Eröffnungsparen.

Anschließend stellte der Interviewer fest, dass das Problem in linearer Zeit mit konstantem Speicher gelöst werden kann (wie in, keine zusätzliche Speichernutzung außer dem, was von der Eingabe belegt wird).

Ich fragte, wie und sie sagte etwas darüber, einmal von links durch die Schnur zu gehen und alle offenen Parens zu identifizieren, und dann ein zweites Mal von rechts, alle engen Parens zu identifizieren ... oder vielleicht war es umgekehrt. Ich verstand es nicht wirklich und wollte sie nicht bitten, mich durch die Hand zu halten.

Kann jemand die von ihr vorgeschlagene Lösung klären?

temporärer_Benutzername
quelle
1
Möglicherweise benötigen wir zuerst eine Klarstellung von Ihnen. Werden die ersten oder zweiten Parens in "(()" als unausgeglichen betrachtet? Werden die letzten Parens oder die vorletzten Parens in "())" als unausgeglichen betrachtet? Oder reicht es aus, einen Satz von Parens mit der geringsten Kardinalität so zu identifizieren, dass durch Entfernen der Parens die verbleibenden Parens ausgeglichen bleiben? Oder etwas anderes? Oder ist dieser Teil des Interviews so, dass eine Antwort nur eine berechtigte Spezifikation enthalten kann?
John L.
Ich würde sagen, es ist egal, bis zu dir. Entfernen Sie alle Sätze, die den Rest im Gleichgewicht halten.
temporärer_Benutzername
5
Dann entfernen Sie sie alle; P
Veedrac
@Veedrac, natürlich hat das Poster (wie Sie wissen) das Wort "minimal" in "Entfernen Sie alle minimalen Sätze ..." vergessen .
LSpice
Ich habe es per se nicht "vergessen", sondern es weggelassen, weil es mir nicht als wichtige Spezifikation erschien, da es nur einen Satz gibt, der entfernt werden kann, um es ausgeglichen zu machen, außer "allen", die besiegt natürlich den Zweck der Übung.
temporärer_Benutzername

Antworten:

17

Ö(1)Θ(Log(n))n

Sie können das Grundprinzip des von Ihnen verwendeten Algorithmus beibehalten. Sie haben eine Gelegenheit zur Speicheroptimierung verpasst.

Verwenden eines Stapels und Durchlaufen der Zeichenfolge, sobald Parens zum Stapel hinzugefügt und vom Stapel entfernt wurden, wenn ich auf einen schließenden Paren stieß und die Oberseite des Stapels einen öffnenden Paren enthielt

Was enthält dieser Stapel? Es wird niemals enthalten ()(eine öffnende Klammer, gefolgt von einer schließenden Klammer), da Sie immer dann, wenn )Sie erscheinen, die Pop- Klammer öffnen, (anstatt die zu drücken ). Der Stapel hat also immer die Form )…)(…(- eine Reihe von schließenden Klammern, gefolgt von einer Reihe von öffnenden Klammern.

Sie benötigen keinen Stapel, um dies darzustellen. Denken Sie nur an die Anzahl der schließenden Klammern und die Anzahl der öffnenden Klammern.

Wenn Sie die Zeichenfolge mit diesen beiden Zählern von links nach rechts verarbeiten, haben Sie am Ende die Anzahl der nicht übereinstimmenden schließenden Klammern und die Anzahl der nicht übereinstimmenden öffnenden Klammern.

Θ(n)

Zusammenfassend: Verarbeiten Sie die Zeichenfolge von links nach rechts. Pflegen Sie einen Zähler mit nicht übereinstimmenden öffnenden Klammern. Wenn Sie eine öffnende Klammer sehen, erhöhen Sie den Zähler. Wenn Sie eine schließende Klammer sehen und der Zähler ungleich Null ist, dekrementieren Sie den Zähler. Wenn Sie eine schließende Klammer sehen und der Zähler Null ist, geben Sie den aktuellen Index als nicht übereinstimmende schließende Klammer aus.

Der Endwert des Zählers ist die Anzahl der nicht übereinstimmenden öffnenden Klammern, aber dies gibt Ihnen nicht ihre Position. Beachten Sie, dass das Problem symmetrisch ist. Führen Sie den Algorithmus einfach in die entgegengesetzte Richtung aus, um die Positionen nicht übereinstimmender öffnender Klammern aufzulisten.

Übung 1: Schreiben Sie dies in einer formalen Notation auf (Mathematik, Pseudocode oder Ihre bevorzugte Programmiersprache).

Übung 2: Überzeugen Sie sich selbst, dass dies der gleiche Algorithmus wie Apass.Jack ist , der nur anders erklärt wurde.

Gilles 'SO - hör auf böse zu sein'
quelle
Oh, sehr gut, Gilles, sehr gut erklärt. Ich verstehe jetzt perfekt. Es ist einige Jahre her, seit ich auf eine meiner Fragen eine Antwort von Ihnen bekommen habe.
temporärer_Benutzername
"Wenn Sie die Positionen der nicht übereinstimmenden Klammern am Ende melden möchten, müssen Sie sich die Position jeder Klammer merken." Nicht ganz. Lineare Zeit bedeutet nicht einmaliges Durchlaufen. Sie können einen zweiten Durchgang durchführen, um Klammern auf der nicht übereinstimmenden Seite zu finden und zu markieren.
Mooing Duck
Für den letzten Schritt müssen Sie es nicht rückwärts ausführen, Sie können einfach das letzte N "(" als
Nichtübereinstimmung
1
@MooingDuck Das funktioniert nicht. ZB (().
Orlp
Während ich diese Antwort wirklich mag, stört mich immer wieder etwas daran. Das ist "Ich muss mich irgendwie an die Position erinnern. Und ich denke, das Problem, das ich damit habe, ist: Wie können Sie den aktuellen Index" ausgeben ", ohne Speicher zu verbrauchen (oder einen ganz bestimmten Kontext, in dem Ihre Ausgaben so verbraucht werden, dass Die Reihenfolge w-Ihrer Ausgaben spielt keine Rolle.
Édouard
8

Da wir einfach alle alphanumerischen Zeichen ignorieren können, gehen wir davon aus, dass die Zeichenfolge von nun an nur noch Klammern enthält. Wie in der Frage gibt es nur eine Art von Klammern: "()".

Wenn wir ausgeglichene Klammern so lange entfernen, bis keine ausgeglichenen Klammern mehr entfernt werden können, müssen alle verbleibenden Klammern wie folgt aussehen: ")) ...) ((... ("), die alle unausgeglichene Klammern sind. Diese Beobachtung legt nahe, dass wir zuerst diesen Wendepunkt finden sollten , vor denen wir nur unausgeglichene schließende Klammern haben und nach denen wir nur unausgeglichene öffnende Klammern haben.

Hier ist der Algorithmus. Kurz gesagt, es berechnet zuerst den Wendepunkt. Anschließend wird eine zusätzliche schließende Klammer ausgegeben, in der die Zeichenfolge von Anfang nach rechts bis zum Wendepunkt gescannt wird. Symmetrisch wird eine zusätzliche öffnende Klammer ausgegeben, die vom Ende nach links bis zum Wendepunkt scannt.


strn

Initialisieren turning_point=0, maximum_count=0, count=0. Für jeden ivon 0bis n-1folgendes zu tun.

  1. Wenn str[i] = ')', addiere 1 zu count; Andernfalls subtrahieren Sie 1.
  2. Wenn count > maximum_count, setzen turning_point=iund maximum_count=count.

Jetzt turning_pointist der Index des Wendepunktes.

Zurücksetzen maximum_count=0, count=0. Für jeden ivon 0bis turning_pointfolgendes zu tun.

  1. Wenn str[i] = ')', addiere 1 zu count; Andernfalls subtrahieren Sie 1.
  2. Wenn count > maximum_count, setzen maximum_count = count. Ausgabe ials Index einer unausgeglichenen schließenden Klammer.

Zurücksetzen maximum_count=0, count=0. Führen Sie für jedes ivon n-1bis turning_point+1nach unten die folgenden Schritte aus .

  1. Wenn str[j] = '(', addiere 1 zu count; Andernfalls subtrahieren Sie 1.
  2. Wenn count > maximum_count, setzen maximum_count = count. Ausgabe ials Index einer unausgeglichenen öffnenden Klammer.

Ö(n)Ö(1)Ö(u)u


Wenn wir den obigen Algorithmus analysieren, werden wir sehen, dass wir den Wendepunkt überhaupt nicht finden und verwenden müssen. Die nette Beobachtung, dass alle unausgeglichenen schließenden Klammern vor allen unausgeglichenen öffnenden Klammern auftreten, kann ignoriert werden, obwohl dies interessant ist.

Hier ist Code in Python .

Klicken Sie einfach auf "Ausführen", um mehrere Testergebnisse anzuzeigen.


Übung 1. Zeigen Sie, dass der obige Algorithmus einen Satz von Klammern mit der geringsten Kardinalität ausgibt, sodass die verbleibenden Klammern ausgeglichen sind.

Problem 1. Können wir den Algorithmus auf den Fall verallgemeinern, dass die Zeichenfolge zwei Arten von Klammern enthält, z. B. "() []"? Wir müssen bestimmen, wie die neue Situation, der Verschachtelungsfall "([)]", erkannt und behandelt werden soll.

John L.
quelle
Lol, Übung 1 und Problem 1, süß. Die Logik des von Ihnen beschriebenen Algorithmus ist überraschend schwer zu visualisieren. Ich müsste das morgen auskodieren, um es zu bekommen.
temporärer_Benutzername
Es sieht so aus, als hätte ich die ziemlich offensichtliche, aber wichtigste Erklärung verpasst. Die Logik ist in der Tat sehr einfach. Zuerst geben wir jede zusätzliche öffnende Klammer aus. Sobald wir den Wendepunkt überschritten haben, geben wir jede zusätzliche schließende Klammer aus. Erledigt.
John L.
Das Finden von unausgeglichenen öffnenden Klammern ist falsch. Dh wenn Ihr arr "())" ist, ist p 2 und p + 1 fällt außerhalb der arr-Grenze. Nur eine Idee - um unausgeglichene öffnende Klammern zu finden, können Sie arr umkehren und einen Teil des Algorithmus verwenden, um unausgeglichene schließende Klammern zu finden (natürlich mit umgekehrt angepassten Indizes).
OzrenTkalcecKrznaric
@OzrenTkalcecKrznaric Genau weil p+1fällt außerhalb der Grenze, gibt es keine unausgeglichenen öffnenden Klammern in "())".
John L.
Ich habe ein bisschen
gebraucht