fos-blog/study
01 / 홈02 / 카테고리
01 / 홈02 / 카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 커머스/F&B 도메인 설계 면접 대비 — 슬롯 경험을 주문·결제·쿠폰·매장 상태 설계로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • investing 페이지로 이동
    • stock-notes 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] CJ푸드빌 디지털 채널 Back-end 개발자 직무 분석
    • [초안] CJ푸드빌 디지털 채널 Back-end 면접 답변집 — 슬롯 도메인 경험을 커머스/F&B 설계로 번역하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/algorithm/[초안] 라이브 코딩 대비 Monotonic…
algorithm

[초안] 라이브 코딩 대비 Monotonic Stack 패턴 정리 (Java)

HackerRank, 코드시그널, 또는 화상 공유 기반 라이브 코딩 면접에서 시간 제한 안에 풀어야 하는 배열 문제 중 상당수가 "각 원소에 대해, 그것보다 큰(혹은 작은) 다음 원소를 찾으라" 형태로 환원된다. 단순 이중 반복문으로 풀면 O(N²)이 되고, N이 10⁵만 되어도 타임아웃이 난다. 이 때 Monotonic Stack(단조 스택)을 알고 있느...

2026.04.27·13 min read·15 views

왜 이 주제가 라이브 코딩에서 중요한가

HackerRank, 코드시그널, 또는 화상 공유 기반 라이브 코딩 면접에서 시간 제한 안에 풀어야 하는 배열 문제 중 상당수가 "각 원소에 대해, 그것보다 큰(혹은 작은) 다음 원소를 찾으라" 형태로 환원된다. 단순 이중 반복문으로 풀면 O(N²)이 되고, N이 10⁵만 되어도 타임아웃이 난다. 이 때 Monotonic Stack(단조 스택)을 알고 있느냐에 따라 30분 안에 풀고 설명까지 마치느냐, 아니면 brute force로 시작했다가 시간에 쫓겨 디버깅하느냐가 갈린다.

시니어 백엔드 면접에서 자료구조 자체를 묻는 비중은 줄지만, 라이브 코딩은 여전히 "이 사람이 자료구조를 도구로 쓸 수 있는가" 를 검증한다. Monotonic Stack은 외워서 풀이를 적는 알고리즘이 아니라 "스택의 단조성을 유지한다"는 한 줄짜리 발상으로 여러 변형 문제를 통일적으로 푸는 패턴이라, 면접관이 follow-up을 던지기에 좋다. 따라서 답을 적는 것보다 "왜 이 자료구조가 이 문제에 맞는지" 설명할 수 있어야 한다.

핵심 개념: 스택의 단조성

Monotonic Stack은 이름 그대로 스택 안의 원소가 아래에서 위로 (또는 위에서 아래로) 단조 증가하거나 단조 감소하도록 유지되는 스택이다. 일반 스택과 자료구조는 동일하지만, push 직전에 단조성이 깨질 원소들을 모두 pop 해버리는 규칙이 추가된다.

스택을 단조 감소(top이 작은 값, bottom이 큰 값)로 유지한다고 하자. 새 원소 x를 push 하기 전에, 스택 top이 x보다 작거나 같으면 pop 한다. pop 되는 원소 입장에서 보면 자기보다 큰 첫 번째 원소가 바로 x다. 이렇게 pop 하는 순간 그 원소의 답이 x로 확정된다.

이 발상이 핵심이고, 나머지는 전부 변형이다.

문제 변형스택 단조성pop 조건
다음으로 큰 원소 (Next Greater)감소top ≤ 현재값 일 때 pop
다음으로 작은 원소 (Next Smaller)증가top ≥ 현재값 일 때 pop
이전으로 큰 원소 (Previous Greater)감소, 왼→오 진행같음
히스토그램 최대 직사각형증가top ≥ 현재값 일 때 pop, pop 시 면적 계산
일일 온도 / 주가 스팬감소top ≤ 현재값 일 때 pop

인덱스를 저장한다, 값을 저장하지 않는다

