web-dev-qa-db-fra.com

Comment trouver la validité d'une chaîne de parenthèses, accolades et crochets?

Je suis récemment entré en contact avec ce problème intéressant. Une chaîne contenant uniquement les caractères '(', ')', '{', '}', '[' et ']', par exemple, "[{()}]", vous devez écrire une fonction permettant de vérifier la validité de cette chaîne de saisie. .
bool isValid(char* s);
ces crochets doivent se fermer dans le bon ordre, par exemple "()" et "()[]{}" sont tous valides, mais "(]", "([)]" et "{{{{" ne le sont pas!

Je suis sorti avec ce qui suit O(n) temps et O(n) solution de complexité d'espace, qui fonctionne bien:

  1. Maintenir une pile de caractères.
  2. Chaque fois que vous trouvez des accolades ouvrantes '(', '{' OR '[' Placez-le dans la pile.
  3. Chaque fois que vous trouvez des accolades fermantes ')', '}' OR ']', vérifiez si le haut de la pile correspond au crochet ouvrant correspondant, si oui, sautez la pile, sinon coupez la boucle et renvoyez false.
  4. Répétez les étapes 2 à 3 jusqu'à la fin de la chaîne.

Cela fonctionne, mais pouvons-nous l'optimiser pour l'espace, peut être un espace supplémentaire constant, je comprends que la complexité temporelle ne peut pas être inférieure à O(n) car nous devons examiner chaque caractère.

Ma question est donc la suivante: pouvons-nous résoudre ce problème dans O(1) espace?

45
Rajendra Uppal

En fait, il existe un algorithme d’espace de log déterministe dû à Ritchie et Springsteel: http://dx.doi.org/10.1016/S0019-9958(72)90205-7 (paywalled, désolé pas en ligne). Puisque nous avons besoin de bits de journal pour indexer la chaîne, c'est un espace optimal.


Si vous êtes prêt à accepter une erreur unilatérale, il existe un algorithme qui utilise n temps polylog (n) et espace polylog (n): http://www.eccc.uni-trier.de/report/2009/119/

11
user287792

En référence à l'excellente réponse de Matthieu M. , voici une implémentation en C # qui semble fonctionner à merveille.

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

Essentiellement, tout ce qu'il fait est de supprimer des paires de crochets jusqu'à ce qu'il ne reste plus rien à supprimer. s'il reste quelque chose, les crochets ne sont pas bien formés.

Exemples de crochets bien formés:

()[]
{()[]}

Exemple de supports mal formés:

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

Si l'entrée est en lecture seule, je ne pense pas que nous puissions faire O(1) space. C’est un fait bien connu que tout O(1) langage décidable dans l’espace est régulier (c’est-à-dire qu’il est possible d’écrire comme une expression régulière). L'ensemble de chaînes que vous avez n'est pas un langage courant.

Bien sûr, il s’agit d’une machine de Turing. Je m'attendrais à ce que cela soit vrai pour les machines Word RAM fixes aussi. 

6
Aryabhatta

Edit: Bien que simple, cet algorithme est en fait O (n ^ 2) en termes de comparaisons de caractères. Pour le démontrer, on peut simplement générer une chaîne sous la forme '(' * n + ')' * n.

J'ai une idée simple, bien que peut-être erronée, que je soumettrai à vos critiques.

C'est un algorithme destructeur, ce qui signifie que si vous aviez besoin de la chaîne, cela ne vous aiderait pas (puisque vous auriez besoin de la copier).

Sinon, l'algorithme fonctionne avec un index simple dans la chaîne en cours.

L'idée est de supprimer les paires les unes après les autres:

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

Il est basé sur le simple fait que si nous avons des paires correspondantes, au moins une d'entre elles est de la forme () sans aucun caractère de paire entre elles.

Algorithme:

  1. i := 0
  2. Trouvez une paire correspondante dans i. Si aucun n'est trouvé, alors la chaîne n'est pas valide. S'il en trouve un, insérons i l'index du premier caractère.
  3. Supprimer [i:i+1] de la chaîne
  4. Si i est à la fin de la chaîne et si la chaîne n'est pas vide, c'est un échec.
  5. Si [i-1:i] est une paire correspondante, i := i-1 et revenez à 3.
  6. Sinon, revenons à 1.

L'algorithme est de complexité O(n) parce que:

  • chaque itération de la boucle supprime 2 caractères de la chaîne
  • l'étape 2., qui est linéaire, est naturellement liée (i ne peut pas croître indéfiniment)

Et c'est O(1) dans l'espace car seul l'index est requis.

Bien sûr, si vous ne pouvez pas vous permettre de détruire la chaîne, vous devrez la copier, et c'est O(n) dans l'espace, donc aucun avantage réel!

À moins, bien sûr, que je me trompe profondément quelque part ... et que quelqu'un pourrait peut-être utiliser l'idée originale (il y en a une paire quelque part) pour obtenir de meilleurs résultats.

3
Matthieu M.

