web-dev-qa-db-ja.com

括弧、中括弧、角括弧の文字列の有効性を見つける方法は?

最近、この興味深い問題に出会いました。 _'('_、_')'_、_'{'_、_'}'_、_'['_、および_']'_の文字のみを含む文字列が与えられます。たとえば、"[{()}]"、このような入力文字列の有効性をチェックする関数を記述する必要があります。関数は次のようになります。
bool isValid(char* s);
これらの括弧は正しい順序で閉じる必要があります。たとえば、"()"および"()[]{}"はすべて有効ですが、_"(]"_、"([)]"および_"{{{{"_は違います!

私は次のO(n)時間とO(n)スペースの複雑さの解決策でうまくいきました:

  1. 文字のスタックを維持します。
  2. 左中括弧を見つけたときはいつでも_'('_、_'{'_ OR _'['_)スタックにプッシュします。
  3. 閉じ中括弧を見つけたときはいつでも_')'_、_'}'_ OR _']'_、スタックの先頭が対応する開始ブラケットかどうかを確認し、はいの場合はスタックをポップし、そうでない場合、ループを中断してfalseを返します。
  4. 文字列の終わりまで手順2〜3を繰り返します。

これは機能しますが、スペース用に最適化できます。一定の余分なスペースになる可能性があります。時間の複雑さはO(n)未満ではないことを理解しています。

私の質問は、この問題をO(1)スペースで解決できるか?

46
Rajendra Uppal

実際、リッチーとスプリングスティールによる決定論的な対数空間アルゴリズムがあります: http://dx.doi.org/10.1016/S0019-9958(72)90205-7ペイウォールド、ごめん オンラインではありません)。文字列にインデックスを付けるにはログビットが必要なので、これはスペースが最適です。


片側エラーを受け入れたい場合は、n polylog(n)時間とpolylog(n)スペースを使用するアルゴリズムがあります。 http://www.eccc.uni-trier.de/report/2009/119 /

11
user287792

Matthieu M。の優れた回答を参照すると、C#での実装が見事に機能しているようです。

/// <summary>
/// Checks to see if brackets are well formed.
/// Passes "Valid parentheses" challenge on www.codeeval.com,
/// which is a programming challenge site much like www.projecteuler.net.
/// </summary>
/// <param name="input">Input string, consisting of nothing but various types of brackets.</param>
/// <returns>True if brackets are well formed, false if not.</returns>
static bool IsWellFormedBrackets(string input)
{
    string previous = "";
    while (input.Length != previous.Length)
    {
        previous = input;
        input = input
            .Replace("()", String.Empty)
            .Replace("[]", String.Empty)
            .Replace("{}", String.Empty);                
    }
    return (input.Length == 0);
}

基本的に、削除するものがなくなるまでブラケットのペアを削除するだけです。何かが残っている場合、ブラケットは適切に形成されていません。

整形式ブラケットの例:

()[]
{()[]}

不正な形式のブラケットの例:

([)]
{()[}]
12
Contango

入力が読み取り専用の場合、O(1)スペースを使用できるとは思わない。任意のO(1)スペース決定可能言語が正規(つまり、正規表現として記述可能)であることはよく知られた事実です。使用する文字列のセットは、通常の言語ではありません。

もちろん、これはチューリングマシンに関するものです。固定Word RAMマシンにも当てはまると思います。

6
Aryabhatta

編集:シンプルですが、このアルゴリズムは実際には文字比較の観点からO(n ^ 2)です。それを示すために、単に'(' * n + ')' * nとして文字列を生成できます。

私はあなたの批判に服従するという単純な、しかしおそらく間違った考えを持っています。

これは破壊的なアルゴリズムです。つまり、文字列が必要な場合、それは役に立たないでしょう(コピーする必要があるため)。

それ以外の場合、アルゴリズムは現在の文字列内の単純なインデックスで機能します。

考え方は、ペアを次々に削除することです。

  1. _([{}()])_
  2. _([()])_
  3. _([])_
  4. _()_
  5. empty-> OK