라이브 코딩에서 가장 흔한 첫 실수가 스택에 값(value)을 그대로 넣는 것이다. 단순한 "다음 큰 값" 정도는 값으로도 풀리지만, 거의 모든 변형 문제에서 다음 중 하나가 필요하다.

  • pop 되는 시점에 그 원소의 인덱스에 답을 기록해야 한다 (배열을 결과로 채우는 패턴).
  • pop 되는 시점에 거리(인덱스 차)를 계산해야 한다 (히스토그램 너비, 일일 온도의 며칠 후).

따라서 라이브 코딩에서 손이 멈출 일을 줄이려면 처음부터 Deque<Integer>에 인덱스를 넣는 것을 기본 패턴으로 굳혀라. 값이 필요하면 arr[stack.peek()]으로 접근하면 된다. 이 한 가지 습관이 N+1 문제처럼 잔버그의 절반을 막는다.

java
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
    while (!stack.isEmpty() && arr[stack.peek()] < arr[i]) {
        int popped = stack.pop();
        result[popped] = arr[i];        // 또는 i - popped 등
    }
    stack.push(i);
}

값 저장 패턴은 "Next Greater Element I"처럼 답을 인덱스 매핑할 필요가 없는 매우 좁은 경우에만 쓰고, 면접에서는 인덱스 저장 한 가지로 통일하는 것이 인지 부담이 적다.

while-pop 조건의 등호 처리

두 번째로 흔한 버그는 pop 조건의 부등호다. <인지 <=인지에 따라 답이 달라진다.

  • "엄격히 큰 다음 원소"를 찾는다 → arr[stack.peek()] < arr[i] 일 때 pop. 같은 값을 만나도 pop 하지 않으므로, 같은 값은 자기 답이 될 수 없다.
  • "같거나 큰 다음 원소"를 찾는다 → arr[stack.peek()] <= arr[i] 일 때 pop.

히스토그램 최대 직사각형에서 같은 높이 막대들이 연속할 때 <=로 처리하느냐 <로 처리하느냐에 따라 너비 계산이 달라지므로, "엄격히 vs 같거나"의 의미를 문제 진술에서 먼저 확인하고 화이트보드에 한 줄 주석으로 박아두는 것이 안전하다.

면접관이 "왜 <=을 썼나요?" 라고 물으면, "같은 값이 들어왔을 때 기존 원소를 pop 시켜서 새 원소가 그 자리의 영역을 흡수하도록 만들기 위해서"처럼 의미 단위로 답할 수 있어야 한다.

배열을 한 번만 순회해도 되는 이유

면접관이 가장 자주 던지는 follow-up 중 하나가 "왜 O(N)인가? while 안에 또 반복문이 있는데?" 다. 답은 각 인덱스가 스택에 정확히 한 번 push 되고, 최대 한 번 pop 된다는 amortized 분석이다.

내부 while 루프가 한 번에 여러 개를 pop 하더라도, 그 pop 된 원소들은 두 번 다시 스택에 들어가지 않는다. 따라서 전체 push/pop 횟수의 합은 2N을 넘지 않는다. 외부 for 루프 N + 내부 pop 총합 ≤ N = 총 2N → O(N).

이 답을 라이브 코딩에서 시간복잡도를 적기 전에 한 줄 주석으로 적어두는 습관을 들이면, 면접관이 묻기 전에 선제적으로 짚어주는 효과가 있다.

java
// 각 원소는 최대 1번 push, 1번 pop → amortized O(N)

끝까지 남은 원소 처리

루프가 끝났는데 스택에 아직 원소가 남아 있다면, 그것들은 자기보다 큰(혹은 작은) 다음 원소가 영원히 등장하지 않은 원소들이다. 문제에 따라 이 원소들의 답은 -1, 0, 또는 자기 자신이 될 수도 있다.

흔한 처리 방법 두 가지가 있다.

  1. 결과 배열을 미리 기본값(예: -1)로 초기화하고, pop 될 때만 덮어쓴다. 루프 종료 후 별도 처리가 필요 없다.
  2. 루프 종료 후 스택을 비우면서 명시적으로 기본값을 채운다.

라이브 코딩에서는 1번이 코드 라인이 짧아 디버깅 표면적이 작다. 면접관이 코드를 따라 읽기에도 더 자연스럽다.

