• Home
  • About
    • Ryureka Moment photo

      Ryureka

      Sin Prisa, Sin Pausa

    • About Me
    • Facebook
    • Github
    • Youtube
  • Projects
  • Posts
    • Posts
    • ProblemSolvings
    • Tags
    • Blog
    • TIL
    • Examples
  • ProblemSolving
    • ProblemSolving
    • BruteForce
    • DFS
    • DP
    • Optimization
  • FrontEnd
    • FrontEnd
    • HTML
  • BackEnd
    • BackEnd
    • Spring
    • Node.js
    • DataBase
      • MySQL
  • Programming
    • Programming
    • Java
    • Python
  • ComputerScience
    • DataStructure
    • Algorithm

[BOJ] 1725. 히스토그램

04 Nov 2020

Reading time ~5 minutes

  • 접근방식 1 : 완전탐색(Brute Force)
    • 완전탐색 풀이1
    • 완전탐색(BruteForce) 풀이1 구현
    • 완전탐색 풀이2
    • 완전탐색(BruteForce) 풀이2 구현
    • 완전탐색 풀이3
    • 완전탐색(BruteForce) 풀이3 구현
  • 접근방식 2 : 분할정복(Divide And Quanquer)
    • 분할정복(Divide And Quanquer) 풀이
    • 분할정복(Stack) 구현
  • 접근방식 3 : 스택(Stack)
    • 스택 풀이
    • 스택(Stack) 구현
해당 문제는 백준 온라인 저지(BOJ)의 1725번 히스토그램에서 풀어보실 수 있습니다.

참고로 백준 온라인 저지(BOJ)의 6549번 히스토그램에서 가장 큰 직사각형 문제와 입출력 형식은 다르지만 같은 문제입니다. 마찬가지로 알고스팟(Algospot)의 울타리 잘라내기와 릿코드(leetcode)의 largest-rectangle-in-histogram에서도 풀어보실 수 있습니다.

접근방식 1 : 완전탐색(Brute Force)

완전탐색 풀이1

최대 직사각형이 될 수 있는 모든 후보를 만들기 위한 방법으로 처음 떠오른 방식은 밑면의 길이를 차례로 고정하고 그에 맞는 높이를 찾는 방법입니다. 설명의 편의를 위해 주어진 직사각형의 높이가 각각 2, 1, 3, 2, 3 이라 예를 들어 보겠습니다. 아래 그림과 같이 밑면의 길이가 1일 때 만들어지는 최대 직사각형 후보의 개수는 5개 입니다. 마찬가지로 2일 때는 4개, 3일 때는 3개, 4일때는 2개, 5일 때는 1개가 있습니다. 이와 같은 방식으로 최대 직사각형이 될 수 있는 모든 후보를 만들어 볼 수 있습니다.

백준 히스토그램 완전탐색 접근방식1

이와 같은 방식으로 구현한 코드는 아래와 같습니다.

완전탐색(BruteForce) 풀이1 구현

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {		
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int heights[] = new int [N];
		for (int i = 0; i < heights.length; i++) {
			heights[i] = sc.nextInt();
		}
		int answer = 0;
		int min_height;
		
		// 밑면의 길이를 1로 고정한 후 값을 하나씩 증가시키며 반복.
		for (int width = 1; width <= N; width++) {
			// 최대 길이 내에서 시작 인덱스를 0으로 고정하고 한 칸씩 오른쪽으로 가면서 반복.
			for (int start_index = 0; start_index+width <= N; start_index++) { 
				// 밑면의 길이와 시작 인덱스를 고정할 때 마다 최소 높이를 구하기 위해서 초기화.
				min_height = Integer.MAX_VALUE; 
				// 최소 높이를 구하기위해 커서를 시작 인덱스부터 밑면의 길이만큼 한 칸씩 이동하며 구간을 탐색. 
				for (int cursor = start_index; cursor < start_index+width; cursor++) { 
					// 최소 높이를 구함.
					min_height = Math.min(min_height,heights[cursor]); 
				}
				// 이전까지의 후보들의 최대값과 비교해 더 큰 값을 정답 처리.
				answer = Math.max(answer,min_height*width); 
			}
		}
		System.out.println(answer); // 모든 최대 직사각형 후보들의 계산값 중 최대값을 정답으로 출력.
	}
}