一致するペアがある場合、少なくとも1つは_()_という形式であり、ペア文字は間にありません。

アルゴリズム:

  1. _i := 0_
  2. iから一致するペアを見つけます。何も見つからない場合、文字列は無効です。見つかったら、iを最初の文字のインデックスにします。
  3. 文字列から_[i:i+1]_を削除します
  4. iが文字列の最後にあり、文字列が空でない場合、失敗です。
  5. _[i-1:i]_が一致するペアの場合、_i := i-1_から3に戻ります。
  6. それ以外の場合、1に戻ります。

アルゴリズムは、次の理由でO(n)複雑です:

  • ループの各反復は、文字列から2文字を削除します
  • ステップ2.(線形)は自然にバインドされます(iは無限に成長できません)

インデックスのみが必要なため、スペース内のO(1)です。

もちろん、文字列を破棄する余裕がない場合は、それをコピーする必要があります。それはスペース内のO(n)であるため、そこには本当のメリットはありません!

もちろん、私がどこかに深く間違えない限り...そしておそらく誰かが元のアイデア(どこかにペアがあります)を使用してより良い効果を得ることができます。

3
Matthieu M.

これは、Javaコードです。文字列式から角かっこを除外し、整形式ブレースをヌルで置き換えて整形式をチェックします。

サンプルinput = (a+{b+c}-[d-e])+[f]-[g] FilterBracketsの出力= ({}[])[][]次に、整形式かどうかを確認します。

コメントを歓迎します。

public class ParanString {

    public static void main(String[] args) {

        String s = FilterBrackets("(a+{b+c}-[d-e])[][]");

        while ((s.length()!=0) && (s.contains("[]")||s.contains("()")||s.contains("{}")))
        {
        //System.out.println(s.length());
        //System.out.println(s);
        s = s.replace("[]", "");
        s = s.replace("()", "");
        s = s.replace("{}", "");
        }

        if(s.length()==0)
        {
            System.out.println("Well Formed");
        }
        else
        {
            System.out.println("Not Well Formed");
        }
    }

    public static String FilterBrackets(String str)
    {
        int len=str.length();
        char arr[] = str.toCharArray();
        String filter = "";
        for (int i = 0; i < len; i++)
        {
            if ((arr[i]=='(') || (arr[i]==')') || (arr[i]=='[') || (arr[i]==']') || (arr[i]=='{') || (arr[i]=='}'))
            {
                filter=filter+arr[i];
            }
        }
        return filter;
    }

}
2

発生を正規表現またはカウントするために内部関数を使用しても、それらはまだO(...)コストを持っているため、より良い解決策を見つけることはできないと思います。一番です :)

スペースを最適化するには、スタック上でランレングスエンコーディングを実行できますが、{{{{{{{{{{}}}}}}}}}}

2
chris

http://www.sureinterview.com/shwqst/112007

スタックでこの問題を解決するのは自然です。

「(」と「)」のみを使用する場合、スタックは必要ありません。一致しない左 '('のカウンタを維持する必要があります。一致中にカウンタが常に負でなく、最後がゼロの場合、式は有効です。

一般に、スタックはまだ必要ですが、一致しないブレースのカウンターを使用することでスタックの深さを減らすことができます。

2
puttyshell

Sbusidan の次の修正はO(n2)時間は複雑だがO(log n)シンプルなスペース。

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char opposite(char bracket) {
 switch(bracket) {
  case '[':
   return ']';
  case '(':
   return ')';
 }
}

bool is_balanced(int length, char *s) {
int depth, target_depth, index;
char target_bracket;
 if(length % 2 != 0) {
  return false;
 }

 for(target_depth = length/2; target_depth > 0; target_depth--) {
  depth=0;
  for(index = 0; index < length; index++) {
   switch(s[index]) {
    case '(':
    case '[':
     depth++;
     if(depth == target_depth) target_bracket = opposite(s[index]);
     break;
    case ')':
    case ']':
     if(depth == 0) return false;
     if(depth == target_depth && s[index] != target_bracket) return false;
     depth--;
     break;
   }
  }
 }
}

