문제 내용
여러 개의 쇠막대기를 레이저로 절단하려고 한다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자른다. 쇠막대기와 레이저의 배치는 다음 조건을 만족한다.
- 쇠막대기는 자신보다 긴 쇠막대기 위에만 놓일 수 있다. - 쇠막대기를 다른 쇠막대기 위에 놓는 경우 완전히 포함되도록 놓되, 끝점은 겹치지 않도록 놓는다.
- 각 쇠막대기를 자르는 레이저는 적어도 하나 존재한다.
- 레이저는 어떤 쇠막대기의 양 끝점과도 겹치지 않는다.
아래 그림은 위 조건을 만족하는 예를 보여준다. 수평으로 그려진 굵은 실선은 쇠막대기이고, 점은 레이저의 위치, 수직으로 그려진 점선 화살표는 레이저의 발사 방향이다.
이러한 레이저와 쇠막대기의 배치는 다음과 같이 괄호를 이용하여 왼쪽부터 순서대로 표현할 수 있다.
- 레이저는 여는 괄호와 닫는 괄호의 인접한 쌍 ‘( ) ’ 으로 표현된다. 또한, 모든 ‘( ) ’는 반드시 레이저를 표현한다.
- 쇠막대기의 왼쪽 끝은 여는 괄호 ‘ ( ’ 로, 오른쪽 끝은 닫힌 괄호 ‘) ’ 로 표현된다.
위 예의 괄호 표현은 그림 위에 주어져 있다.
쇠막대기는 레이저에 의해 몇 개의 조각으로 잘려지는데, 위 예에서 가장 위에 있는 두 개의 쇠막대기는 각각 3개와 2개의 조각으로 잘려지고, 이와 같은 방식으로 주어진 쇠막대기들은 총 17개의 조각으로 잘려진다.
쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 주어졌을 때, 잘려진 쇠막대기 조각의 총 개수를 구하는 프로그램을 작성하시오.
입력
한 줄에 쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 공백없이 주어진다. 괄호 문자의 개수는 최대 100,000이다.
출력
잘려진 조각의 총 개수를 나타내는 정수를 한 줄에 출력한다.
문제 접근 방법
문제를 보면 알겠지만 쇠막대기에 레이저 n개를 쐈을때 해당 쇠막대기는 n+1개로 조각나게 된다.
이를 이용해 문제를 풀었다.
내풀이
- 첫번째로 스택을 이용해서 레이저의 위치를 알아낸다. 문제에서 레이저는 ' ) ' 가 나왔을때 바로전 ' ( ' 이 괄호가 나왔을때 레이저라고 판단한다.
- 두번째도 스택을 이용해서 쇠막대기의 위치를 알아낸다. 레이저를 제외하고는 열린괄호 ' ( ' 와 닫힌 괄호 ' ) ' 의 짝이 맞았을때 쇠막대기가 생성이 된다.
- 세번째로 위에서 알아낸 위치들을 갖고 쇠막대기 각각 하나마다 몇개의 레이저가 닿는지 알아내고 n개의 레이저가 닿았을때 n+1개로 생성된 조각난 쇠막대기를 answer(정답)변수에 추가해준다.
내가푼 방식은 기본적으로 위의 과정을 거친다.
하지만 내가 풀었던 방식과 유사하지만 훨씬 더 효율적인 방법이 존재한다.
이는 다른분들의 풀이를 참고한 풀이이고 설명하자면 다음과 같다.
다른풀이
- 내풀이와 마찬가지로 스택을 이용한다(안해도되긴함).
' ( ' 일때 스택에 추가, ' ) ' 일때 스택에서 반환(삭제) 한다. - ' ) ' 이게 나왔을때 스택에서 pop을 해주고 바로 전 요소가 ' ( ' 이거라면 answer에 스택의 사이즈만큼 더해준다.
- 바로 전 요소가 ' ( ' 이게 아니라면 answer++ 을 해준다.
내풀이와 다른풀이 차이점
두개의 풀이에서 레이저와 쇠막대기가 만들어지는 과정은 문제에서 정의된것이기 때문에 무조건 같다.
다만, 내풀이는 쇠막대기를 하나씩 레이저를 쏴서 조각난 개수를 더하는 방법이고
다른풀이는 레이저를 쐈을때 그 레이저에 닿는 모든 쇠막대기는 조각나게 되는데 그때 조각난 쇠막대기의 한쪽부분의 개수만 세어준다. 이 과정을 레이저를 만날때마다 반복해주면서 레이저가 아닐땐 남은 쇠막대기조각을 추가하기 위해 answer++을 해주는 것이다.
즉, 정리하면
내풀이 -> 쇠막대기 하나마다의 경우를 봄.
다른풀이 -> 레이저하나에 닿는 쇠막대기 전부의 경우를 봄.
풀이를 보고 생각해보자.
풀이
내풀이
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Stack;
public class Main {
static ArrayList<Integer> razerIdx = new ArrayList<Integer>();//레이저 위치를 저장하는 리스트
static Stack<StickPoint> stickIdx = new Stack<StickPoint>();//쇠막대기 위치를 저장하는 스택
public static void main(String[] args) throws IOException{
int answer = 0;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
location(s);//쇠막대기와 레이저 위치
//쇠막대기 하나씩 잘라서 answer에 추가
while(!stickIdx.isEmpty()) {
StickPoint sp = stickIdx.pop();
answer += stickDivide(sp,1);
}
System.out.println(answer);
}
//위치
public static void location(String s) {
Stack<StickPoint> st = new Stack<StickPoint>();
for(int i=0; i<s.length(); i++) {
char temp = s.charAt(i);
//스택에 '('일때 추가하고, ')'일때 pop.
if(st.isEmpty() || temp==st.peek().c) {
st.add(new StickPoint(i, 0, temp));
} else {
StickPoint sp = st.pop();
//바로 전요소 체크후 레이저면 레이저 위치 추가.
if(s.charAt(i-1)=='(') { razerIdx.add(i); }
//레이저가 아니면 쇠막대기 위치 추가.
else if(s.charAt(i-1)!='(') { stickIdx.add(new StickPoint(sp.x, i, temp));}
}
}
}
//쇠막대기 조각내서 개수 세기
//cnt(개수)는 레이저 n개를 쐈을때 조각난 쇠막대기가 n+1개가 되야하므로
//초기값을 1로하고 시작함.
public static int stickDivide(StickPoint sp, int cnt) {
int stickX = sp.x;//쇠막대기 시작 위치
int stickY = sp.y;//쇠막대기 끝나는 위치
for(int i=0; i<razerIdx.size(); i++) {
int razer = razerIdx.get(i);
//레이저가 쇠막대기 시작과 끝위치 안에 포함되면
//레이저가 닿는다는 의미로 쇠막대기가 조각나게됨.
//따라서 레이저 닿을때 마다 cnt 증가.
if(stickX<razer && stickY>razer) { cnt++; }
}
return cnt;
}
}
//스택에 저장해줄 클래객체
class StickPoint{
int x;//start 위치
int y;//end 위치
char c;//괄호 모양
public StickPoint(int x, int y, char c) {
this.x = x;
this.y = y;
this.c = c;
}
}
다른 풀이
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Stack;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
int answer = 0;
Stack<Integer> st = new Stack<Integer>();
for(int i=0; i<s.length(); i++) {
char temp = s.charAt(i);
//스택에 '(' 일때 추가하고, ')'일때 pop
if(temp=='(') {
st.add(i);
}else {
st.pop();
if(s.charAt(i-1)=='(') { //만약 레이저라면
answer += st.size(); //남은 스택사이즈(잘릴 쇠막대기 갯수) 더함.
} else { //레이저가 아닐땐 잘린 쇠막대기 끝에 남은거 하나를 더함.
answer++;
}
}
}
System.out.println(answer);
}
}
마치며
스택을 사용하는 방법을 알아볼 수 있는 문제였다. 사실 두번째 풀이로 푼다면 스택이 필요없이 풀수 있긴하다.
여튼 스택은 문자 짝맞추는거, 괄호문제 등등에 자주 쓰이기 때문에 알아두는게 좋다.
'알고리즘 > BOJ' 카테고리의 다른 글
[JAVA] BOJ(백준) - 후위 표기식2 - 1935 (0) | 2022.01.06 |
---|---|
[JAVA] BOJ(백준) - 스택 수열 - 1874 (0) | 2022.01.05 |
[JAVA] BOJ(백준) - 프린터 큐 - 1966 (0) | 2021.12.30 |
[JAVA] BOJ(백준) - 주유소 - 13305번 (0) | 2021.12.07 |
[JAVA] BOJ(백준) - 잃어버린 괄호 - 1541번 (0) | 2021.12.06 |