또 다른 트릭은 배열 끝에 가상의 "무한대" 원소를 추가했다고 가정하고, 마지막에 한 번 더 가상 원소로 비우는 패턴이다. 히스토그램 문제에서 자주 쓰이며, "원형 배열의 다음 큰 원소"처럼 wrap-around 가 있는 변형에서는 인덱스를 2*n 까지 돌리고 i % n으로 접근하는 변종이 등장한다.

자주 나오는 구현 버그

  1. Stack<Integer> 사용 — Java의 레거시 Stack 클래스는 Vector 상속이라 동기화 오버헤드가 있다. ArrayDeque를 쓰자. 면접관이 보고 "왜 ArrayDeque?"라고 물으면 점수 포인트가 된다.
  2. stack.peek()을 두 번 호출 — peek 후 pop 하기 전에 값을 변수에 담아두지 않으면 가독성이 떨어진다. 라이브 코딩에서는 int top = stack.peek() 한 줄로 의도를 드러내는 편이 좋다.
  3. isEmpty 체크 누락 — pop 조건에서 !stack.isEmpty() && ... 를 빼먹어 NoSuchElementException. 화이트보드에서 short-circuit 평가 순서가 중요하다는 것을 알고 있어야 한다.
  4. 인덱스 vs 값 혼용 — 스택에 인덱스를 넣어놓고 비교할 때 stack.peek() < arr[i] 처럼 인덱스와 값을 비교하는 실수. 반드시 arr[stack.peek()] < arr[i].
  5. 결과 배열 크기 N+1로 잡기 — 입력 길이 N에 결과 길이 N인데 무심코 N+1로 잡거나 0-기반/1-기반을 혼용. 라이브 코딩에서 1분을 잃기 쉽다.
  6. 스택 비우는 순서 거꾸로 — 루프 종료 후 스택을 비우면서 결과를 채울 때, pop 순서가 인덱스 역순임을 잊고 잘못된 거리 계산.

라이브 면접에서 접근을 설명하는 순서

면접관이 보고 있는 화면에서 코드를 곧장 치기 전에, 30~60초 동안 다음 순서로 말로 풀이를 설명하라. 이 단계에서 면접관이 힌트를 주거나 잘못된 가정을 교정해 준다.

  1. 문제를 한 줄로 재진술한다. "각 인덱스 i에 대해, i보다 오른쪽에서 처음으로 arr[i]보다 큰 값을 찾는 문제로 이해했다."
  2. brute force부터 짚는다. "이중 반복문이면 O(N²)이고, N=10⁵에서는 1초 안에 안 들어올 것이다."
  3. 관찰을 한다. "왼쪽에서 오른쪽으로 진행하면서, 아직 답을 못 찾은 인덱스들을 모아두고, 새 값이 들어올 때마다 그것들 중 답이 결정되는 것들을 한꺼번에 처리할 수 있다."
  4. 자료구조를 호명한다. "이건 단조 감소 스택 패턴이다. 스택에 인덱스를 저장하고, 새 원소 arr[i]가 들어올 때 스택 top의 값이 arr[i]보다 작으면 pop 하면서 답을 i로 기록한다."
  5. 복잡도를 미리 말한다. "각 인덱스가 최대 1번 push, 1번 pop 되므로 amortized O(N), 공간 O(N)."
  6. 엣지 케이스를 짚는다. "끝까지 답을 못 찾은 인덱스는 결과 배열을 -1로 초기화해두고 처리한다. 같은 값에 대해서는 strict / non-strict 가정을 확인하겠다."
  7. 그 다음에 코딩한다.

이 7단계는 외워서 토하는 게 아니라 사고 과정을 말로 옮기는 훈련이다. 시니어 라이브 코딩에서는 답을 빨리 적는 것보다 의사결정 흐름을 보여주는 것이 더 높이 평가된다.

로컬 연습 환경

라이브 코딩 대비는 IDE 자동완성에 의존하지 않는 환경에서 연습해야 한다. 다음 두 가지 중 하나를 추천한다.

  • 단일 파일 java: Solution.java 한 파일에 public class Solution { public static void main ... } 구조를 두고, javac Solution.java && java Solution. JDK 17+ 권장.
  • HackerRank 스타일: Main 클래스 안에 Scanner로 표준 입력을 읽고, 메서드 시그니처는 문제에서 주어진 그대로. Scanner 대신 BufferedReader를 쓰는 습관을 들이면 N=10⁶에서도 안전하다.