void main(char* argv[]) {
  char input[] = "([)[(])]";
  char *balanced = is_balanced(strlen(input), input) ? "balanced" : "imbalanced";
  printf("%s is %s.\n", input, balanced);
}
2

入力文字列を上書きできる場合(私が想定しているユースケースでは合理的ではありませんが、一体...)、一定のスペースでそれを行うことができますが、時間要件はO(n2

このような:

string s = input
char c = null
int i=0
do
  if s[i] isAOpenChar()
    c = s[i]
  else if
    c = isACloseChar()
      if closeMatchesOpen(s[i],c)
         erase s[i]
         while s[--i] != c ;
         erase s[i]
         c == null
         i = 0;      // Not optimal! It would be better to back up until you find an opening character
      else 
         return fail
  end if
while (s[++i] != EOS)
if c==null
  return pass
else
  return fail

これの本質は、入力の初期部分をスタックとして使用することです。

1
dmckee

私はこのパーティーに少し遅れていることを知っています。また、StackOverflowに関する最初の投稿でもあります。

しかし、答えを調べてみると、より良い解決策を思いつくことができると思いました。

したがって、私の解決策は、いくつかのポインターを使用することです。
レジスタを使用できるため、RAMストレージを使用する必要はありません。
コードをテストしていません。その場で書かれています。
タイプミスを修正してデバッグする必要がありますが、アイデアは得られると思います。

メモリ使用量:ほとんどの場合、CPUのみが登録されます。
CPU使用率:状況によって異なりますが、文字列の読み取りにかかる時間の約2倍です。
メモリを変更します:いいえ。

b:文字列 beginning、e:文字列 end。
l: left位置、r: r正しい位置。
c: char、m: mキャッチチャー

rが文字列の最後に到達すると、成功します。
lはrからbに向かって後方に移動します。
rが新しい開始の種類に合うたびに、l = rを設定します。
lがbに達すると、ブロックは完了です。次のブロックの先頭にジャンプします。

const char *chk(const char *b, int len) /* option 2: remove int len */
{
  char c, m;
  const char *l, *r;

  e = &b[len];  /* option 2: remove. */
  l = b;
  r = b;
  while(r < e) /* option 2: change to while(1) */
  {
    c = *r++;
    /* option 2: if(0 == c) break; */
    if('(' == c || '{' == c || '[' == c)
    {
      l = r;
    }
    else if(')' == c || ']' == c || '}' == c)
    {
      /* find 'previous' starting brace */
      m = 0;
      while(l > b && '(' != m && '[' != m && '{' != m)
      {
        m = *--l;
      }
      /* now check if we have the correct one: */
      if(((m & 1) + 1 + m) != c)  /* cryptic: convert starting kind to ending kind and match with c */
      {
        return(r - 1);  /* point to error */
      }
      if(l <= b) /* did we reach the beginning of this block ? */
      {
        b = r; /* set new beginning to 'head' */
        l = b; /* obsolete: make left is in range. */
      }
    }
  }
  m = 0;
  while(l > b && '(' != m && '[' != m && '{' != m)
  {
    m = *--l;
  }
  return(m ? l : NULL); /* NULL-pointer for OK */
}

しばらくこのアプローチについて考えた後、私はそれが今のように機能しないことに気付きました。
問題は、「[()()]」がある場合、「]」に到達すると失敗することです。
しかし、提案されたソリューションを削除する代わりに、実際に機能させることは不可能ではないため、ここに残しておきますが、多少の修正が必要です。

1
user1985657

c#OOPSプログラミングを使用して...小さくてシンプルなソリューション

Console.WriteLine("Enter the string");
            string str = Console.ReadLine();
            int length = str.Length;
            if (length % 2 == 0)
            {
                while (length > 0 && str.Length > 0)
                {
                    for (int i = 0; i < str.Length; i++)
                    {
                        if (i + 1 < str.Length)
                        {
                            switch (str[i])
                            {
                                case '{':
                                    if (str[i + 1] == '}')
                                        str = str.Remove(i, 2);
                                    break;
                                case '(':
                                    if (str[i + 1] == ')')
                                        str = str.Remove(i, 2);
                                    break;
                                case '[':
                                    if (str[i + 1] == ']')
                                        str = str.Remove(i, 2);
                                    break;
                            }
                        }
                    }
                    length--;
                }
                if(str.Length > 0)
                    Console.WriteLine("Invalid input");
                else
                    Console.WriteLine("Valid input");
            }
            else
                Console.WriteLine("Invalid input");
            Console.ReadKey();
