읽기 전에 알아두면 좋아요
아래 개념이 익숙하면 본문을 바로 읽으세요. 낯선 개념만 링크에서 복습하면 됩니다.
- 자료구조 스택 쉽게 이해하기: push pop으로 문제 풀이 감 잡기스택은 마지막에 넣은 값을 먼저 꺼냅니다. 넣고 꺼낼 때 남는 순서를 확인합니다. 피연산자 두 개를 꺼내는 순서와 연산 결과를 다시 넣는 동작을 확인합니다.
- JavaScript 배열: 인덱스 조회와 삽입·삭제 비용배열은 여러 값을 순서대로 담습니다. 인덱스로 값을 읽고 반복문으로 하나씩 살펴봅니다. 인덱스 접근과 배열을 순회할 때의 연산 비용을 확인합니다.
개념과 예제는 실행하지 않아도 읽을 수 있습니다. 내 컴퓨터·터미널에서 직접 실행하는 과정은 선택 실습입니다.
과정 위치와 선택 복습 안내
과정 위치: 자료구조 개념과 문제 · 문제
과정 위치는 이 글이 속한 묶음입니다. 이 단계의 모든 개념을 먼저 읽을 필요는 없습니다. 위에 연결한 개념 중 낯선 내용만 복습하세요. 문제의 조건·상태·갱신 순서는 익숙한 언어로 설명하면 됩니다.
JavaScript로 문법·실행 복습하기 (선택)
개념 글의 코드는 JavaScript 예시입니다. 문제 화면에서는 JavaScript·Python·Java·C++·C# 중 익숙한 언어의 함수를 작성하세요. 본문의 Node.js 입출력 예시는 로컬 실행을 위한 별도 안내입니다.
후위식 연산 문제에서는 어떤 형태의 수식을 입력받아 무엇을 반환하나요?
후위식으로 주어진 수식을 계산해 결과를 반환하는 문제입니다. 문자열·연산자·스택의 기본을 준비하고, 아래 입력·반환 계약을 확인해 직접 풀어보세요.
적용 조건·범위: 후위식을 왼쪽부터 읽으며 피연산자를 스택에 쌓고 연산자를 만날 때 최근 두 값을 계산하는 JavaScript 풀이입니다. 뺄셈·나눗셈의 피연산자 순서, 전역 isNaN()의 강제 형변환, 한 자리 숫자라는 문제 전제와 다자리 토큰으로 확장할 때의 검증 기준을 구분합니다.
본문 근거: MDN 전역 isNaN() · Number.isNaN() 문서
문제

