Задача на работу со скобками

Условие задачи

Реализуйте алгоритм для вывода всех корректных  (правильно открытых и закрытых) комбинаций из n пар круглых скобок.

Пример:

Ввод: 3
Вывод: ( ( () ) ), ( ()() ), ( () )(), ()( () ), ()()()

Решение

Первое, что приходит в голову, — использовать рекурсивный подход и строить ре­шение для f(n), добавляя пары круглых скобок в f(n-1). Мысль абсолютно здравая.

Рассмотрим решение для n = З:

( () () )  (( () ))  ()( () )   ( () ) ()  ()()()

Как получить это решение из решения для n = 2?

( () )  () ()

Можно вставить пары скобок в каждую существующую пару скобок, а также одну пару в начале строки. Другие возможные места для вставки скобок (например, в конце строки) сводятся к предыдущим случаям.

Итак, имеем следующее:

( () ) -> ( ()() ) /* скобки вставлены после первой левой скобки */
-> (( () )) /* скобки вставлены после второй левой скобки */
-> ( ) ( () ) /* скобки вставлены в начале строки */
( ) ( ) -> ( () )() /* скобки вставлены после первой левой скобки */
-> ( ) ( ( ) ) /* скобки вставлены после второй левой скобки */
- > () () () /* скобки вставлены в начале строки */

Постойте, в списке есть дубликаты! Строка () ( () ) встречается дважды! Если мы будем использовать данный подход, следует реализовать проверку дубли­катов перед добавлением строки в список.

import java.util.HashSet;
import java.util.Set;

public class QuestionA {
	public static String insertInside(String str, int leftIndex) {
		String left = str.substring(0, leftIndex + 1);
		String right = str.substring(leftIndex + 1, str.length());
		return left + "()" + right;
	}
	
	public static Set generateParens(int remaining) {
		Set set = new HashSet();
		if (remaining == 0) {
			set.add("");
		} else {
			Set prev = generateParens(remaining - 1); 
			for (String str : prev) {
				for (int i = 0; i < str.length(); i++) {
					if (str.charAt(i) == '(') {
						String s = insertInside(str, i);
						/ * Добавить строку s в множество, если ее там нет . Примечание :
                                                  * HashSet автоматически проверяет дубликаты перед
                                                  * добавлением, так что явная проверка окажется лишней . */
						set.add(s);			
					}
				}
				set.add("()" + str);
			}
		}
		return set;
	}
	
	public static void main(String[] args) {
		Set list = generateParens(4);
		for (String s : list) {
			System.out.println(s);
		}
		System.out.println(list.size());
	}

Алгоритм работает, но не очень эффективно. Он тратит слишком много времени на дублирующиеся строки.

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

При каждом рекурсивном вызове мы получаем индекс определенного символа в строке. Теперь нужно выбрать скобку (левую или правую). Когда можно исполь­зовать левую скобку, а когда — правую?

  1. Левая скобка: пока не израсходованы все левые скобки, мы всегда можем вста­вить левую скобку.
  2. Правая скобка: добавить правую скобку можно в том случае, если добавление не приведет к синтаксической ошибке. Когда появляется синтаксическая ошибка? Тогда, когда правых скобок больше, чем левых.

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

import java.util.ArrayList;

public class QuestionB {
	
	public static void addParen(ArrayList list, int leftRem, int rightRem, char[] str, int index) {
		if (leftRem < 0 || rightRem < leftRem) return; // Некорректное состояние
		
		if (leftRem == 0 && rightRem == 0) { /* Скобок не осталось */
			list.add(String.copyValueOf(str));
		} else {
			str[index] = '('; /* Добавить левую скобку, если они еще остались . */
			addParen(list, leftRem - 1, rightRem, str, index + 1);
			
			str[index] = ')'; /* Добавить правую скобку, если выражение корректно */
			addParen(list, leftRem, rightRem - 1, str, index + 1);
		}
	}
	
	public static ArrayList generateParens(int count) {
		char[] str = new char[count*2];
		ArrayList list = new ArrayList();
		addParen(list, count, count, str, 0);
		return list;
	}
	
	public static void main(String[] args) {
		ArrayList list = generateParens(6);
		for (String s : list) {
			System.out.println(s);
		}
		System.out.println(list.size());		
	}

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

Источник: Карьера программиста

Ещё интересное для вас:
Тест: какой язык программирования вам стоит выбрать для изучения?
Тест: как хорошо вы разбираетесь в Data Science?
Соревнования и бесплатная онлайн-школа для программистов