190102 스택&큐 (1) 쇠막대기

2019-01-02

스택&큐 (1) 쇠막대기

문제 설명

여러 개의 쇠막대기를 레이저로 절단하려고 합니다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자릅니다. 쇠막대기와 레이저의 배치는 다음 조건을 만족합니다.

  • 쇠막대기는 자신보다 긴 쇠막대기 위에만 놓일 수 있습니다.
  • 쇠막대기를 다른 쇠막대기 위에 놓는 경우 완전히 포함되도록 놓되, 끝점은 겹치지 않도록 놓습니다.
  • 각 쇠막대기를 자르는 레이저는 적어도 하나 존재합니다.
  • 레이저는 어떤 쇠막대기의 양 끝점과도 겹치지 않습니다.

아래 그림은 위 조건을 만족하는 예를 보여줍니다. 수평으로 그려진 굵은 실선은 쇠막대기이고, 점은 레이저의 위치, 수직으로 그려진 점선 화살표는 레이저의 발사 방향입니다.

이러한 레이저와 쇠막대기의 배치는 다음과 같이 괄호를 이용하여 왼쪽부터 순서대로 표현할 수 있습니다.

  • 레이저는 여는 괄호와 닫는 괄호의 인접한 쌍 ‘()’으로 표현합니다. 또한 모든 ‘()’는 반드시 레이저를 표현합니다.
  • 쇠막대기의 왼쪽 끝은 여는 괄호 ‘(‘로, 오른쪽 끝은 닫힌 괄호 ‘)’로 표현됩니다.
  • 위 예의 괄호 표현은 그림 위에 주어져 있습니다.

쇠막대기는 레이저에 의해 몇 개의 조각으로 잘리는데, 위 예에서 가장 위에 있는 두 개의 쇠막대기는 각각 3개와 2개의 조각으로 잘리고, 이와 같은 방식으로 주어진 쇠막대기들은 총 17개의 조각으로 잘립니다.

쇠막대기와 레이저의 배치를 표현한 문자열 arrangement가 매개변수로 주어질 때, 잘린 쇠막대기 조각의 총 개수를 return 하도록 solution 함수를 작성해주세요.


풀이 과정
  • 막대기 내부에 포함된 레이저 개수가 n개라면 막대 조각은 n+1개가 된다.
  • 막대와 레이저의 위치를 계산하고, 막대 내부에 포함되는 레이저 수를 구해 더한다.

  • (을 마주치면 스택에 넣고, )을 마주쳤을때 pop
    • 길이가 1 이라면 레이저 배열에 레이저 시작점(‘(‘의 위치)를 넣는다.
    • 길이가 1 이상이라면 막대 리스트(시작점 ‘(‘의 인덱스와 끝점 ‘)’의 인덱스 가짐)에 넣는다
import java.util.*;

public class Stick{
	int sPoint;
	int ePoint;
	 public Stick(int sPoint,int ePoint){
	        this.sPoint=sPoint;
	        this.ePoint=ePoint;
	    }
	    public int getSPoint(){
	        return this.sPoint;
	    }
	    public int getEPoint(){
	        return this.ePoint;
	    }
}// 막대의 시작점과 끝점을 가지고 있는 객체

class Solution {
	ArrayList<Integer> laser=new ArrayList<>(); // 레이저 시작 인덱스를 가진 배열
	ArrayList<Integer> stack=new ArrayList<>(); // 막대 위치 계산을 위한 스택
	ArrayList<Stick> stick=new ArrayList<>(); // 막대의 시작점과 끝점을 가지는 객체의 리스트
	
	public int solution(String arrangement) {
		int answer=0;
		
		for(int i=0;i<arrangement.length();i++) {
			if(arrangement.charAt(i)=='(') {
				stack.add(i);
			}else if(arrangement.charAt(i)==')'){
				int sp=pop();
				int ep=i;
				if(ep-sp==1) {
					laser.add(sp);
				}// 만약 길이가 1이라면 레이저 시작점을 레이저 배열에 넣는다
				else {
					Stick newStick=new Stick(sp,ep);
					stick.add(newStick);
				}// 길이가 1이 아니라면 새로운 막대이므로 stick list에 넣는다
			}
		}// 문자열 연산을 통해 레이저와 막대를 구분하는 과정
		
		for(int i=0;i<stick.size();i++) {
			int sp=stick.get(i).getSPoint();
			int ep=stick.get(i).getEPoint();
			int cutLaser=0;
			for(int idx:laser) {
				if(idx>sp && idx<ep) {
					cutLaser++;
				}// 막대 안에 포함된 레이저의 개수를 구한다.
			}
			answer=answer+cutLaser+1; 
            // 막대 내부에 포함된 레이저의 개수가 n개이면 잘린 막대조각은 n+1개이다.
		}// 막대마다 잘려지는 개수를 구하고, answer에 그 값을 더한다.  
        
		return answer;
	}
	
	public int pop(){
		int element=stack.get(stack.size()-1);
		stack.remove(stack.size()-1);
		
		return element;
	};// stack의 pop연산 구현
}

더 생각해볼것
  • 정확도는 100이었으나 예상보다 실행 시간이 오래 걸렸다 (4461.27ms, 52MB)

    • Stick을 객체로 만들었기 때문일까? 더 빠른 방법은?
  • 막대가 아닌 레이저 끝점 위치 기준으로 생각하면 더 빠르다. 예를 들어 위의 막대 그림에서 인덱스 6의 첫 레이저를 만났을 때 스택의 사이즈는 3이고, 이 위치에서 막대를 자르면 3조각이 나오게 된다.

import java.util.Stack;

class Solution {
    public int solution(String arrangement) {
        int answer = 0;
        Stack<Integer> stack = new Stack<>();

        for (int i=0; i < arrangement.length(); i++) {
            if (arrangement.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (arrangement.charAt(i-1) == '(') {
                    answer += stack.size();
                } else {
                    answer += 1;
                }
            }
        }
        return answer;
    }
}  
  • 자바 컬렉션 프레임워크에 Stack이 있다는 점을 잊고 있었다(…완전 코찔이 티내는중 ㅜㅜ) 조만간 컬렉션 프레임워크에 대해 한번 쭉 정리할 예정!