文字列内のすべての不均衡な括弧を一定のメモリで線形時間でどのように見つけることができますか?


11

インタビュー中に次の問題が発生しました。

かっこ(かっこや中かっこではなく、かっこのみ)と他の英数字の混合文字列を提供し、対応するかっこがないかっこをすべて識別します。

たとえば、文字列 ")(ab))"では、インデックス0および5に、一致する括弧がない括弧が含まれています。

私はO(n)メモリを使用し、スタックを使用してスタックに括弧を追加し、閉じ括弧とスタックの上部が含まれているときにスタックからそれらを削除してから文字列を調べて、O(n)ソリューションを実行しました開き括弧。

その後、インタビュアーは、問題が一定のメモリで線形時間で解決できることを指摘しました(たとえば、入力が何を使用するかを除いて、追加のメモリ使用はありません)。

私はどのように尋ねたか、彼女は左からすべての開いている括弧を識別し、次に右から2番目にすべての閉じている括弧を識別する文字列を通過することについて何か言いました...またはそれが逆だったかもしれません。私は本当に理解していなかったので、それを手で握ってくれるように彼女に頼んだくありませんでした。

彼女が提案した解決策を誰かが明確にできますか?


1
最初に説明が必要な場合があります。「(()」の最初の括弧または2番目の括弧は不均衡と見なされますか?「())」の最後の括弧または最後から2番目の括弧は不均衡と見なされますか?または、カーディナリティが最小の一連の括弧を識別して、それらを削除すると残りの括弧のバランスが保たれるようにするのに十分ですか?または、他の何か?または、インタビューのこの部分は、答えが正当な仕様を提示できるようにするためのものですか?
John L.

それはあなた次第だと思います。残りのバランスが取れたセットをすべて削除します。
temporary_user_name

5
次に、それらすべてを削除します; P
Veedrac '18年

@Veedrac、もちろん(ご存じのように)ポスターは、「最小セットを削除する… 」で「最小」という単語を忘れていました。
-LSpice

それ自体は「忘れる」ことはしませんでしたが、「すべて」の他に、バランスをとるために削除できるセットが1つしかないため、それは私にとって重要な仕様ではないため、省略しました。もちろん、演習の目的を打ち破っています。
temporary_user_name

回答:


17

これはプログラミングの背景であり、理論的なコンピュータサイエンスの演習ではないため、インデックスを文字列に格納するにはメモリが必要だと思います。理論的なコンピュータサイエンスでは、これはRAMモデルを使用することを意味します。Turingマシンではこれを行うことができず、インデックスを長さ文字列に格納するにはメモリが必要になります。O(1)Θ(log(n))n

使用したアルゴリズムの基本原則を維持できます。メモリ最適化の機会を逃しました。

スタックを使用し、括弧をスタックに追加して文字列を調べ、閉じかっこがあり、スタックの上部に開きかっこが含まれている場合は、スタックから削除します。