자동완성 없는 상태로 30분 안에 두 문제를 푸는 훈련을 일주일에 두 번씩만 해도 라이브 코딩 직전에 손이 굳지 않는다.

연습 문제

문제 1 (쉬움): Daily Temperatures

정수 배열 temperatures가 주어진다. 각 인덱스 i에 대해, i 이후로 처음으로 temperatures[i]보다 더 따뜻한 날까지 며칠을 기다려야 하는지 를 담은 배열을 반환하라. 그런 날이 없으면 0을 채운다.

  • 입력: [73, 74, 75, 71, 69, 72, 76, 73]
  • 출력: [1, 1, 4, 2, 1, 1, 0, 0]
  • 제약: 1 ≤ N ≤ 10^5, 30 ≤ temperatures[i] ≤ 100

먼저 스스로 종이에 단조 감소 스택을 그려보고, 인덱스 저장 패턴으로 손코딩한 뒤 아래를 펼친다.

풀이 보기

스택에 인덱스를 넣고 단조 감소를 유지한다. 새 인덱스 i가 들어올 때, 스택 top의 온도가 temperatures[i]보다 엄격히 작으면 pop 하면서 그 인덱스의 답을 i - popped로 기록한다. 같은 온도에서는 pop 하지 않는다 ("더 따뜻한"이 strict이기 때문). 결과 배열은 0으로 초기화하므로 끝까지 남은 인덱스는 자동으로 0.

복잡도: 시간 O(N), 공간 O(N).

흔한 함정: <= 로 잘못 쓰면 같은 온도일 때도 pop 되어 오답. "strict 비교"를 처음 분석할 때 적어두면 막힌다.

java
import java.util.ArrayDeque;
import java.util.Deque;
 
public class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] answer = new int[n]; // 0으로 초기화됨
        Deque<Integer> stack = new ArrayDeque<>();
 
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
                int popped = stack.pop();
                answer[popped] = i - popped;
            }
            stack.push(i);
        }
        // 스택에 남은 인덱스는 더 따뜻한 날을 못 만난 경우 → answer가 이미 0
        return answer;
    }
 
    public static void main(String[] args) {
        int[] t = {73, 74, 75, 71, 69, 72, 76, 73};
        int[] ans = new Solution().dailyTemperatures(t);
        for (int v : ans) System.out.print(v + " ");
        // 1 1 4 2 1 1 0 0
    }
}

면접에서 follow-up이 들어올 만한 지점:

  • "왜 인덱스를 넣었나?" → 거리 계산 때문에 위치 정보가 필요하다.
  • "스택에 값을 넣고 별도 인덱스 배열을 두면 안 되나?" → 가능하지만 두 자료구조를 동기화 유지하는 부담이 늘어, 인덱스 단일화가 버그 표면적이 작다.
  • "amortized 분석이 무엇인가?" → 각 인덱스가 정확히 한 번 push 되고 최대 한 번 pop 되므로 push/pop 총합이 2N으로 bound 된다.

문제 2 (중간): Largest Rectangle in Histogram

음이 아닌 정수 배열 heights가 주어진다. 각 막대의 너비는 1이다. 이 히스토그램에서 만들 수 있는 가장 큰 직사각형의 넓이를 반환하라.

  • 입력: [2, 1, 5, 6, 2, 3]
  • 출력: 10 (인덱스 2~3, 높이 5, 너비 2 → 너비 2가 아니라 인덱스 2,3을 다 포함하는 5×2=10? 사실 정답은 인덱스 2~3 막대 두 개 모두 높이 ≥ 5 → 5×2=10.)
  • 제약: 1 ≤ N ≤ 10^5, 0 ≤ heights[i] ≤ 10^4

이 문제는 monotonic stack의 "꽃" 이라 불릴 정도로 자주 나오고, 변형(0/1 매트릭스 최대 직사각형)도 많다. 라이브 코딩에서 이걸 30분 안에 푼다면 패턴 숙련도를 강하게 어필할 수 있다.

