如何找到一串括号,大括号和方括号的有效性?

时间:2010-03-24 16:16:56

标签: algorithm string

我最近接触到了这个有趣的问题。您将获得一个仅包含字符'('')''{''}''['']'的字符串,例如{{1} 1}},你需要编写一个函数来检查这样一个输入字符串的有效性,函数可能是这样的:
 "[{()}]"
这些括号必须以正确的顺序关闭,例如bool isValid(char* s);"()"都有效,但"()[]{}""(]""([)]"不是!

我推出了O(n)时间和O(n)空间复杂度解决方案,效果很好:

  1. 保持一堆字符。
  2. 每当您发现开口大括号"{{{{"时,'(''{'将其推入堆栈。
  3. 每当你发现结束括号'['')''}'时,检查堆栈顶部是否对应开括号,如果是,则弹出堆栈,否则打破循环并返回假的。
  4. 重复步骤2 - 3直到字符串结束。
  5. 这是有效的,但我们可以优化空间,可以是恒定的额外空间,我知道时间复杂度不能小于O(n),因为我们必须查看每个角色。

    所以我的问题是我们可以在O(1)空间中解决这个问题吗?

17 个答案:

答案 0 :(得分:12)

参考 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);
}

基本上,它所做的就是删除一对括号,直到没有任何一个删除;如果还有什么东西,那么托架就不会很好。

格式良好的括号的例子:

()[]
{()[]}

格式错误的括号示例:

([)]
{()[}]

答案 1 :(得分:11)

实际上,由于Ritchie和Springsteel有一个确定性的对数空间算法:http://dx.doi.org/10.1016/S0019-9958(72)90205-7 paywalled,抱歉不在线)。由于我们需要日志位来索引字符串,因此这是空间最优的。


如果您愿意接受片面错误,那么有一种算法使用n polylog(n)时间和polylog(n)空间:http://www.eccc.uni-trier.de/report/2009/119/

答案 2 :(得分:6)

如果输入是只读的,我认为我们不能做O(1)空间。众所周知,任何O(1)空间可判定语言都是规则的(即可写为正则表达式)。你拥有的字符串集不是常规语言。

当然,这是关于图灵机。我希望固定字RAM机也是如此。

答案 3 :(得分:3)

编辑:虽然简单,但就角色比较而言,此算法实际上是O(n ^ 2)。为了演示它,可以简单地生成一个字符串'(' * n + ')' * n

我有一个简单但可能是错误的想法,我会接受你的批评。

这是一种破坏性算法,这意味着如果你需要字符串它就无济于事(因为你需要将其复制下来)。

否则,算法使用当前字符串中的简单索引。

这个想法是一个接一个地删除对:

  1. ([{}()])
  2. ([()])
  3. ([])
  4. ()
  5. empty - &gt; OK
  6. 这是基于一个简单的事实,即如果我们有匹配的对,那么至少有一个是()形式,其间没有任何配对字符。

    算法:

    1. i := 0
    2. i找到匹配的对。如果未找到,则该字符串无效。如果找到一个,请i为第一个字符的索引。
    3. 从字符串
    4. 中删除[i:i+1]
    5. 如果i位于字符串的末尾,而字符串不为空,则表示失败。
    6. 如果[i-1:i]是匹配对,i := i-1并返回3。
    7. 否则,回到1。
    8. 该算法的复杂度为O(n)因为:

      • 循环的每次迭代都会从字符串中删除2个字符
      • 第2步是线性的,自然是绑定的(i无法无限增长)

      它在空间中是O(1),因为只需要索引。

      当然,如果你不能销毁字符串,那么你将不得不复制它,那就是O(n)在太空中,所以没有真正的好处!

      当然,除非我在某个地方犯了很大的错误......也许有人可以使用最初的想法(在某处有一对)来更好地发挥作用。

答案 4 :(得分:2)

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

用堆栈解决这个问题是很自然的。

如果仅使用'('和')',则不需要堆栈。我们只需要维持一个不匹配左边的计数器'('。如果计数器在匹配期间总是非负的并且在结束时为零,则表达式有效。

一般情况下,虽然堆栈仍然是必需的,但是通过使用不匹配的大括号计数器可以减少堆栈的深度。

答案 5 :(得分:2)

我怀疑你会找到一个更好的解决方案,因为即使你使用内部函数来regexp或计算出现次数,它们仍然有O(...)成本。我说你的解决方案是最好的:)

要优化空间,你可以在你的堆栈上进行一些游程编码,但我怀疑它会给你带来很大的收获,除非像{{{{{{{{{{}}}}}}}}}}这样的情况。

答案 6 :(得分:2)

这是一个有效的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;
    }

}

答案 7 :(得分:2)

Sbusidan的答案的以下修改是O( n 2 )时间复杂但是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);
}

答案 8 :(得分:1)

如果你可以覆盖输入字符串(在我设想的用例中不合理,但是哎呀...)你可以在恒定的空间内完成它,尽管我相信时间要求达到 O( ñ 2

像这样:

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

这个的本质是将输入的早期部分用作堆栈。

答案 9 :(得分:1)

我知道我这个派对有点晚了;这也是我在StackOverflow上的第一篇文章。

但是当我查看答案时,我想我可以提出更好的解决方案。

所以我的解决方案是使用一些指针 它甚至不必使用任何RAM存储器,因为可以使用寄存器。
我没有测试过代码;它是在飞行中写的。
你需要修复我的拼写错误并进行调试,但我相信你会明白这个想法。

内存使用:大多数情况下只有CPU注册 CPU使用率:取决于,但大约是读取字符串所需时间的两倍 修改内存:否。

b:字符串 b eginning,e:string e nd。
l: l eft位置,r: r 位置。
c: c har,m: m atch char

如果r到达字符串的末尾,我们就取得了成功 l从r向后退回b。
每当r遇到新的起始类型时,设置l = r。
当我到达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 */
}

在考虑了这种方法一段时间之后,我意识到它现在不会起作用 问题是,如果你有“[()()]”,它会在到达']'时失败 但是,我不会删除建议的解决方案,而是将其留在这里,因为它实际上并非不可能,但它确实需要进行一些修改。

答案 10 :(得分:0)

/**
 *
 * @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;
}

}

答案 11 :(得分:0)

您可以使用两个指针来检查字符串的字符,而不是将大括号放入堆栈。一个从字符串的开头开始,另一个从字符串的结尾开始。

之类的东西
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;
}

请注意,有些角落案件没有处理。

答案 12 :(得分:0)

我认为您可以实现O(n)算法。只需要为每种类型初始化一个计数器变量:卷曲,方形和普通括号。之后应该迭代字符串,如果括号打开则应增加相应的计数器,否则减少它。如果计数器为负,则返回false。之后我认为你可以实现一个O(n)算法。只需要为每种类型初始化一个计数器变量:卷曲,方形和普通括号。之后应该迭代字符串,如果括号打开则应增加相应的计数器,否则减少它。如果计数器为负,则返回false。计算所有括号后,应检查所有计数器是否为零。在这种情况下,字符串有效,您应该返回true。

答案 13 :(得分:0)

这是我解决问题的方法。 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;
}

答案 14 :(得分:0)

您可以提供该值并检查其是否为有效值,它将打印为YES,否则将打印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();

        }

答案 15 :(得分:0)

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);
};

答案 16 :(得分:0)

使用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();