for문이 3번 중첩되어 \(O(N^3)\)의 시간복잡도를 가지게 됩니다. 히스토그램 문제의 입력 N의 최대값은 \(100,000(=10^5)\) 이므로 최대 \(10^{15}\)정도의 시간이 걸리게 된다고 생각해 볼 수 있습니다. 현대 컴퓨터에서의 수행시간을 대략 반복문 수행횟수 1억(=\(10^8\)) 당 1초가 걸린다고 가정한다면 약 1천만초(=\(10^7\)) 즉, 날짜로 환산하면 (\(10^7/3600*24\)) 116일 가까이 걸리게 되므로 문제에 명시된 2초의 제한시간으로는 시간초과가 발생함을 예상해 볼 수 있습니다.

완전탐색 풀이2

마찬가지로 모든 밑면의 길이를 탐색하여 최대 직사각형 후보를 만들기 위한 방식입니다. 다만 풀이1과 관점을 약간 달리하여 시작 인덱스를 차례로 고정하고 종료 인덱스를 한 칸씩 오른쪽으로 이동하면서 넓이를 계산하는 방법입니다. 아래 그림과 같이 시작인덱스를 0으로 고정한 후 종료 인덱스를 1부터 5까지 이동하면서 탐색합니다. 다음으로는 시작인덱스를 1로 고정한 후 종료인덱스를 2부터 5까지 이동하면서 탐색합니다. 이와 같은 방식으로 최대 직사각형이 될 수 있는 모든 후보를 만들어 볼 수 있습니다.

백준 히스토그램 완전탐색 접근방식2

완전탐색(BruteForce) 풀이2 구현

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {		
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int heights[] = new int [N];
		for (int i = 0; i < N; i++) {
			heights[i] = sc.nextInt();
		}
		int answer = 0;
		int min_height;
		
		// 시작인덱스 0으로 고정한 후 한 칸씩 오른쪽으로 가면서 차례로 고정.
		for (int start_index = 0; start_index < heights.length; start_index++) {  
			// 종료인덱스를 시작인덱스의 한 칸 오른쪽 인덱스로 고정한 후 한 칸씩 오른쪽으로 가면서 차례로 고정.
			for (int end_index = start_index+1; end_index <= heights.length; end_index++) {
				// 시작 및 종료인덱스가 고정될 때마다 최소 높이를 구하기 위해서 초기화.
				min_height = Integer.MAX_VALUE; 
				// 시작인덱스부터 종료인덱스까지 커서를 한 칸씩 이동하며 최소 높이를 계산.
				for (int cursor = start_index; cursor < end_index; cursor++) {
					// 최소 높이를 구함.
					min_height = Math.min(min_height, heights[cursor]); 
				}
				// 이전까지의 후보들의 최대값과 비교해 더 큰 값을 정답 처리.
				answer = Math.max(answer,min_height*(end_index-start_index)); 
			}
		}
		System.out.println(answer); // 모든 최대 직사각형 후보들의 계산값 중 최대값을 정답으로 출력.
	}
}

for문이 3번 중첩되어 풀이1과 동일한 \(O(N^3)\)의 시간복잡도를 가지게 됩니다. 마찬가지로 시간초과를 예상해 볼 수 있습니다. 하지만 풀이1과는 달리 시간복잡도를 개선하는 방법이 존재합니다.

완전탐색 풀이3

부분의 최소값을 구하는 방식을 순차적으로 즉시 처리하면 풀이2의 시간복잡도인 \(O(N^3)\)에서 for문의 중첩을 한 겹 벗겨낼 수 있습니다. 그렇게 하면 시간복잡도를 \(O(N^2)\)으로 개선할 수 있습니다.

백준 히스토그램 완전탐색 접근방식3

