【问题标题】:How to find validity of a string of parentheses, curly brackets and square brackets?如何找到一串括号、大括号和方括号的有效性?
【发布时间】:2011-01-31 08:22:14
【问题描述】:

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

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

  1. 维护一堆字符。
  2. 只要找到左大括号 '(''{''[' 就将其压入堆栈。
  3. 当你找到右大括号')''}'']'时,检查栈顶是否是对应的左括号,如果是,则弹出堆栈,否则中断循环并返回false。
  4. 重复步骤 2 - 3 直到字符串结束。

这可行,但是我们可以针对空间进行优化吗,可能是恒定的额外空间,我知道时间复杂度不能小于 O(n),因为我们必须查看每个字符。

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

【问题讨论】:

  • 很高兴你没有要求正则表达式……
  • 您的算法似乎缺少一步。您弹出右大括号,但不要松开左大括号。
  • @Jeff B:右大括号甚至没有被压入堆栈。如果找到的右大括号对应于堆栈上的顶部左大括号,则仅将顶部左大括号弹出堆栈。
  • 我认为我没有遗漏任何东西,我已经检查过了,它运行良好。我从来没有说过我弹出右大括号,见步骤 3,我说:右大括号是下一个标记,我检查它是否与当前堆栈顶部匹配,然后我弹出堆栈,意味着取出左大括号并扔掉它,然后我继续...
  • 当你到达一个字符串的末尾时,你不应该检查堆栈是否为空吗?

标签: algorithm string


【解决方案1】:

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

本质上,它所做的只是删除成对的括号,直到没有剩余的括号可以删除;如果还有任何东西,括号的格式就不是很好。

格式正确的括号示例:

()[]
{()[]}

格式错误的括号示例:

([)]
{()[}]

【讨论】:

  • { ( [ ] ) } 等相当合法的输入下能否正常工作而在( [ { ) ] } 下失败?
  • @abatishchev 是的。
  • 好时机:))
  • 好主意,但是这个的时间复杂度是 O(N^2),看看这个例子 (((((())))))。此外,您不断分配字符串,这是一个非常糟糕的主意。
  • 这个 O(1) 空间怎么样?你做了很多新的字符串。
【解决方案2】:

实际上,由于 Ritchie 和 Springsteel,有一个确定性的对数空间算法: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/

【讨论】:

  • 你能概括一下算法吗?
  • 看来这需要去图书馆一趟。我怀疑第二个链接中的算法可以去随机化,并通过多次传递将其存储减少到 O(log n) 位,但我还没有弄清楚细节。
  • 第一个链接的论文现在似乎可用(单击页面左上角的 PDF 链接。在第 319 页上,在定义 3 中,我们看到“Dyck 语言可以被认为是由匹配标记括号的格式良好的字符串”,这就是我们在这里讨论的内容。但就我个人而言,我需要花费大量时间从本文开始编写代码......
  • @user287792 无法访问该文章,但我想出了一个(我相信类似的)解决方案。但是假设字符串有 n 位长,并且我们想检查位置 i 的左括号对应于位置 n - i 的右括号(或 n - i +1,具体取决于您的索引方式),那么复杂度是多少?操作“ni”?它不需要n个空间吗?或者我们可以简单地存储像 {n,i,n} 这样的东西并减少第二个 n 吗?所以我们使用 O(log n) 空间?
【解决方案3】:

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

当然,这是关于图灵机的。我希望它也适用于固定字 RAM 机器。

【讨论】:

  • 对于固定字 RAM 机器来说这很简单,因为 O(1) 位足以处理输入的常量前缀。通常当人们说 O(1) 空间时,他们实际上是指 O(1) 词,除非他们谈论的是可以流式传输输入的计算模型。
  • @algorithmist:是的,不指定模型,谈论复杂性没有太大意义。我的主要观点是,即使允许 O(n^2) 时间(或任何其他任意时间),在 TM 上的 O(1) 空间中仍然可能无法进行。 RAM 是一个不同的模型,在谈论复杂性之前需要明确一些假设,就像你说的,如果我们假设我们只做索引(而不是沿输入的 +1 或 -1),那么我们需要 O (logn) 空间。
【解决方案4】:

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

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

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

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

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

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

这是基于一个简单的事实,如果我们有匹配的对,那么至少有一个是 () 的形式,中间没有任何对字符。

算法:

  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),所以那里没有真正的好处!

当然,除非我在某处严重错误……也许有人可以使用最初的想法(某处有一对)来获得更好的效果。

【讨论】:

  • n + (n-2) + (n-4) + … 是 O(n^2)。
  • 假设输入字符串是这样的 {[(])}
  • @raym0nd:你提出的字符串不正确,我认为我概述的算法会检测到它不是......你担心什么?
  • 算法虽然简单漂亮,但不是O(n),而是O(n^2)
  • 该算法使用 log(n) 空间来存储索引。
【解决方案5】:

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

为了优化空间,您可以在堆栈上进行一些行程编码,但我怀疑它会为您带来很多好处,除非{{{{{{{{{{}}}}}}}}}} 这样的情况。

【讨论】:

  • 他说的是更好的大 O 空间解决方案,而不是时间。
  • 如果你在 lisp 中计算括号,我想这将是一个很好的优化
  • 如果您只查看一种类型的括号,则可以使用 O(log2 N) 空间(一个计数器,打开时递增,关闭时递减)。
【解决方案6】:

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

用栈来解决这个问题很自然。

如果只使用 '(' 和 ')',则不需要堆栈。我们只需要为不匹配的左 '(' 维护一个计数器。如果计数器在匹配期间始​​终为非负并且最后为零,则表达式有效。

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

【讨论】:

    【解决方案7】:

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

    【讨论】:

    • 你创建了很多新字符串,而不是 O(1)。
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      如果您可以覆盖输入字符串(在我设想的用例中不合理,但到底是什么......)您可以在恒定空间中完成它,尽管我相信时间要求会上升到 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
      

      这里的本质是把输入的早期部分作为栈。

      【讨论】:

        【解决方案10】:

        我知道我参加这个聚会有点晚了;这也是我在 StackOverflow 上的第一篇文章。

        但是当我查看答案时,我想我可能会想出一个更好的解决方案。

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

        内存使用:大多数情况下只有 CPU 寄存器。
        CPU 使用率:视情况而定,但大约是读取字符串所需时间的两倍。
        修改内存:否。

        b:字符串 beginning,e:字符串 end.
        l:l右位,r:r右位。
        c: char, m: match char

        如果 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 */
        }
        

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

        【讨论】:

        • 关于如何使其发挥作用的提示:您需要跳出框框思考片刻。不要认为“堆叠”,而是“跳过计数”;例如,在开始比较之前,您需要跳回多少次。这段代码太复杂了,在这里贴出来,但现在你应该有足够的灵感来开始了。
        【解决方案11】:
        /**
         *
         * @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;
        }
        
        }
        

        【讨论】:

        • 感谢您发布答案!虽然代码 sn-p 可以回答这个问题,但添加一些附加信息仍然很棒,比如解释等..
        【解决方案12】:

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

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

        请注意,有些极端情况没有处理。

        【讨论】:

          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

            你可以提供值并检查它是否有效,它会打印 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】:

              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】:

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

                【讨论】:

                  【解决方案17】:

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

                  【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2016-12-14
                  • 2021-01-11
                  • 2014-01-11
                  • 1970-01-01
                  • 2011-09-26
                  • 1970-01-01
                  • 2019-02-22
                  • 2022-11-19
                  相关资源
                  最近更新 更多