class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer> heightStack = new ArrayDeque<Integer>();
Deque<Integer> widthStack = new ArrayDeque<Integer>();
Deque<Integer> stack = new ArrayDeque<Integer>();
int max =0;
int memo = 0;
if(heights.length==1){
return heights[0];
}
for(int i=0; i<heights.length; i++){
int curr = heights[i];
int jump = 0;
if(!heightStack.isEmpty()){
int prev = heightStack.peek();
if(curr==prev && i!=heights.length-1){
memo++;
}else{
if(curr<prev){
while(!heightStack.isEmpty()&& curr<prev){
int height = heightStack.pop();
int width = widthStack.pop()+memo;
max = Math.max(max, height * width);
if(!heightStack.isEmpty()){
prev = heightStack.peek();
}else{
prev = -1;
}
jump = width;
}
}
while(!widthStack.isEmpty()){
stack.push(widthStack.pop()+1+memo);
}
while(!stack.isEmpty()){
int module = stack.pop();
widthStack.push(module);
}
if(curr!=prev){
heightStack.push(curr);
widthStack.push(jump+1);
}
memo = 0;
}
}else{
heightStack.push(curr);
widthStack.push(1);
}
}
while(!heightStack.isEmpty()){
int h = heightStack.pop();
int w = widthStack.pop();
max=Math.max(max,h*w);
}
return max;
}
}
과도한 복잡도:
잘못된 로직:
while(!widthStack.isEmpty()){
stack.push(widthStack.pop()+1+memo);
}
while(!stack.isEmpty()){
int module = stack.pop();
widthStack.push(module);
}
복잡한 상태 관리:
memo, jump 변수의 역할 불명확불필요한 분기:
if(curr==prev && i!=heights.length-1) 조건이 불필요핵심 아이디어: 인덱스만 저장하고, 높이가 감소할 때 면적 계산
class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
int maxArea = 0;
int n = heights.length;
for (int i = 0; i < n; i++) {
// 현재 높이가 스택의 높이보다 작으면
while (!stack.isEmpty() && heights[stack.peek()] > heights[i]) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
// 스택에 남은 인덱스들 처리
while (!stack.isEmpty()) {
int height = heights[stack.pop()];
int width = stack.isEmpty() ? n : n - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
return maxArea;
}
}
시간복잡도: O(n) 공간복잡도: O(n) Runtime: 8-10ms ✔
class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
stack.push(-1); // Sentinel
int maxArea = 0;
for (int i = 0; i < heights.length; i++) {
while (stack.peek() != -1 &&
heights[stack.peek()] > heights[i]) {
int height = heights[stack.pop()];
int width = i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
while (stack.peek() != -1) {
int height = heights[stack.pop()];
int width = heights.length - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
return maxArea;
}
}
개선점:
stack.isEmpty() 체크 불필요class Solution {
public int largestRectangleArea(int[] heights) {
int n = heights.length;
// 끝에 0을 추가하여 모든 막대 처리 보장
int[] newHeights = new int[n + 1];
System.arraycopy(heights, 0, newHeights, 0, n);
Deque<Integer> stack = new ArrayDeque<>();
int maxArea = 0;
for (int i = 0; i <= n; i++) {
while (!stack.isEmpty() &&
newHeights[stack.peek()] > newHeights[i]) {
int height = newHeights[stack.pop()];
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
}
class Solution {
public int largestRectangleArea(int[] heights) {
return calculateArea(heights, 0, heights.length - 1);
}
private int calculateArea(int[] heights, int start, int end) {
if (start > end) return 0;
// 최소 높이 인덱스 찾기
int minIndex = start;
for (int i = start; i <= end; i++) {
if (heights[i] < heights[minIndex]) {
minIndex = i;
}
}
// 최소 높이로 만들 수 있는 직사각형
int currentArea = heights[minIndex] * (end - start + 1);
// 왼쪽과 오른쪽 분할 정복
int leftArea = calculateArea(heights, start, minIndex - 1);
int rightArea = calculateArea(heights, minIndex + 1, end);
return Math.max(currentArea, Math.max(leftArea, rightArea));
}
}
시간복잡도: 평균 O(n log n), 최악 O(n²)
| 방법 | 시간복잡도 | 공간복잡도 | Runtime | Memory | 가독성 | 추천도 |
|---|---|---|---|---|---|---|
| 원본 코드 | O(n²) | O(n) | 849ms | 59.88MB | ⭐ | ❌ |
| 단조 스택 | O(n) | O(n) | 8-10ms | 56MB | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Sentinel | O(n) | O(n) | 8-10ms | 56MB | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 0 추가 | O(n) | O(n) | 8-10ms | 56.5MB | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| D&C | O(n log n) ~ O(n²) | O(log n) | 20-200ms | 55MB | ⭐⭐⭐ | ⭐⭐ |
개선 효과: 849ms → 8ms = 약 100배 빠름! 🚀
문제: heights = [2, 1, 5, 6, 2, 3]
단조 스택 처리 과정:
i=0: h=2
stack: [0]
i=1: h=1 < 2
pop(0): height=2, width=1, area=2
stack: [1]
i=2: h=5
stack: [1, 2]
i=3: h=6
stack: [1, 2, 3]
i=4: h=2 < 6
pop(3): height=6, width=1 (4-2-1), area=6
pop(2): height=5, width=2 (4-1-1), area=10 ✓
stack: [1, 4]
i=5: h=3
stack: [1, 4, 5]
끝: 스택 정리
pop(5): height=3, width=2 (6-4-1), area=6
pop(4): height=2, width=4 (6-1-1), area=8
pop(1): height=1, width=6, area=6
최대 면적: 10
각 인덱스는:
- 정확히 1번 push
- 최대 1번 pop
총 연산: 2n = O(n)
핵심:
“스택에 남아있는 막대는 항상 증가하는 높이 순서”
int width = stack.isEmpty() ? i : i - stack.peek() - 1;
설명:
i: 현재 인덱스 (오른쪽 경계)stack.peek(): 현재 막대보다 낮은 왼쪽 막대 (왼쪽 경계)width = i - stack.peek() - 1예시:
heights = [2, 1, 5, 6, 2, 3]
0 1 2 3 4 5
i=4에서 h=6을 pop:
- i = 4 (현재 위치)
- stack.peek() = 2 (인덱스 2의 h=5)
- width = 4 - 2 - 1 = 1 ✓
과도한 설계는 독
단조 스택의 본질
단조 증가 스택 유지
→ 감소하는 순간 = 직사각형 계산 시점
인덱스의 힘
Sentinel의 활용
추천: Sentinel 방식 (가독성과 성능 모두 우수)
class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer> stack = new ArrayDeque<>();
stack.push(-1); // Sentinel: 왼쪽 경계 역할
int maxArea = 0;
for (int i = 0; i < heights.length; i++) {
// 현재 높이가 스택 top보다 낮으면
// 스택의 막대들로 만들 수 있는 직사각형 계산
while (stack.peek() != -1 &&
heights[stack.peek()] > heights[i]) {
int height = heights[stack.pop()];
int width = i - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
// 스택에 남은 막대들 처리
while (stack.peek() != -1) {
int height = heights[stack.pop()];
int width = heights.length - stack.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
return maxArea;
}
}
/**
* 시간복잡도: O(n) - 각 막대는 1번 push, 최대 1번 pop
* 공간복잡도: O(n) - 스택 크기
*
* Runtime: 8-10ms (상위 95%)
* Memory: 56MB
*/
개선 효과:
단조 증가 스택
스택: [인덱스들]
heights[스택의 인덱스들]은 증가 순서
감소하는 순간 = 직사각형 완성
실무 활용
관련 문제 패턴
LeetCode 85. Maximal Rectangle ⭐⭐⭐⭐⭐
LeetCode 42. Trapping Rain Water ⭐⭐⭐⭐
LeetCode 496. Next Greater Element I ⭐⭐
LeetCode 739. Daily Temperatures ⭐⭐
LeetCode 221. Maximal Square ⭐⭐⭐
입력: [2, 1, 5, 6, 2, 3]
// 각 단계 출력
i=0, h=2: stack=[0], maxArea=0
i=1, h=1: pop 0, area=2*1=2, stack=[1], maxArea=2
i=2, h=5: stack=[1,2], maxArea=2
i=3, h=6: stack=[1,2,3], maxArea=2
i=4, h=2:
- pop 3, h=6, w=1, area=6, maxArea=6
- pop 2, h=5, w=2, area=10, maxArea=10 ✓
- stack=[1,4], maxArea=10
i=5, h=3: stack=[1,4,5], maxArea=10
끝:
- pop 5, h=3, w=2, area=6
- pop 4, h=2, w=4, area=8
- pop 1, h=1, w=6, area=6
답: 10
Brute Force (시간초과):
for (int i = 0; i < n; i++) {
int minHeight = heights[i];
for (int j = i; j < n; j++) {
minHeight = Math.min(minHeight, heights[j]);
maxArea = Math.max(maxArea, minHeight * (j - i + 1));
}
}
// O(n²)
최적화 사고:
#MonotonicStack #Histogram #LargestRectangle #Stack #DynamicProgramming
#단조스택#히스토그램 #면적최대화 #Deque #IndexTracking #O(n)Algorithm #Sentinel
#LeetCodeHard#코딩테스트 #Hard #최적화