읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- 자료구조 스택 쉽게 이해하기: push pop으로 문제 풀이 감 잡기스택은 마지막에 넣은 값을 먼저 꺼냅니다. 넣고 꺼낼 때 남는 순서를 확인합니다. 짝과 순서를 만족하는 push/pop을 익힙니다.
- JavaScript 배열: 인덱스 조회와 삽입·삭제 비용배열은 여러 값을 순서대로 담습니다. 인덱스로 값을 읽고 반복문으로 하나씩 살펴봅니다. 인덱스 접근과 배열을 순회할 때의 연산 비용을 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 자료구조 개념과 문제 · 문제
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
올바른 괄호 문제는 문자열의 어떤 성질을 판별해 어떤 값을 반환하나요?
괄호 문자열의 짝과 순서가 올바른지 판별해 YES 또는 NO를 반환하는 문제입니다. 문자열·조건문·스택의 기본을 준비하고, 아래 입력·반환 계약을 확인해 직접 풀어보세요.
본문 근거: MDN push() · pop() 문서
문제

괄호가 입력되면 올바른 괄호이면 “YES”, 올바르지 않으면 “NO”를 출력합니다.
예를 들어 (()())는 괄호의 쌍이 올바르게 위치하는 경우이지만, (()()는 올바른 괄호가 아닙니다.
입력 설명
첫 번째 줄에 괄호 문자열이 입력됩니다. 문자열의 최대 길이는 300입니다.
출력 설명
첫 번째 줄에 YES 또는 NO를 출력합니다.
입력 예제 1
(()())(()
출력 예제 1
NO
직접 풀어보기
먼저 선택한 언어의 입력·반환 계약에 맞춰 작성해 보세요. 힌트 받기와 정답과 비교하기는 코드를 실행하지 않는 AI 분석입니다. 해답·해설은 아래에서 필요할 때 펼칠 수 있습니다.
AI 힌트
코드를 실행한 채점 결과가 아닌 AI의 코드 분석입니다.
해답·해설 보기
function solution(s) {
const stack = [];
for (const ch of s) {
if (ch === "(") stack.push(ch);
else {
if (! stack.length) return "NO";
stack.pop();
}
}
return stack.length === 0 ? "YES" : "NO";
}
solution(s)에 s(문자열)를 전달하고 문자열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
def solution(s):
stack = []
for ch in s:
if ch == "(":
stack.append(ch)
else:
if not stack:
return "NO"
stack.pop()
return "NO" if stack else "YES"
solution(s)에 s(문자열)를 전달하고 문자열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
import java.util.*;
class Solution {
public String solution(String s) {
Deque < Character > stack = new ArrayDeque < >();
for (char ch : s.toCharArray()) {
if (ch == '(') stack.push(ch);
else {
if (stack.isEmpty()) return "NO";
stack.pop();
}
}
return stack.isEmpty() ? "YES" : "NO";
}
}
Solution.solution(String s)에 s(문자열)를 전달하고 문자열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stack>
#include <stdexcept>
std::string solution(const std::string & s) {
std::stack < char > stack;
for (char ch : s) {
if (ch == '(') stack.push(ch);
else {
if (stack.empty()) return "NO";
stack.pop();
}
}
return stack.empty() ? "YES" : "NO";
}
solution(s)에 s(문자열)를 전달하고 문자열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
using System;
using System.Collections.Generic;
public class Solution {
public string Solve(string s) {
var stack = new Stack < char >();
foreach(char ch in s) {
if (ch == '(') stack.Push(ch);
else {
if (stack.Count == 0) return "NO";
stack.Pop();
}
}
return stack.Count == 0 ? "YES" : "NO";
}
}
Solution.Solve(string s)에 s(문자열)를 전달하고 문자열를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
내가 푼 풀이

문자열을 순회하면서 여는 괄호는 배열에 저장하고, 닫는 괄호가 나오면 하나씩 제거하는 방식으로 접근했습니다. 중간에 제거할 대상이 없으면 바로 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)입니다.
이 문제를 푼 뒤 확인하기
닫는 괄호가 먼저 나오는 경우와 여는 괄호가 남는 경우를 따로 추적해 보세요.
본문의 예제와 이 반례를 직접 실행해 예상 출력과 비교하고, 결과가 다르면 입력 조건과 반복 범위를 다시 확인하세요.
풀이 함수를 독립 실행해 확인합니다
선수 지식은 배열·조건문·반복문과 위에서 설명한 자료구조입니다. 해답의 완성 함수 블록 하나를 solution.cjs로 저장하고, 그 아래에 다음 호출을 붙입니다. 앞의 비교용 풀이와 같은 이름의 함수를 함께 붙이지 않습니다. 터미널에서 node solution.cjs로 실행합니다.
console.log(JSON.stringify(solution(...["(()"])));
이 변형 호출의 JSON 출력은 "NO"입니다. 기존 함수 블록의 console.log가 있으면 그 출력 다음 줄에서 확인합니다.
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 해설을 가리고 다시 풀고, 경계 입력을 하나 바꾸어 예상 결과와 실제 결과를 비교하세요. 삽입·제거 전후의 상태를 그리고, 내장 도구를 쓰는 풀이와 직접 구현의 목적을 구분하세요.
다음 추천 · 문제: 코딩테스트 스택 풀이: 괄호문자 제거하기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 58개 · 필수 58개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.