후위연산식이 주어지면 연산한 결과를 출력하는 프로그램을 작성하세요.
만약 3*(5+2)-9를 후위연산식으로 표현하면 352+*9-로 표현되며, 그 결과는 12입니다.
입력설명
첫 줄에 후위연산식이 주어집니다.
연산식의 길이는 50을 넘지 않습니다.
식은 1~9의 숫자와 +, -, *, / 연산자로만 이루어집니다.
출력설명
연산한 결과를 출력합니다.
입력예제 1
352+*9-
출력예제 1
12
직접 풀어보기
먼저 선택한 언어의 입력·반환 계약에 맞춰 작성해 보세요. 힌트 받기와 정답과 비교하기는 코드를 실행하지 않는 AI 분석입니다. 해답·해설은 아래에서 필요할 때 펼칠 수 있습니다.
AI 힌트
코드를 실행한 채점 결과가 아닌 AI의 코드 분석입니다.
해답·해설 보기
function solution(s) {
const stack = [];
for (const ch of s) {
if (ch >= "1" && ch <= "9") stack.push(Number(ch));
else {
const right = stack.pop(), left = stack.pop();
if (ch === "+") stack.push(left + right);
else if (ch === "-") stack.push(left - right);
else if (ch === "*") stack.push(left * right);
else stack.push(left / right);
}
}
return stack[0];
}
solution(s)에 s(문자열)를 전달하고 수치를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
def solution(s):
import math
stack = []
for ch in s:
if "1" <= ch <= "9":
stack.append(float(ch))
else:
right, left = stack.pop(), stack.pop()
if ch == "+":
stack.append(left + right)
elif ch == "-":
stack.append(left - right)
elif ch == "*":
stack.append(left * right)
else:
if right == 0.0:
if left == 0.0 or math.isnan(left):
stack.append(float("nan"))
else:
sign = math.copysign(1.0, left) * math.copysign(1.0, right)
stack.append(math.copysign(float("inf"), sign))
else:
stack.append(left / right)
return stack[0]
solution(s)에 s(문자열)를 전달하고 수치를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
import java.util.*;
class Solution {
public double solution(String s) {
Deque < Double > stack = new ArrayDeque < >();
for (char ch : s.toCharArray()) {
if (ch >= '1' && ch <= '9') stack.push((double)(ch - '0'));
else {
double right = stack.pop(), left = stack.pop();
if (ch == '+') stack.push(left + right);
else if (ch == '-') stack.push(left - right);
else if (ch == '*') stack.push(left * right);
else stack.push(left / right);
}
}
return stack.pop();
}
}
Solution.solution(String s)에 s(문자열)를 전달하고 수치를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <stack>
#include <stdexcept>
double solution(const std::string & s) {
std::stack < double > stack;
for (char ch : s) {
if (ch >= '1' && ch <= '9') stack.push(ch - '0');
else {
double right = stack.top();
stack.pop();
double left = stack.top();
stack.pop();
if (ch == '+') stack.push(left + right);
else if (ch == '-') stack.push(left - right);
else if (ch == '*') stack.push(left * right);
else stack.push(left / right);
}
}
return stack.top();
}
solution(s)에 s(문자열)를 전달하고 수치를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
using System;
using System.Collections.Generic;
public class Solution {
public double Solve(string s) {
var stack = new Stack < double >();
foreach(char ch in s) {
if (ch >= '1' && ch <= '9') stack.Push(ch - '0');
else {
double right = stack.Pop(), left = stack.Pop();
if (ch == '+') stack.Push(left + right);
else if (ch == '-') stack.Push(left - right);
else if (ch == '*') stack.Push(left * right);
else stack.Push(left / right);
}
}
return stack.Pop();
}
}
Solution.Solve(string s)에 s(문자열)를 전달하고 수치를 반환합니다. 아래 문제의 유효 입력 조건을 따릅니다. 표준 입력과 출력 코드는 작성하지 않습니다.
내가 푼 풀이