풀이 보기

각 막대 i에 대해 "i를 높이로 사용할 때 만들 수 있는 최대 직사각형의 너비"를 구하면, 그 너비는 왼쪽으로 i보다 낮은 첫 막대의 인덱스 L과 오른쪽으로 i보다 낮은 첫 막대의 인덱스 R 사이 거리 R - L - 1로 결정된다. 면적은 heights[i] * (R - L - 1).

이걸 위해 보통 두 번 스캔(왼쪽 단조 증가 스택, 오른쪽 단조 증가 스택)으로 푸는 직관적 풀이가 있고, 한 번 스캔으로 끝내는 변형이 더 우아하다.

한 번 스캔 풀이는 다음 관찰에 기반한다.

  • 단조 증가 스택을 유지한다 (스택 top이 큰 값).
  • 새 인덱스 i의 높이가 스택 top의 높이보다 작거나 같으면 pop 하고, pop 된 인덱스 t에 대해 면적을 계산한다.
  • pop 된 t의 오른쪽 경계는 i (i가 처음으로 t의 높이를 깨뜨렸으므로).
  • pop 된 t의 왼쪽 경계는 pop 후 새 top (스택에 t보다 낮은 인덱스만 남는다). 스택이 비면 -1.
  • 너비 = i - newTop - 1. 면적 = heights[t] * width.

루프가 끝나면 스택에 남은 인덱스를 똑같은 방식으로 비운다 (오른쪽 경계는 n).

<=로 처리하는 이유: 같은 높이가 연속될 때 왼쪽 인덱스를 강제로 pop 시켜 새 인덱스가 그 영역을 다시 계산하도록 한다. 어차피 같은 높이라 면적은 동일하지만, pop 누락으로 인한 인덱스 꼬임을 막는다.

복잡도: 시간 O(N), 공간 O(N).

흔한 함정:

  • 너비를 i - t 로 잘못 계산 (실제로는 i - newTop - 1).
  • 스택이 비었을 때 newTop을 -1로 처리하지 않고 NPE/예외.
  • 루프 종료 후 스택을 비우는 단계를 빼먹어 마지막 막대들 면적이 누락.
java
import java.util.ArrayDeque;
import java.util.Deque;
 
public class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        Deque<Integer> stack = new ArrayDeque<>(); // 인덱스 저장, 단조 증가
        int maxArea = 0;
 
        for (int i = 0; i <= n; i++) {
            int curHeight = (i == n) ? 0 : heights[i]; // 끝에 가상의 0 막대
            while (!stack.isEmpty() && heights[stack.peek()] >= curHeight) {
                int t = stack.pop();
                int leftBoundary = stack.isEmpty() ? -1 : stack.peek();
                int width = i - leftBoundary - 1;
                int area = heights[t] * width;
                if (area > maxArea) maxArea = area;
            }
            stack.push(i);
        }
        return maxArea;
    }
 
    public static void main(String[] args) {
        int[] h = {2, 1, 5, 6, 2, 3};
        System.out.println(new Solution().largestRectangleArea(h)); // 10
        System.out.println(new Solution().largestRectangleArea(new int[]{2, 4})); // 4
        System.out.println(new Solution().largestRectangleArea(new int[]{0, 0, 0})); // 0
    }
}

가상의 0 막대 트릭(i == n일 때 curHeight = 0)을 쓰면 루프가 끝난 뒤 스택을 따로 비우는 코드를 안 짜도 된다. 라이브 코딩에서 이 두 줄 차이가 시간 압박 상황에서 큰 차이를 만든다. 면접관이 "왜 i를 n까지 돌리는가?"라고 물으면 이 가상 막대로 잔여 처리 코드를 통합한 것이라고 설명한다.

