убрать лишние скобки

Вопрос:

Удалите лишние скобки из строки.
например.

    ((a+b))*c       => (a+b)*c  
    (a+b)+c         => (a+b)+c  
    ((a+b)/(c+d))   => ((a+b)/(c+d))   
    (a+(((b-c)))*d) => (a+(b-c)*d)  and so on.

Я пришел к следующему решению:
Подход: я просматриваю строку и запоминаю (используя карту) индекс открывающей скобки, а также то, лишняя она или нет (по умолчанию лишняя). Если я нахожу закрывающую скобку, я проверяю соответствующую открывающую скобку на карте и, если она лишняя, удаляю обе.

void removeExtraParentheses(string& S){
  map<int, bool> pmap;
  for(int i = 0; i < S.size(); i++){
    map<int, bool>::iterator it;
    if(S.at(i) == '('){
        pmap[i] = true;
    }
    else if(S.at(i) == ')'){
        it = pmap.end();
        it--;
        if(!(*it).second){
            pmap.erase(it);
        }
        else{
            S.erase(S.begin() + i);
            S.erase(S.begin() + (*it).first);
            pmap.erase(it);
            i = i - 2;
        }
    }
    else{
        if(!pmap.empty()){
            it = pmap.end();
            it--;
            (*it).second= false;
        }
    }
  }
}  

Временная сложность: O(n2)
Пространство: O(n)
Я не слишком доволен своим решением, потому что использую дополнительное хранилище и делаю это за квадратичное время.

Можем ли мы сделать это за время O(n) и пространство O(1)? Если нет, что лучше всего можно сделать?