このスタックには何が含まれていますか?出現する()たびにを押す代わりに)ポップするので、これは決して含まれません(開始括弧とそれに続く終了括弧)。したがって、スタックは常に次の形式になります。閉じ括弧の束とそれに続く開き括弧の束。())…)(…(

これを表すためにスタックは必要ありません。閉じ括弧の数と開き括弧の数を覚えておいてください。

これらの2つのカウンターを使用して文字列を左から右に処理する場合、最後にあるのは、一致しない閉じ括弧の数と一致しない開き括弧の数です。

一致しない括弧の位置を最後に報告する場合は、各括弧の位置を覚えておく必要があります。最悪の場合、メモリが必要になります。しかし、出力を生成するために最後まで待つ必要はありません。一致しない閉じ括弧が見つかるとすぐに、それが一致しないことがわかるので、すぐに出力します。そして、一致しない右括弧の数を何にも使用しないので、一致しない左括弧のカウンターを保持します。Θ(n)

要約すると、文字列を左から右に処理します。一致しない開き括弧のカウンターを維持します。左括弧が表示された場合は、カウンターを増やします。右括弧が表示され、カウンターがゼロ以外の場合は、カウンターをデクリメントします。右括弧が表示され、カウンターがゼロの場合、現在のインデックスを一致しない右括弧として出力します。

カウンターの最終的な値は、一致しない左括弧の数ですが、これはそれらの位置を提供しません。問題は対称的であることに注意してください。一致しない左括弧の位置をリストするには、アルゴリズムを反対方向に実行します。

演習1:これを正式な表記(数学、疑似コード、またはお気に入りのプログラミング言語)で書き留めます。

演習2:これはApass.Jackと同じアルゴリズムであることを納得させてください。


ああ、とても良いジルです。私は完全に理解しました。私の質問の1つであなたから回答を得てから、かなりの数年になります。
temporary_user_name

「最後に一致しない括弧の位置を報告する場合は、各括弧の位置を覚えておく必要があります。」結構です。線形時間は単一パスを意味しません。2番目のパスを実行して、不一致の側にあるブラケットを見つけてマークすることができます。
Mooing Duck

最後のステップでは、あなたが不一致として「(」あなたは、単に最後のNをマークすることができ、逆にそれを実行する必要はありません。
ダックMooing

1
@MooingDuck動作しません。例(()
orlp

私はこの答えが本当に好きですが、何かが私を悩ませ続けています。それは「私はどういうわけか位置を覚える必要がありますそして私はそれに関して私が持っている問題はメモリを消費せずに「現在のインデックスを出力する」方法です(または出力がそのような方法で消費される非常に特定のコンテキスト)出力の順序は関係ありません)
エドゥアール

8

すべての英数字を無視できるので、今後は文字列に括弧のみが含まれると想定します。質問のように、括弧の種類は "()"のみです。

バランスのとれた括弧が削除できなくなるまでバランスの括弧を削除し続けると、残りの括弧はすべて "))…)((…("のようになります。これらはすべて非括弧の括弧です。この観察から、最初にターニングポイントを見つける必要があることがわかります。 、その前に不均等な閉じ括弧のみがあり、その後に不均等な開き括弧のみがある。

これがアルゴリズムです。簡単に言えば、最初にターニングポイントを計算します。次に、追加の閉じかっこを出力し、文字列を最初から右に向かって分岐点までスキャンします。対称的に、それは余分な左括弧を出力し、最後から左に転換点までスキャンします。


してみましょうstrサイズで文字の配列として文字列で。n

初期化turning_point=0, maximum_count=0, count=0。それぞれのifrom について、以下0n-1実行します。

  1. もしstr[i] = ')'に1を追加し、count。それ以外の場合は、1を引きます。
  2. の場合count > maximum_countturning_point=iおよびを設定しmaximum_count=countます。

turning_pointがターニングポイントの指標です。

リセットmaximum_count=0, count=0。それぞれのifrom について、以下0turning_point実行します。

  1. もしstr[i] = ')'に1を追加し、count。それ以外の場合は、1を引きます。
  2. の場合count > maximum_count、設定しmaximum_count = countます。iアンバランスな閉じ括弧のインデックスとして出力されます。

リセットmaximum_count=0, count=0。各ifrom n-1からturning_point+1downsに対して、次のことを行います。

  1. もしstr[j] = '('に1を追加し、count。それ以外の場合は、1を引きます。
  2. の場合count > maximum_count、設定しmaximum_count = countます。iアンバランスな開き括弧のインデックスとして出力されます。

アルゴリズムが時間および補助メモリと出力メモリで実行されることは明らかです。ここで、は不平衡括弧の数です。O(n)O(1)O(u)u


上記のアルゴリズムを分析すると、実際にはターニングポイントを見つけて使用する必要がないことがわかります。興味深いことに、すべての不均等な開き括弧が無視される前に、すべての不均等な閉じ括弧が発生するという素晴らしい観察結果があります。

これがPythonのコードです

「実行」を押すだけで、いくつかのテスト結果が表示されます。


演習1.上記のアルゴリズムがカーディナリティが最小の括弧のセットを出力し、残りの括弧のバランスをとることを示します。

問題1.文字列に「()[]」などの2種類の括弧が含まれる場合にアルゴリズムを一般化できますか?新しい状況であるインターリーブの場合の "([)]"を認識して処理する方法を決定する必要があります。


笑、エクササイズ1と問題1、かわいい。あなたが説明したアルゴリズムのロジックは、視覚化するのが驚くほど難しいです。明日これを取得するにはコード化する必要があります。
temporary_user_name

私はかなり明白だが最も重要な説明を逃したようです。ロジックは、実際には非常に簡単です。最初に、余分な開き括弧をそれぞれ出力します。ターニングポイントを通過すると、追加の閉じ括弧がそれぞれ出力されます。できた
John L.

バランスのとれていない左括弧の検索は正しくありません。つまり、arrが "())"の場合、pは2であり、p + 1はarr境界の外側にあります。ただのアイデア-不均衡な開き括弧を見つけるには、arrを逆にして、アルゴリズムの一部を使用して不均衡な閉じ括弧を見つけることができます(もちろん、逆に適応されたインデックスを使用します)。
OzrenTkalcecKrznaric

@OzrenTkalcecKrznaric正確にが境界の外側にあるため、 "())"に開き括弧の不均衡はありません。p+1
John L.

これを理解するのに少し時間がかかりましたが、私はそれが好きです、それはかなり賢いです...そして私が考えたすべてのケースで少なくとも機能します
dquijada
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.