Il s'agit d'un code Java fonctionnel dans lequel je filtre les crochets de l'expression de chaîne, puis vérifie la qualité de la mise en forme en remplaçant les accolades wellformed par des valeurs null.

Exemple input = (a+{b+c}-[d-e])+[f]-[g] FilterBrackets affichera = ({}[])[][] Ensuite, je vérifie le bon fonctionnement.

Commentaires bienvenus.

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
Balaji Ramamurthy

Je doute que vous trouviez une meilleure solution, car même si vous utilisez des fonctions internes pour créer une expression rationnelle ou compter les occurrences, elles ont toujours un coût O(...). Je dirais que votre solution est la meilleure :)

Pour optimiser l’espace, vous pouvez effectuer un encodage de longueur d’exécution sur votre pile, mais je doute que cela vous rapporterait beaucoup, sauf dans des cas comme {{{{{{{{{{}}}}}}}}}}.

2
chris

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

Il est naturel de résoudre ce problème avec une pile.

Si seuls '(' et ')' sont utilisés, la pile n'est pas nécessaire. Nous avons juste besoin de maintenir un compteur pour la gauche non appariée '('. L'expression est valide si le compteur est toujours non négatif pendant le match et vaut zéro à la fin.

Dans le cas général, bien que la pile soit toujours nécessaire, sa profondeur peut être réduite en utilisant un compteur pour les accolades sans correspondance. 

2
puttyshell

La modification suivante de la réponse de Sbusidan est O (n2) complexe temporel mais O (log n) espace simple.

#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
Bjartur Thorlacius

Si vous pouvez écraser la chaîne d'entrée (ce qui n'est pas raisonnable dans les cas d'utilisation que j'envisage, mais bon sang ...), vous pouvez le faire dans un espace constant, même si je crois que le temps requis va jusqu'à O (n2).

Comme ça:

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

L'essentiel est d'utiliser la première partie de l'entrée en tant que pile.

1
dmckee

Je sais que je suis un peu en retard à cette fête; c'est aussi mon tout premier article sur StackOverflow.

Mais quand j'ai parcouru toutes les réponses, j'ai pensé que je pourrais peut-être trouver une meilleure solution.

Ma solution consiste donc à utiliser quelques indicateurs.
Il n’est même pas nécessaire d’utiliser la mémoire RAM, les registres peuvent être utilisés à cet effet.
Je n'ai pas testé le code; c'est écrit à la volée.
Vous aurez besoin de corriger mes fautes de frappe et de les corriger, mais je pense que vous aurez l’idée.

Utilisation de la mémoire: seul le processeur s'enregistre dans la plupart des cas.
Utilisation du processeur: Cela dépend, mais environ deux fois plus de temps que nécessaire pour lire la chaîne.
Modifie la mémoire: Non.

b: chaîne beginning, e: string edakota du Nord.
l: lposition arrière, r: right position.
c: char, m: match char

si r atteint la fin de la chaîne, nous avons réussi.
l va en arrière de r vers b.
Chaque fois que r rencontre un nouveau type de début, définissez l = r.
lorsque l atteint b, nous en avons terminé avec le bloc; saute au début du bloc suivant.

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

Après avoir réfléchi à cette approche pendant un moment, je me suis rendu compte que cela ne fonctionnerait pas tel qu’il est actuellement.
Le problème sera que si vous avez "[() ()]", cela échouera si vous atteignez le ']'.
Mais au lieu de supprimer la solution proposée, je la laisserai ici, car il n’est en fait pas impossible de la faire fonctionner, elle nécessite cependant quelques modifications.

1
user1985657

Utilisation de la programmation c # OOPS ... Petite solution simple

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

C’est ma solution au problème .. O (n) est la complexité du temps sans complexité de l’espace . Code en 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
Madhusudan Singh

Vous pouvez fournir la valeur et vérifier si elle est valide, elle imprimera OUI sinon elle imprimera NON

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
rafaelcastrocouto

Au lieu de mettre des accolades dans la pile, vous pouvez utiliser deux pointeurs pour vérifier les caractères de la chaîne. une commence au début de la chaîne et l'autre commence à la fin de la chaîne. quelque chose comme

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

Notez qu'il y a des cas de coin non traités.

0
Jiangbo

Je pense que vous pouvez implémenter un algorithme O(n). Vous devez simplement initialiser une variable de compteur pour chaque type: crochets bouclés, carrés et normaux. Après que vous devriez itérer la chaîne et devrait augmenter le compteur correspondant si le crochet est ouvert, sinon le diminuer. Si le compteur est négatif, retournez false. Après, je pense que vous pouvez implémenter un algorithme O(n). Vous devez simplement initialiser une variable de compteur pour chaque type: crochets bouclés, carrés et normaux. Après que vous devriez itérer la chaîne et devrait augmenter le compteur correspondant si le crochet est ouvert, sinon le diminuer. Si le compteur est négatif, retournez false. Après avoir compté tous les crochets, vous devez vérifier si tous les compteurs sont à zéro. Dans ce cas, la chaîne est valide et vous devez renvoyer true.

0
Svetlin Ralchev