0
Jaydeep Shil

これが私の問題の解決策です。 O(n)は時間の複雑さであり、スペースの複雑さはありません。Cのコード.

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

bool checkBraket(char *s)
{
    int curly = 0, rounded = 0, squre = 0;
    int i = 0;
    char ch = s[0];
    while (ch != '\0')
    {
        if (ch == '{') curly++;
        if (ch == '}') {
            if (curly == 0) {
                return false;
            } else {
                curly--; }
        }
        if (ch == '[') squre++;
        if (ch == ']') {
            if (squre == 0) {
                return false;
            } else {
                squre--;
            }
        }
        if (ch == '(') rounded++;
        if (ch == ')') {
            if (rounded == 0) {
                return false;
            } else {
                rounded--;
            }
        }
        i++;
        ch = s[i];
    }
    if (curly == 0 && rounded == 0 && squre == 0){
        return true;
    }
    else {
        return false;
    }
}
void main()
{
    char mystring[] = "{{{{{[(())}}]}}}";
    int answer = checkBraket(mystring);
    printf("my answer is %d\n", answer);
    return;
}
0
SBusidan
/**
 *
 * @author madhusudan
 */
public class Main {

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    new Main().validateBraces("()()()()(((((())))))()()()()()()()()");
    // TODO code application logic here
}

/**
 * @Use this method to validate braces
 */
public void validateBraces(String teststr)
{
    StringBuffer teststr1=new StringBuffer(teststr);
    int ind=-1;
    for(int i=0;i<teststr1.length();)
    {

    if(teststr1.length()<1)
    break;
    char ch=teststr1.charAt(0);
    if(isClose(ch))
    break;
    else if(isOpen(ch))
    {
        ind=teststr1.indexOf(")", i);
        if(ind==-1)
        break;
        teststr1=teststr1.deleteCharAt(ind).deleteCharAt(i);
    }
    else if(isClose(ch))
    {
        teststr1=deleteOpenBraces(teststr1,0,i);
    }
    }
    if(teststr1.length()>0)
    {
        System.out.println("Invalid");

    }else
    {
        System.out.println("Valid");
    }
}
public boolean  isOpen(char ch)
{
    if("(".equals(Character.toString(ch)))
    {
        return true;
    }else
        return false;
}
public boolean  isClose(char ch)
{
    if(")".equals(Character.toString(ch)))
    {
        return true;
    }else
        return false;
}
public StringBuffer deleteOpenBraces(StringBuffer str,int start,int end)
{
    char ar[]=str.toString().toCharArray();
    for(int i=start;i<end;i++)
    {
        if("(".equals(ar[i]))
         str=str.deleteCharAt(i).deleteCharAt(end); 
        break;
    }
    return str;
}

}
0

値を指定して、有効な値であるかどうかを確認できます。そうでない場合は、NOが出力されます。

static void Main(string[] args)
        {
            string value = "(((([{[(}]}]))))";
            List<string> jj = new List<string>();
            if (!(value.Length % 2 == 0))
            {
                Console.WriteLine("NO");
            }
            else
            {
                bool isValid = true;


                List<string> items = new List<string>();

                for (int i = 0; i < value.Length; i++)
                {
                    string item = value.Substring(i, 1);
                    if (item == "(" || item == "{" || item == "[")
                    {
                        items.Add(item);
                    }
                    else
                    {
                        string openItem = items[items.Count - 1];
                        if (((item == ")" && openItem == "(")) || (item == "}" && openItem == "{") || (item == "]" && openItem == "["))
                        {
                            items.RemoveAt(items.Count - 1);

                        }
                        else
                        {
                            isValid = false;
                            break;
                        }



                    }
                }


                if (isValid)
                {
                    Console.WriteLine("Yes");
                }
                else
                {
                    Console.WriteLine("NO");
                }
            }
            Console.ReadKey();

        }
