Ответ 1
Обзор проблемы
Это классическая комбинаторная проблема, которая проявляется по-разному. Эти проблемы по существу идентичны:
- Создание всех возможных способов балансировки пар круглых скобок
N
(т.е. этой проблемы) - Создание всех возможных способов применения бинарного оператора к
N+1
факторам - Генерация всех полных двоичных деревьев с
N+1
оставляет - Многие другие...
См. также
Прямое рекурсивное решение
Здесь простой рекурсивный алгоритм для решения этой проблемы в Java:
public class Parenthesis {
static void brackets(int openStock, int closeStock, String s) {
if (openStock == 0 && closeStock == 0) {
System.out.println(s);
}
if (openStock > 0) {
brackets(openStock-1, closeStock+1, s + "<");
}
if (closeStock > 0) {
brackets(openStock, closeStock-1, s + ">");
}
}
public static void main(String[] args) {
brackets(3, 0, "");
}
}
Вышеприведенные отпечатки (как видно на ideone.com):
<<<>>>
<<><>>
<<>><>
<><<>>
<><><>
По существу, мы отслеживаем, сколько открытых и закрытых круглых скобок "доступно" для использования нами при построении строки рекурсивно.
- Если ничего нет в наличии, строка полностью построена, и вы можете просто распечатать ее
- Если на складе имеется открытая скобка, попробуйте добавить ее.
- Теперь у вас есть одна открытая скобка, но еще одна закрывающая скобка для ее баланса.
- Если на складе имеется закрытая скобка, попробуйте добавить ее.
- Теперь у вас есть одна менее близкая скобка
Обратите внимание: если вы поменяете порядок рекурсии таким образом, что вы пытаетесь добавить закрывающую скобку, прежде чем пытаться добавить открытую скобку, вы просто получите тот же список сбалансированных круглых скобок, но в обратном порядке! (см. на ideone.com).
"Оптимизированный" вариант
Вышеупомянутое решение является очень простым и поучительным, но может быть оптимизировано далее.
Самая важная оптимизация - в аспекте построения строки. Хотя это выглядит как простая конкатенация строк на поверхности, в приведенном выше решении фактически есть "скрытый" компонент построения строки O(N^2)
(поскольку конкатенация одного символа на неизменяемый String
длины N
является операцией O(N)
), Как правило, мы оптимизируем это, используя вместо этого переменный StringBuilder
, но для этого конкретного случая мы также можем просто использовать переменную char[]
и index
с фиксированным размером.
Мы также можем оптимизировать, упростив дерево рекурсии. Вместо того, чтобы рекурсировать "оба пути", как в исходном решении, мы можем просто повторить "один путь" и сделать "другой путь" итеративно.
В дальнейшем мы выполнили обе оптимизации, используя char[]
и index
вместо String
, и рекурсивно добавляем открытые круглые скобки, добавляя тесные круглые скобки итеративно: (см. также на ideone.com)
public class Parenthesis2 {
public static void main(String[] args) {
brackets(4);
}
static void brackets(final int N) {
brackets(N, 0, 0, new char[N * 2]);
}
static void brackets(int openStock, int closeStock, int index, char[] arr) {
while (closeStock >= 0) {
if (openStock > 0) {
arr[index] = '<';
brackets(openStock-1, closeStock+1, index+1, arr);
}
if (closeStock-- > 0) {
arr[index++] = '>';
if (index == arr.length) {
System.out.println(arr);
}
}
}
}
}
Логика рекурсии теперь менее очевидна, но две методики оптимизации являются поучительными.