person NGambit    schedule 02.11.2012    source источник
comment
когда вы говорите дополнительные скобки, вы просто имеете в виду места, где они есть (( или вы имеете в виду места, где математика не требует их? например, (a+b)+c также дает a+b+c, поскольку сложение является коммутативным. если нет, просто замените строку ((( (( на (, и ))) и )) на ).   -  person Frank Thomas    schedule 03.11.2012
comment
Я думаю, что второй образец, который я дал, развеивает ваши сомнения. Я не уверен, что просто замените строку ((( (( с (, и ))) и )) с ) работает   -  person NGambit    schedule 03.11.2012
comment
@FrankThomas Во-первых, примеры показывают, что математика не учитывается, и заменяются только двойные скобки. Во-вторых, это провал. Подумайте, что бы это сделало с ((a + b) + (c + d))   -  person Jasper    schedule 03.11.2012
comment
@jasper, я думаю, он вернет (a + b) + (c + d). хороший момент, их тоже можно было бы удалить.   -  person Frank Thomas    schedule 03.11.2012
comment
@FrankThomas Вы подняли интересную проблему. Для типов с плавающей запятой (и, возможно, для целочисленных типов со знаком, но это не относится ни к одной из обычных архитектур), сложение не ассоциативно, и (a + b) + c совпадает с a + b + c, но в случае a + (b + c) вам нужны круглые скобки. Или, возможно, его проблема недостаточно определена: на машине с Windows или на большинстве Unices, в последнем случае, вам не нужны скобки для int, но на какой-то экзотической машине они могут понадобиться.   -  person James Kanze    schedule 03.11.2012
comment
@FrankThomas Re (a + b) + (c + d): первое явно можно удалить. Второй не может по крайней мере с плавающей запятой. Другими словами, (a + b) + (c + d) дает те же результаты, что и a + b + (c +d), но другие результаты, чем (a + b) + c + d.   -  person James Kanze    schedule 03.11.2012
comment
@FrankThomas Я имел в виду, что, судя по образцам, он не принимает во внимание какой-либо приоритет оператора, ведь он ничего не удаляет в (a+b)+c (если бы это было об особых пограничных случаях, как предлагает @JamesKenze, он бы упомянул об этом) . Таким образом, я считаю, что он хочет удалить только настоящие двойные пары, и в этом случае ваш план не работает, потому что он удаляет что-то из ((a + b) + (c + d)) (или ((a + b) + (c + d)) * e, если хотите)   -  person Jasper    schedule 03.11.2012


Ответы (3)


Постройте дерево выражений, затем перегенерируйте выражение с минимальным количеством скобок. Любые круглые скобки в оригинале, которых нет в генераторах, не нужны.

Простым, почти правильным решением было бы назначить приоритет каждому оператору. Затем вам понадобятся круглые скобки каждый раз, когда узел непосредственно под узлом, над которым вы работаете, является оператором, который имеет более низкий приоритет, чем у узла; например, если вы находитесь на узле * (умножение), а один из двух дочерних узлов имеет узел + (сложение). Плюс немного логики для обработки левой или правой привязки: если вы находитесь на узле +, а правый узел также является узлом +, вам нужны круглые скобки.

Это правильно лишь отчасти, потому что в C++ есть несколько конструкций, которые на самом деле не могут быть сопоставлены с грамматикой приоритета: на ум приходят некоторые конструкции приведения типов или тернарные операторы. Однако, по крайней мере, в случае с тернарным оператором особая обработка не так уж сложна.

РЕДАКТИРОВАТЬ:

Что касается ваших больших вопросов: это, очевидно, не O (1) в пространстве, поскольку вам нужно построить все выражение в памяти. Я не думаю, что O (1) для памяти возможен, поскольку потенциально вы можете иметь неограниченное количество вложений, и вы не можете сказать, необходимы ли круглые скобки или нет, до неопределенного времени позже. Я на самом деле не анализировал это, но я думаю, что это O (n) по времени. Верхняя граница количества узлов равна n (длина строки), и вам нужно посетить каждый узел ровно один раз.

person James Kanze    schedule 02.11.2012
comment
+1: Судя по представленному коду, ОП может быть незнаком с разбором приоритета операторов, так что это может быть выше его/ее головы, но, тем не менее, это правильный ответ. Я бы не стал беспокоиться о конструкциях приведения С++ и т.д.; вопрос OP, похоже, касается только выражения id и бинарной операции (id сам по себе является унарным выражением, но я не уверен, понимают ли они это или нет). Кроме того, я не вижу там унарного минуса и, надеюсь, ради них он не подразумевается. - person WhozCraig; 03.11.2012
comment
@WhozCraig Я подозреваю, что вы правы в отношении того, что может знать ОП. Но это очень похоже на домашнее задание, и цель, вероятно, состоит в том, чтобы он реализовал простой синтаксический анализатор для его решения. Если он не знаком с синтаксическим анализом приоритета операторов как с методом или как с термином, он, вероятно, знает (или должен, если он следовал курсу) иметь по крайней мере интуитивное представление о приоритете и знать, как написать простой синтаксический анализатор, который его принимает. в учетную запись. - person James Kanze; 03.11.2012
comment
@WhozCraig И FWIW, я никогда не упоминал разбор приоритета оператора; Я только что сказал построить дерево выражений (что очень легко сделать с помощью рекурсивного спуска). pokey909 не говорил о синтаксическом анализе, но его псевдокод — это настоящий синтаксический анализ приоритета. Что мне действительно нравится (хотя он не будет правильно обрабатывать такие случаи, как оператор ?:). - person James Kanze; 03.11.2012
comment
Я не уверен, как бы вы сократили свое дерево выражений без решения о приоритете. Мне бы он точно нужен. В этом, я думаю, вы оба правы. pokeys answer не было, когда я сделал свой первоначальный комментарий, но теперь, когда я его вижу, я согласен с вами. - person WhozCraig; 03.11.2012
comment
@WhozCraig C ++ не имеет грамматики приоритета, и есть несколько нечетных углов (вероятно, не имеющих значения здесь), где его нельзя выразить в грамматике приоритета. В C++ нет приоритета (но может быть полезно представить его с использованием приоритета, если не забыты нечетные углы). - person James Kanze; 05.11.2012

Более-менее нашел в сети...

Данные входные данные: ((A+B)*C) Ожидаемый результат: (A+B)*C

Предположения:

  • Peek (очередь) просто указывает элемент в начале очереди, не удаляя его.
  • priority() — это функция, которая просматривает таблицу приоритетов операторов.

Псевдокод ниже:

  1. Преобразование инфиксного выражения в RPN (например, алгоритм маневровой станции O (n))

    AB+C*

  2. Вставить только операторов в очередь Q

    (спереди)+ -------- *(сзади)

  3. Разобрать постфиксное выражение
  4. Если операнд, поместите в стек 'S'
  5. If operator
    • y=Delete(Q)
    • Если приоритет (y) > приоритет (Peek (Q)), то нажмите (S, «Pop (S) y Pop (S)»)
    • Если приоритет(y) ‹ приоритет(Peek(Q)), то Push (S, "( Pop(S) y Pop(S) )")
  6. Окончательный результат поверх S

Все должно быть O(n).

person pokey909    schedule 02.11.2012

Я думал, что я возьму удар на это. Это решение проблемы, которая пришла мне в голову. Обратите внимание, что это псевдокод и не предназначен для прямого запуска.

(На самом деле, это скорее похоже на C++, но я уже давно не писал настоящий C++, и мне не хотелось прилагать усилия, чтобы сделать все правильно, когда я намеревался пройти через алгоритм.)

queue<tuple<int, bool> > q = new queue<tuple<int, bool> >();

for (int i = 0; i < str.length; i++)
{
    char a = str.charAt(i);

    if (a == '(')
    {
        q.push(new tuple<int, bool>(i, false));
    }
    else if (a == ')')
    {
        if (q.top().bool)
        {
            // remove top.int and i from string
        }

        q.pop();
        q.top().bool = true;
    }
    else
    {
        q.top().bool = false;
    }
}

Он выполняет работу в O(n) и использует O(n) пространства (или на самом деле объем используемого пространства фактически основан на самом глубоком уровне вложенности, присутствующем в строке, но он гарантированно будет ниже, чем n)

(Обратите внимание, что // remove top.int and i from string на самом деле нельзя сделать в O(1). Однако, если вы проявите немного изобретательности, вы можете сделать что-то подобное в O(1). Например, вы можете создать список символов для вывода и сохранить итератор вместо int, то вы можете удалить два символа в O (1). В конце вы можете построить свою окончательную строку, перебирая список в O (n). Альтернативным решением было бы фактически работать с массивом (или вектором ) из DummyOrCharacters, которые либо являются фиктивными и ничего не содержат, либо содержат символ. Еще раз, вы можете заменить символ фиктивным в O(1). Еще раз, вы будете перебирать структуру и строить свою выходную строку в O(n))

person Jasper    schedule 03.11.2012