주요 포인트 한눈에 보기
소괄호 문자열이 올바른지 판별할 때 ‘개수’가 아니라 순서와 미매칭 상태를 어떻게 추적하는지 배우는 JavaScript 스택 풀이입니다. 빈 스택에서 닫는 괄호가 나오는 경우와 순회 후 여는 괄호가 남는 경우를 분리하고, 단일 괄호 카운터와 여러 괄호 스택의 선택 기준까지 정리합니다.
문제

괄호가 입력되면 올바른 괄호이면 “YES”, 올바르지 않으면 “NO”를 출력합니다.
예를 들어 (()())는 괄호의 쌍이 올바르게 위치하는 경우이지만, (()()는 올바른 괄호가 아닙니다.
입력 설명
첫 번째 줄에 괄호 문자열이 입력됩니다. 문자열의 최대 길이는 300입니다.
출력 설명
첫 번째 줄에 YES 또는 NO를 출력합니다.
입력 예제 1
(()())(()
출력 예제 1
NO
내가 푼 풀이

문자열을 순회하면서 여는 괄호는 배열에 저장하고, 닫는 괄호가 나오면 하나씩 제거하는 방식으로 접근했습니다. 중간에 제거할 대상이 없으면 바로 NO를 반환하도록 구성했습니다.
function solution(s) {
let stack = [];
let arr = s.split('');
for (let x of arr) {
if (x === '(') stack.push('(');
if (stack.length === 0) return 'NO';
if (x === ')') stack.pop();
}
return stack.length === 0 ? 'YES' : 'NO';
}
해답
아래 코드는 이 문제의 표준적인 정답 풀이입니다. 스택을 사용해 괄호의 순서를 검증하며, 닫는 괄호를 만났을 때 즉시 유효성을 판단하는 구조입니다.
function solution(s) {
let answer = "YES";
let stack = [];
for (let x of s) {
if (x === '(') {
stack.push(x);
} else {
if (stack.length === 0) return "NO";
stack.pop();
}
}
if (stack.length > 0) return "NO";
return answer;
}
1단계 – 여는 괄호 처리
if (x === '(') stack.push(x);
여는 괄호를 만나면 스택에 저장합니다. 이 시점에서는 올바른지 판단하지 않습니다.
2단계 – 닫는 괄호 검증
if (stack.length === 0) return "NO";
닫는 괄호가 나왔는데 스택이 비어 있다면, 앞에 짝이 없다는 의미이므로 즉시 NO를 반환합니다.
3단계 – 짝 제거
stack.pop();
검증을 통과한 경우, 가장 최근에 쌓인 여는 괄호를 제거하여 짝을 맞춥니다.
4단계 – 최종 상태 확인
if (stack.length > 0) return "NO";
모든 문자를 처리한 뒤에도 스택에 여는 괄호가 남아 있다면 올바르지 않은 괄호 문자열입니다.
내 풀이와 정답 풀이의 차이점
① 검증 시점의 차이
내 풀이에서는 반복문 안에서 스택이 비어 있는지를 매번 확인합니다. 이로 인해 여는 괄호를 처리한 직후에도 불필요한 검증이 수행됩니다. 반면 정답 풀이는 닫는 괄호를 만났을 때만 검증을 수행하여 논리가 더 명확합니다.
② 조건 분기의 명확성
정답 풀이는 if (x === '(') 와 else 구조로 역할이 분리되어 있어, 코드만 읽어도 괄호 처리 흐름이 자연스럽게 이해됩니다. 내 풀이는 조건이 분산되어 있어 흐름을 따라가야만 의도를 파악할 수 있습니다.
③ 예외 케이스 안정성
정답 풀이는 닫는 괄호가 먼저 등장하는 경우, 중간에 스택이 비는 경우, 마지막에 여는 괄호가 남는 경우를 각각 자연스럽게 처리합니다. 이는 조건을 단계별로 나눈 구조 덕분입니다.
④ 코딩테스트 관점의 평가
두 풀이 모두 정답 처리는 가능하지만, 정답 풀이는 면접이나 코딩테스트에서 의도와 사고 과정이 명확하게 드러나는 코드라는 점에서 더 좋은 평가를 받을 수 있습니다.
괄호 기반 스택 응용을 더 넓혀보려면 JS 쇠막대기 스택 레이저 절단을 함께 보시면 좋습니다.
연산 우선순위를 다루는 유형은 JS 후위식 연산 스택 계산에서도 확인해두면 유사 패턴 정리가 쉬워집니다.
스택 불변식과 실패 케이스 검증
순회 중 스택에는 아직 짝을 찾지 못한 여는 괄호만 있어야 합니다. 닫는 괄호를 만났을 때 스택이 비어 있으면 바로 실패하고, 끝까지 읽은 뒤 스택이 비어 있어야 성공입니다. 이 두 검사를 분리하면 )(처럼 총개수는 같아도 순서가 잘못된 입력과 (()처럼 여는 괄호가 남은 입력을 모두 잡을 수 있습니다.
console.assert(solution('(()())') === 'YES');
console.assert(solution(')(') === 'NO');
console.assert(solution('(()') === 'NO');
console.assert(solution('') === 'YES');
소괄호 한 종류만 판별할 때는 숫자 카운터만으로도 같은 불변식을 O(1) 추가 공간에 표현할 수 있습니다. ()[]{}처럼 종류가 여러 개라면 최근 여는 괄호의 종류까지 확인해야 하므로 스택이 자연스럽습니다. push()와 pop()의 변경·반환 규칙은 MDN push()와 pop() 문서에서 확인하세요.
function solutionWithCount(s) {
let open = 0;
for (const ch of s) {
if (ch === '(') open += 1;
else if (ch === ')') open -= 1;
else return 'NO';
if (open < 0) return 'NO';
}
return open === 0 ? 'YES' : 'NO';
}
카운터 버전에서도 중간의 open < 0 검사를 생략하면 )(를 잘못 통과시킬 수 있습니다. 마지막 값만 0인지 보는 것과 순회 중 한 번도 음수가 되지 않았는지를 함께 확인해야 ‘모든 접두 구간에서 여는 괄호 수가 닫는 괄호 수 이상’이라는 조건이 성립합니다.
현재 정답 코드는 문제 조건상 입력 문자가 ( 또는 )뿐이라는 전제를 사용해 else를 모두 닫는 괄호로 처리합니다. 범용 검증기라면 다른 문자를 무시할지 오류로 볼지 먼저 정하고 분기해야 합니다. 공백이나 설명 문자가 섞인 문자열을 받는다면 괄호만 필터링할지 전체 입력을 거부할지도 같은 계약에 포함합니다.
정리: 성공 예제 하나보다 ‘중간에 먼저 닫힘’과 ‘마지막에 남음’을 각각 테스트하는 것이 중요합니다. 단일 괄호는 카운터, 여러 괄호는 스택이라는 선택 기준까지 설명하면 풀이 의도가 선명해집니다. 빈 문자열을 올바른 괄호열로 볼지도 문제 정의에 포함하며, 현재 구현은 YES를 반환합니다.
FAQ
Q. 괄호의 개수만 같으면 정답 아닌가요?
괄호 문제의 핵심은 개수가 아니라 순서입니다. 닫는 괄호가 먼저 나오면 올바르지 않습니다.
Q. 스택 없이도 풀 수 있나요?
소괄호 한 종류만 다룬다면 카운터만으로도 간결하게 풀 수 있습니다. 여러 종류의 괄호가 섞여 최근에 열린 종류까지 확인해야 할 때 스택의 장점이 커집니다.
Q. 시간 복잡도는 어떻게 되나요?
문자열을 한 번만 순회하므로 시간 복잡도는 O(n)입니다.
구현 원리로 돌아가기
풀이에 사용한 자료구조를 왜 선택하는지, 코드의 동작과 연산 비용을 함께 복습하세요.
풀이 전에 확인할 순서
- 입력값과 출력값을 한 문장으로 다시 적습니다.
- 반복할 대상과 비교·저장할 값을 정합니다.
- 필요한 자료구조와 시간복잡도를 예상합니다.
- 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.
테스트 확인
- 가능한 가장 작은 입력
- 같은 값이나 문자가 반복되는 입력
- 정답이 처음 또는 마지막 위치에서 결정되는 입력
- 입력 제한에 가까운 경우의 실행 시간
확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.
이 글이 도움이 되었나요?
코딩테스트 JavaScript 학습 순서
필수 49개 · 전체 49개
읽음 기록 관리
전체 과정 목차 (49개)
- 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
- 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
- 필수 학습 · 삼각형 판별하기 JavaScript 풀이
- 필수 학습 · 연필 개수 JavaScript 풀이
- 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
- 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
- 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
- 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
- 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
- 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
- 필수 학습 · 대문자 찾기 JavaScript 풀이
- 필수 학습 · 대문자로 통일 JavaScript 풀이
- 필수 학습 · 대소문자 변환 JavaScript 풀이
- 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
- 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기 현재 글
- 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
- 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
- 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
- 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
- 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
- 필수 학습 · 가장 긴 문자열 JavaScript 풀이
- 필수 학습 · 가운데 문자 출력 JavaScript 풀이
- 필수 학습 · 중복문자제거 JavaScript 풀이
- 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
- 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
- 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
- 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
- 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
- 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
- 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
- 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
- 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
- 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
- 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
- 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
- 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
- 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
- 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
- 필수 학습 · 중복단어제거 JavaScript 풀이
- 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
- 필수 학습 · 큰 수 출력하기 JavaScript 풀이
- 필수 학습 · 보이는 학생 JavaScript 풀이
- 필수 학습 · 가위바위보 JavaScript 풀이
- 필수 학습 · 점수계산 JavaScript 풀이
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.