Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.
Example 1:
Input: n = 3 Output: ["((()))","(()())","(())()","()(())","()()()"]
Example 2:
Input: n = 1 Output: ["()"]
Constraints:
1 <= n <= 8가장 직관적으로 이해하자면, 백트래킹은 미로 찾기를 할 때 "이 길이 아닌가 보네? 다시 뒤로 돌아가서 다른 길로 가야지" 하는 행동을 알고리즘으로 만든 것입니다.
핵심 개념: 모든 가능한 경우의 수를 탐색하되, 어떤 조건을 만족하지 않는 경로라면 더 이상 깊이 들어가지 않고(가지치기), 직전 상태로 되돌아와서(Back) 다른 경로를 탐색하는(Tracking) 알고리즘 기법입니다.
이 문제에서 n=3일 때, 유효한 괄호 쌍(예: ((())), ()()())을 만들어야 합니다. 컴퓨터가 빈 문자열 ""에서 시작해서 괄호를 하나씩 붙여나가는 과정을 미로 찾기라고 생각해볼 수 있습니다.
여는 괄호 (와 닫는 괄호 )를 무조건 6개 채울 때까지 모든 조합을 만듭니다. ((((((, )))))), ()()(), )))( (( 등을 다 만든 뒤, 마지막에 가서야 "어? )))( ((는 올바른 괄호가 아니네?" 하고 버립니다. 엄청난 시간 낭비가 발생합니다.
괄호를 붙여나가는 와중에 "어차피 끝까지 가봤자 정답이 될 수 없는 상태"를 미리 감지하고 돌아섭니다. (가지치기)
)를 고른 경우:(로 시작해야 합니다. 첫 글자가 )가 되는 순간, 뒤에 뭐가 오든 올바른 괄호 쌍이 될 수 없습니다. 따라서 컴퓨터는 뒤를 더 보지도 않고 "이 길은 망했다" 하고 즉시 탐색을 종료한 뒤, 다시 첫 글자로 (를 고르는 갈림길로 되돌아옵니다(Backtrack).
)의 개수가 여는 괄호 (의 개수를 넘어서는 경우:(())가 되었다면? 이미 닫는 괄호가 더 많아서 유효한 괄호가 될 가능성이 0%입니다. 이 상태에서도 즉시 탐색을 중단하고 직전 상태로 되돌아갑니다.
재귀를 활용한 깔끔한 백트래킹 구조입니다. 전역 변수 대신 매개변수로 상태를 넘겨주어 각 재귀 호출이 독립적으로 상태를 관리하도록 합니다.
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
// 시작 상태: 빈 문자열 "", 여는 괄호 0개, 닫는 괄호 0개
backtrack(result, "", 0, 0, n);
return result;
}
private void backtrack(List<String> result, String currentString, int openCount, int closeCount, int max) {
// [1] 탈출 조건: 원하는 길이(2 * n)에 도달하면 정답 리스트에 추가하고 종료
if (currentString.length() == max * 2) {
result.add(currentString);
return;
}
// [2] 여는 괄호 추가 (가지치기 조건 포함)
if (openCount < max) {
backtrack(result, currentString + "(", openCount + 1, closeCount, max);
}
// [3] 닫는 괄호 추가 (가지치기 조건 포함)
if (closeCount < openCount) {
backtrack(result, currentString + ")", openCount, closeCount + 1, max);
}
}
}
if (openCount < max)와 if (closeCount < openCount) 두 조건문이 핵심적인 가지치기 역할을 합니다. 올바르지 않은 괄호 쌍이 될 경로는 아예 재귀 함수를 호출조차 하지 않고 차단합니다.currentString + "(" 형태로 새로운 문자열을 생성하여 인자로 넘기면, 해당 재귀 호출이 끝나고 돌아왔을 때 기존 currentString 변수의 값은 변하지 않습니다. 따라서 이전 갈림길로 돌아왔을 때(Backtrack) 별도로 글자를 지워주는 코드를 작성할 필요가 없어 실수를 줄여줍니다.openCount, closeCount 같은 명확한 이름을 사용하여 코드의 가독성을 높였습니다.