면접에서 follow-up이 들어올 만한 지점:

  • "왜 단조 감소가 아니라 단조 증가인가?" → 막대의 면적 계산은 "현재 막대를 높이로 쓸 때 양 옆으로 얼마나 뻗어갈 수 있는가"이고, 양 옆은 각각 "현재보다 낮은 첫 인덱스"다. 따라서 단조 증가 스택을 유지하다 낮은 값이 들어오면 pop 시점이 "오른쪽 낮은 경계 발견" 시점이 된다.
  • "두 번 스캔 풀이 대비 장점은?" → 코드 라인 절감, 캐시 친화. 단점은 처음 보면 가상 막대 트릭이 직관적이지 않아 면접관이 따라오기 어려울 수 있어, 두 번 스캔 풀이를 먼저 설명하고 한 번 스캔으로 압축했다고 말하는 흐름이 안전하다.
  • "히스토그램이 아니라 0/1 매트릭스의 최대 직사각형은?" → 행마다 누적 높이를 만들어 이 함수를 N번 호출. 시간 O(N×M).

면접 답변 프레이밍

라이브 코딩이 끝난 뒤(혹은 채용 매니저 라운드에서) "최근에 푼 알고리즘 문제 중 인상 깊은 것"을 묻는 경우, monotonic stack은 자료구조의 amortized 분석을 자연스럽게 풀어낼 수 있는 소재라 점수 포인트가 된다. 다음 구조로 답하면 단순히 "monotonic stack을 알아요" 보다 한 단계 위로 들린다.

"다음으로 큰 원소를 찾는 류의 문제는 brute force가 O(N²)인데, 핵심 관찰은 '아직 답을 못 찾은 인덱스들이 단조 감소하는 형태로 쌓인다'는 점입니다. 이 단조성을 스택으로 유지하면, 각 원소가 한 번 들어가고 한 번 나오므로 amortized O(N)이 됩니다. 인덱스 저장으로 통일해서 거리 계산 변형까지 같은 패턴으로 푸는 습관을 들였고, 등호 처리(strict 비교 vs not)를 문제 진술에서 먼저 확인하는 단계를 루틴에 넣었습니다."

이 한 단락이 "패턴을 외운 게 아니라 분석과 함께 내재화했다"는 인상을 준다.

또, 백엔드 시스템 맥락과 연결하면 더 좋다. 단조 스택의 발상은 실시간 윈도우 통계 (예: 최근 N개 요청 중 최대 응답 시간)에서 monotonic deque로 일반화되는데, 이걸 알고 있으면 알고리즘 문제에서 시스템 사고로 이어가는 follow-up에 강하다.

라이브 코딩 직전 체크리스트

  • Java Stack 대신 ArrayDeque 를 쓴다.
  • 스택에는 인덱스를 넣고, 비교는 arr[stack.peek()]로 한다.
  • 단조 증가/감소 중 어느 쪽인지를 한 줄 주석으로 명시한다.
  • pop 조건의 부등호가 strict (<) 인지 non-strict (<=) 인지 문제 진술에서 확인했다.
  • !stack.isEmpty() && ... 의 short-circuit 순서를 지켰다.
  • 결과 배열을 기본값(보통 -1 또는 0)으로 초기화했다.
  • 루프 종료 후 스택에 남은 원소 처리 방식을 정했다 (가상 끝 원소 트릭 또는 별도 루프).
  • amortized O(N) 설명을 코드 위에 한 줄 주석으로 적었다.
  • N=1, N=0, 모든 값이 동일, 단조 증가/감소 입력에 대해 머릿속으로 트레이스했다.
  • 푼 뒤 "이 문제는 단조 감소 스택 패턴이고, 같은 발상이 일일 온도/주가 스팬/히스토그램 직사각형으로 확장된다"고 한 줄로 정리해서 면접관에게 말했다.
on this page
  • 01왜 이 주제가 라이브 코딩에서 중요한가
  • 02핵심 개념: 스택의 단조성
  • 03인덱스를 저장한다, 값을 저장하지 않는다
  • 04while-pop 조건의 등호 처리
  • 05배열을 한 번만 순회해도 되는 이유
  • 06끝까지 남은 원소 처리
  • 07자주 나오는 구현 버그
  • 08라이브 면접에서 접근을 설명하는 순서
  • 09로컬 연습 환경
  • 10연습 문제
  • 문제 1 (쉬움): Daily Temperatures
  • 문제 2 (중간): Largest Rectangle in Histogram
  • 11면접 답변 프레이밍
  • 12라이브 코딩 직전 체크리스트

댓글 (0)