완전탐색(BruteForce) 풀이3 구현

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {		
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int heights[] = new int [N];
		for (int i = 0; i < heights.length; i++) {
			heights[i] = sc.nextInt();
		}
		int answer = 0;
		int min_height;
		int cursor = 0;
		// 시작 인덱스를 0으로 고정한 후 한 칸씩 오른쪽으로 가면서 차례로 고정.
		for (int start_index = 0; start_index < heights.length; start_index++) { 
			// 시작인덱스가 고정될 때마다 최소 높이를 구하기 위해서 초기화.
			// 종료인덱스가 고정될 때마다 초기화 하지 않는 이유는 이전 구간의 최소 높이로 재사용되기 때문
			min_height = Integer.MAX_VALUE;  
			for (int end_index = start_index+1; end_index <= heights.length; end_index++) {
				// 이전 구간에서 추가된 부분의 높이를 탐색할 커서위치.
				cursor = end_index-1; 
				// 우변의 Math.min 함수 내의 변수는 종료인덱스가 한 칸씩 오른쪽으로 이동할 때마다 이전 구간의 최소 높이로 사용.
				// 좌변의 변수는 이전 구간 최소높이와 커서위치 높이를 비교해서 현재 구간의 최소높이를 구함.
				min_height = Math.min(min_height, heights[cursor]); 
				// 이전까지의 후보들의 최대값과 비교해 더 큰 값을 정답 처리.
				answer = Math.max(answer,min_height*(end_index-start_index)); 
			}
		}
		System.out.println(answer); // 모든 최대 직사각형 후보들의 계산값 중 최대값을 정답으로 출력.
	}
}

for문이 2번 중첩되어 \(O(N^2)\)의 시간복잡도를 가지게 됩니다. 하지만 마찬가지로 히스토그램 문제의 입력 N의 최대값은 \(100,000(=10^5)\) 이므로 최대 \(10^{10}\)정도의 시간이 걸리게 된다고 생각해 볼 수 있습니다. 현대 컴퓨터에서의 수행시간을 대략 반복문 수행횟수 1억(=\(10^8\)) 당 1초가 걸린다고 가정한다면 약 100초(=\(10^2\)) 가까이 걸리게 되므로 문제에 명시된 2초의 제한시간으로는 시간초과가 발생함을 예상해 볼 수 있습니다. 116일에서 100초로 어느정도 성능이 향상되었지만 아직 제한시간에는 못 미치기에 다른 방법을 생각해내야합니다.

접근방식 2 : 분할정복(Divide And Quanquer)

분할정복(Divide And Quanquer) 풀이

백준 히스토그램 분할정복 접근방식

분할정복(Stack) 구현


접근방식 3 : 스택(Stack)

스택 풀이

지금까지는 직사각형의 밑면의 길이를 고정해서 후보를 만들어 보는 접근방식이었습니다. 하지만 이번에는 높이를 고정하고 그때의 높이를 좌우로 최대로 늘려 최대 직사각형 후보를 만드는 방식을 사용합니다. 최대 직사각형의 높이가 될 수 있는 후보는 인덱스의 수만큼 이므로 인덱스 마다의 높이를 모두 탐색해보는 방법입니다.

백준 히스토그램 스택 접근방식

스택(Stack) 구현

import java.util.Scanner;
import java.util.Stack;

import java.lang.Math;

class Main{
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int heights []= new int [N+2];
		heights[0]=0;
		heights[N+1]=0;
		for (int i = 1; i <= N; i++) {
			heights[i] = sc.nextInt();
		}
		Stack<Integer> stack = new Stack<Integer>();

        long answer = 0;
		int width = 0;
		int i = 0;
		for (int cursor = 0; cursor < heights.length; cursor++) {
			while(!stack.isEmpty() && heights[stack.peek()] >= heights[cursor]){
				i = stack.peek();
				stack.pop();
				if(!stack.isEmpty())
					width = cursor - (stack.peek() + 1);
				
				answer = Math.max(answer, (long)(width)*heights[i]);
			}
			stack.push(cursor);		
		}
        System.out.println(answer);
	}
}


OptimizationBruteForceStackDivideConquer Share