0
Maxymus
var verify = function(text) 
{
  var symbolsArray = ['[]', '()', '<>'];
  var symbolReg = function(n) 
  {
    var reg = [];
    for (var i = 0; i < symbolsArray.length; i++) {
      reg.Push('\\' + symbolsArray[i][n]);
    }
    return new RegExp('(' + reg.join('|') + ')','g');
  };
  // openReg matches '(', '[' and '<' and return true or false
  var openReg = symbolReg(0);
  // closeReg matches ')', ']' and '>' and return true or false
  var closeReg = symbolReg(1);
  // nestTest matches openSymbol+anyChar+closeSymbol
  // and returns an obj with the match str and it's start index
  var nestTest = function(symbols, text) 
  {
    var open = symbols[0]
      , close = symbols[1]
      , reg = new RegExp('(\\' + open + ')([\\s\\S])*(\\' + close + ')','g')
      , test = reg.exec(text);
    if (test) return {
      start: test.index,
      str: test[0]
    };
    else return false;
  };
  var recursiveCheck = function(text) 
  {
    var i, nestTests = [], test, symbols;
    // nestTest with each symbol
    for (i = 0; i < symbolsArray.length; i++) 
    {
      symbols = symbolsArray[i];
      test = nestTest(symbols, text);
      if (test) nestTests.Push(test);
    }
    // sort tests by start index
    nestTests.sort(function(a, b) 
    {
      return a.start - b.start;
    });
    if (nestTests.length) 
    {
      // build nest data: calculate match end index
      for (i = 0; i < nestTests.length; i++) 
      {
        test = nestTests[i];
        var end = test.start + ( (test.str) ? test.str.length : 0 );
        nestTests[i].end = end;
        var last = (nestTests[i + 1]) ? nestTests[i + 1].index : text.length;
        nestTests[i].pos = text.substring(end, last);
      }
      for (i = 0; i < nestTests.length; i++) 
      {
        test = nestTests[i];
        // recursive checks  what's after the nest 
        if (test.pos.length && !recursiveCheck(test.pos)) return false;
        // recursive checks  what's in the nest 
        if (test.str.length) {
          test.str = test.str.substring(1, test.str.length - 1);
          return recursiveCheck(test.str);
        } else return true;
      }
    } else {
      // if no nests then check for Orphan symbols
      var closeTest = closeReg.test(text);
      var openTest = openReg.test(text);
      return !(closeTest || openTest);
    }
  };
  return recursiveCheck(text);
};
0

中括弧をスタックに入れる代わりに、2つのポインターを使用して文字列の文字を確認できます。 1つは文字列の先頭から始まり、もう1つは文字列の末尾から始まります。何かのようなもの

bool isValid(char* s) {
    start = find_first_brace(s);
    end = find_last_brace(s);
    while (start <= end) {
        if (!IsPair(start,end)) return false;
        // move the pointer forward until reach a brace
        start = find_next_brace(start);
        // move the pointer backward until reach a brace
        end = find_prev_brace(end);
    }
    return true;
}

処理されないコーナーケースがあることに注意してください。

0
Jiangbo

O(n)アルゴリズムを実装できると思います。単純に、各タイプのカウンター変数を初期化する必要があります。中括弧、角括弧、通常の括弧。ブラケットが開いている場合は対応するカウンターを増やし、そうでない場合は減らします。カウンターが負の場合はfalseを返します。O(n)アルゴリズムを実装できると思います。各タイプのカウンター変数:中括弧、角括弧、通常の括弧。その後、文字列を繰り返し、括弧が開いている場合は対応するカウンターを増やし、そうでない場合は減らす必要があります。カウンターが負の場合はfalseを返します。すべてのカウンタがゼロかどうかを確認する必要がありますその場合、文字列は有効であり、trueを返す必要があります。

0
Svetlin Ralchev