이 첫 풀이는 배열을 스택으로 사용하지만 덧셈·뺄셈·곱셈만 구현했고 나눗셈 분기가 없습니다. 입력 82/를 처리하려면 아래 정답 풀이를 사용해야 합니다. 먼저 두 구현을 비교하며 오른쪽 피연산자를 먼저 꺼내는 순서를 확인합니다.
핵심 아이디어는 다음과 같습니다. 숫자는 스택에 그대로 저장하고, 연산자를 만나면 스택의 마지막 두 값을 꺼내 계산한 뒤 다시 스택에 넣습니다. 이 방식은 후위식 계산의 기본 원리를 그대로 따르고 있습니다.
function solution(s) {
let arr = [];
for (let x of s) {
if (!isNaN(x)) {
arr.push(x);
continue;
}
let num1 = Number(arr.at(-2));
let num2 = Number(arr.at(-1));
arr.pop();
arr.pop();
if (x === '+') arr.push(num1 + num2);
else if (x === '-') arr.push(num1 - num2);
else if (x === '*') arr.push(num1 * num2);
}
return arr[0];
}
이 코드의 흐름을 단계별로 정리하면 다음과 같습니다.
1. 문자열을 순회하면서 각 문자를 확인합니다.
2. 숫자인 경우 바로 배열에 push 하여 스택에 저장합니다.
3. 연산자를 만나면 스택의 마지막 두 값을 꺼내 연산합니다.
4. 계산 결과를 다시 스택에 push 합니다.
다만 이 풀이에는 몇 가지 아쉬운 점도 있습니다. 숫자를 문자열 상태로 저장했다가 연산 직전에 Number로 변환해야 하며, at(-1), at(-2) 사용은 익숙하지 않은 환경에서는 가독성을 떨어뜨릴 수 있습니다.
정답 풀이
정답 풀이는 후위식(Postfix) 연산 문제에서 요구하는 사고 흐름을 가장 정석적으로 구현한 방식입니다. 이 문제의 핵심은 연산자 우선순위를 고려하는 것이 아니라, 등장 순서대로 계산하되 스택을 이용해 필요한 순간에 두 값을 꺼내는 것입니다.
후위식 계산의 기본 규칙은 매우 단순합니다.
1. 숫자를 만나면 스택에 push 합니다.
2. 연산자를 만나면 스택에서 두 개의 숫자를 pop 합니다.
3. 먼저 pop 된 값은 오른쪽 피연산자, 그 다음 값은 왼쪽 피연산자로 사용합니다.
4. 계산 결과를 다시 스택에 push 합니다.
function solution(s) {
let stack = [];
for (let x of s) {
if (!isNaN(x)) stack.push(Number(x));
else {
let rt = stack.pop();
let lt = stack.pop();
if (x === '+') stack.push(lt + rt);
else if (x === '-') stack.push(lt - rt);
else if (x === '*') stack.push(lt * rt);
else if (x === '/') stack.push(lt / rt);
}
}
return stack[0];
}
이 코드를 실제 입력 예제 352+*9- 기준으로 단계별로 따라가 보겠습니다.
① 문자 ‘3’ → 숫자이므로 스택에 push → [3]
② 문자 ‘5’ → 숫자이므로 push → [3, 5]
③ 문자 ‘2’ → 숫자이므로 push → [3, 5, 2]
④ 문자 ‘+’ → 연산자이므로 pop 두 번 수행
– rt = 2, lt = 5 → 5 + 2 = 7 → 결과 push → [3, 7]
⑤ 문자 ‘*’ → 연산자
– rt = 7, lt = 3 → 3 * 7 = 21 → 결과 push → [21]
⑥ 문자 ‘9’ → 숫자이므로 push → [21, 9]
⑦ 문자 ‘-‘ → 연산자
– rt = 9, lt = 21 → 21 – 9 = 12 → 결과 push → [12]
모든 문자를 처리한 뒤 스택에는 하나의 값만 남게 되며, 이 값이 바로 최종 계산 결과입니다.
이 풀이의 가장 중요한 포인트는 연산자 처리 시 피연산자 순서입니다. 스택은 LIFO 구조이기 때문에 먼저 pop 되는 값이 항상 오른쪽 피연산자가 됩니다. 이 순서를 지키지 않으면 뺄셈이나 나눗셈에서 잘못된 결과가 나옵니다.
정답 풀이는 숫자를 스택에 넣는 순간부터 Number 타입으로 관리하기 때문에, 연산 과정에서 불필요한 타입 변환이 발생하지 않고 코드의 의도가 명확하게 드러난다는 장점이 있습니다.
괄호의 유효성 판단까지 함께 정리하려면 JavaScript 올바른 괄호 스택 검증을 함께 확인하세요.
스택 상태 전환이 반복되는 구조는 JS 크레인 인형뽑기 스택 풀이에서 추가로 정리되어 있습니다.
isNaN 사용 이유와 주의점
이 문제에서 isNaN은 “이 문자가 숫자인지 아닌지”를 구분하기 위해 사용됩니다. 후위식 문자열은 모두 문자(String)이기 때문에, 단순히 숫자처럼 보여도 실제 타입은 문자열입니다.
예를 들어 후위식 352+*9-를 하나씩 읽으면, 프로그램 입장에서는 모두 아래처럼 문자입니다.
'3', '5', '2', '+', '*', '9', '-'
따라서 매 순간 “이 문자가 숫자인가, 연산자인가”를 구분해야 하며, 이때 가장 간단하게 사용할 수 있는 방법이 isNaN입니다.
isNaN은 값이 숫자로 바뀔 수 없는 경우 true를 반환합니다.
isNaN('3'); // false → 숫자
isNaN('9'); // false → 숫자
isNaN('+'); // true → 숫자가 아님
isNaN('*'); // true → 숫자가 아님
그래서 정답 코드에서 사용하는 조건문 !isNaN(x)은 다음과 같은 의미가 됩니다.
“x가 숫자라면”
숫자인 경우에는 스택에 그대로 push 하고, 숫자가 아닌 경우에는 연산자로 판단해 계산 로직을 수행합니다.
이제 isNaN에서 가장 헷갈리는 부분을 아주 쉽게 설명해 보겠습니다.
isNaN은 값을 바로 검사하지 않고, 먼저 숫자로 한 번 바꿔본 뒤 판단합니다. 이 과정을 “타입 변환”이라고 부릅니다.
예를 들어 아래 값들을 보면 모두 문자열이지만, 자바스크립트는 먼저 숫자로 바꿔보려고 시도합니다.
Number('3'); // 3
Number(''); // 0
Number(' '); // 0
Number('+'); // NaN
빈 문자열('')이나 공백 문자열(' ')은 숫자로 바꾸면 0이 됩니다. 그래서 isNaN('')이나 isNaN(' ')은 false를 반환합니다.
isNaN(''); // false
isNaN(' '); // false
isNaN('+'); // true
이 결과를 말로 풀면 다음과 같습니다.
– 빈 문자열은 숫자 0으로 바꿀 수 있다 → 숫자로 판단
– 공백 문자열도 숫자 0으로 바꿀 수 있다 → 숫자로 판단
– ‘+’ 같은 문자는 숫자로 바꿀 수 없다 → 숫자가 아님
이 때문에 일반적인 문자열 검사 상황에서는 isNaN을 그대로 사용하면 의도하지 않은 결과가 나올 수 있습니다.
반면 Number.isNaN은 다르게 동작합니다. 이 함수는 타입 변환을 하지 않고, 값이 정말로 NaN인지 여부만 검사합니다.
Number.isNaN(''); // false
Number.isNaN(' '); // false
Number.isNaN(NaN); // true
즉, Number.isNaN은 “이 값이 NaN 그 자체인가?”만 확인하고, 문자열을 숫자로 바꾸려는 시도를 하지 않습니다.
하지만 이 문제에서는 입력값이 1~9 숫자와 연산자만으로 제한되어 있기 때문에, 빈 문자열이나 공백이 들어올 수 없습니다.
그래서 이 문제 범위 안에서는 isNaN을 사용해도 안전하며, 오히려 “숫자인지 아닌지”를 빠르게 구분하기에 가장 이해하기 쉬운 선택입니다.
토큰 전제·피연산자 순서·완료 조건
유효한 후위식의 길이를 N이라 하면 정답 함수는 각 문자를 한 번 읽고 상수 횟수의 산술·push·pop을 수행하므로 통상적인 동적 배열 모델에서 O(N) 상환 시간입니다. 아직 계산하지 않은 피연산자가 스택에 쌓여 최악 O(N) 추가 공간을 사용합니다. 입력 문자열 저장은 O(N), 반환 숫자 하나는 O(1)이므로 입력을 포함한 전체 저장 공간도 O(N)입니다. 한 자리 숫자와 JavaScript Number 연산을 사용하는 현재 코드의 분석이며, 앞의 나눗셈이 빠진 첫 풀이의 정답성을 보완한다는 뜻은 아닙니다.
연산자를 만났을 때 먼저 꺼낸 값은 오른쪽 피연산자, 다음 값은 왼쪽 피연산자입니다. 덧셈과 곱셈에서는 순서를 바꿔도 결과가 같지만 lt - rt와 lt / rt에서는 순서가 결과를 바꾸므로 변수 이름으로 역할을 드러내야 합니다. 연산 전에 스택에 값이 두 개 이상 있는지도 확인해야 합니다.
전역 isNaN()은 값을 숫자로 강제 변환한 뒤 판정하므로 빈 문자열과 공백에서 예상 밖의 결과를 낼 수 있습니다. 현재 문제처럼 공백 없는 한 자리 숫자와 정해진 연산자만 들어온다는 조건에서는 사용할 수 있지만, 일반 파서라면 토큰을 먼저 분리하고 허용 형식을 명시적으로 검사해야 합니다. 차이는 MDN 전역 isNaN()과 Number.isNaN() 문서에서 확인하세요.
console.assert(solution('352+*9-') === 12);
console.assert(solution('82/') === 4);
// 다자리 수를 허용하려면 '12 3 +'처럼 토큰 경계를 별도로 정의합니다.
모든 토큰을 처리한 뒤 스택에는 결과 하나만 남아야 합니다. 값이 더 남으면 연산자가 부족하고, 연산 중 두 값을 꺼낼 수 없으면 피연산자가 부족한 잘못된 식입니다. 0으로 나누는 경우 Infinity 또는 NaN을 허용할지도 호출 규칙에서 정해야 합니다.
정리: 한 자리 숫자 문제의 간단한 판별과 범용 토큰 파서를 섞지 않는 것이 중요합니다. 오른쪽 값을 먼저 꺼낸다는 규칙과 ‘마지막 스택 크기 1’을 테스트로 고정하면 뺄셈·나눗셈 오류를 크게 줄일 수 있습니다.
FAQ
Q. 왜 후위식 계산에는 스택을 사용하나요?
연산자를 만났을 때 가장 최근에 등장한 두 피연산자를 사용해야 하므로, LIFO 구조인 스택이 가장 적합합니다.
Q. 숫자가 두 자리 이상이면 어떻게 되나요?
이 문제에서는 1~9의 한 자리 숫자만 주어지므로 추가 처리가 필요 없지만, 일반화하려면 문자열 파싱 로직이 필요합니다.
Q. 내가 푼 풀이와 정답 풀이 중 어떤 방식이 더 좋은가요?
첫 풀이에는 / 분기가 없어 나눗셈 입력 82/를 처리하지 못합니다. 아래 정답 풀이를 사용하면 4를 반환합니다. 숫자 형식뿐 아니라 네 연산자를 모두 구현했는지 확인해야 합니다.
이 문제를 푼 뒤 확인하기
뺄셈·나눗셈에서 꺼낸 두 피연산자의 순서를 바꿨을 때 결과가 어떻게 달라지는지 적어 보세요.
본문의 예제와 이 반례를 직접 실행해 예상 출력과 비교하고, 결과가 다르면 입력 조건과 반복 범위를 다시 확인하세요.
풀이 함수를 독립 실행해 확인합니다
선수 지식은 배열·조건문·반복문과 위에서 설명한 자료구조입니다. 정답 풀이의 완성 함수 블록 하나를 solution.cjs로 저장하고, 그 아래에 다음 호출을 붙입니다. 앞의 비교용 풀이와 같은 이름의 함수를 함께 붙이지 않습니다. 터미널에서 node solution.cjs로 실행합니다.
console.log(JSON.stringify(solution(...["28-"])));
이 변형 호출의 JSON 출력은 -6입니다. 기존 함수 블록의 console.log가 있으면 그 출력 다음 줄에서 확인합니다.
앞의 첫 풀이에는 나눗셈 분기가 없습니다. 정답 풀이의 82/는 4, 28-는 -6입니다. 현재 완성 함수는 유효한 식을 가정하며 피연산자 부족·최종스택크기·0 나누기 검사를 구현한 범용 파서는 아닙니다.
이 글이 도움이 되었나요?
이어서 공부하기
체크포인트: 해설을 가리고 다시 풀고, 경계 입력을 하나 바꾸어 예상 결과와 실제 결과를 비교하세요. 삽입·제거 전후의 상태를 그리고, 내장 도구를 쓰는 풀이와 직접 구현의 목적을 구분하세요.
다음 추천 · 문제: 코딩테스트 스택 풀이: 쇠막대기 레이저 절단 개수 세기
권장 순서는 학습 가이드와 같습니다. 다른 순서는 접힌 참고 목록에서 확인할 수 있습니다. 읽음 표시는 숙달이나 채점 결과가 아닙니다.
원래 과정 목록 순서 (선택)
읽음 기록
읽음 — / 전체 58개 · 필수 58개
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.