4. 셔틀버스(난이도: 중)

카카오에서는 무료 셔틀버스를 운행하기 때문에 판교역에서 편하게 사무실로 올 수 있다. 카카오의 직원은 서로를 ‘크루’라고 부르는데, 아침마다 많은 크루들이 이 셔틀을 이용하여 출근한다.

이 문제에서는 편의를 위해 셔틀은 다음과 같은 규칙으로 운행한다고 가정하자.

  • 셔틀은 09:00부터 총 nt분 간격으로 역에 도착하며, 하나의 셔틀에는 최대 m명의 승객이 탈 수 있다.
  • 셔틀은 도착했을 때 도착한 순간에 대기열에 선 크루까지 포함해서 대기 순서대로 태우고 바로 출발한다. 예를 들어 09:00에 도착한 셔틀은 자리가 있다면 09:00에 줄을 선 크루도 탈 수 있다.

일찍 나와서 셔틀을 기다리는 것이 귀찮았던 콘은, 일주일간의 집요한 관찰 끝에 어떤 크루가 몇 시에 셔틀 대기열에 도착하는지 알아냈다. 콘이 셔틀을 타고 사무실로 갈 수 있는 도착 시각 중 제일 늦은 시각을 구하여라.

단, 콘은 게으르기 때문에 같은 시각에 도착한 크루 중 대기열에서 제일 뒤에 선다. 또한, 모든 크루는 잠을 자야 하므로 23:59에 집에 돌아간다. 따라서 어떤 크루도 다음날 셔틀을 타는 일은 없다.

입력 형식

셔틀 운행 횟수 n, 셔틀 운행 간격 t, 한 셔틀에 탈 수 있는 최대 크루 수 m, 크루가 대기열에 도착하는 시각을 모은 배열 timetable이 입력으로 주어진다.

  • 0 < n ≦ 10
  • 0 < t ≦ 60
  • 0 < m ≦ 45
  • timetable은 최소 길이 1이고 최대 길이 2000인 배열로, 하루 동안 크루가 대기열에 도착하는 시각이 HH:MM 형식으로 이루어져 있다.
  • 크루의 도착 시각 HH:MM00:01에서 23:59 사이이다.

출력 형식

콘이 무사히 셔틀을 타고 사무실로 갈 수 있는 제일 늦은 도착 시각을 출력한다. 도착 시각은 HH:MM 형식이며, 00:00에서 23:59 사이의 값이 될 수 있다.

입출력 예제

n t m timetable answer
1 1 5 [“08:00”, “08:01”, “08:02”, “08:03”] “09:00”
2 10 2 [“09:10”, “09:09”, “08:00”] “09:09”
2 1 2 [“09:00”, “09:00”, “09:00”, “09:00”] “08:59”
1 1 5 [“00:01”, “00:01”, “00:01”, “00:01”, “00:01”] “00:00”
1 1 1 [“23:59”] “09:00”
10 60 45 [“23:59”,”23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”, “23:59”] “18:00”

문제 해설

쉬워 보이는데 어려운 문제가 바로 이 문제였던 거 같네요. 당초 난이도를 ‘중’으로 두고 문제를 중간 즈음에 배치하였는데, 시간을 계산하는 부분에서 많은 분들이 어려워하셨던 거 같습니다.

예를 들어 2번 입출력 예제의 경우 ["09:10", "09:09", "08:00"]인데 이 경우 두 번째 버스는 9:10분에 출발하기 때문에 9:10분에 오면 되지 않느냐 많이들 혼동하셨을 거 같아요. 하지만 9:00에 오는 버스는 8:00에 대기하는 크루 1명만 탑승할 수 있고, 따라서 9:10 버스에는 남아 있는 두 명이 모두 타게 됩니다. 따라서 좀 더 이른 9:09에 와야 탑승할 수 있습니다.

전체 계산은 어렵지 않지만 이처럼 정확하게 시간 계산을 해야 하는 부분이 많고 마지막 버스 시간까지 빈틈없이 계산해야 해서 많은 분들이 실수를 한 거 같습니다. 이 문제는 정답률이 두 번째로 낮은 26.79%입니다.





난이도 중 문제임에도 정답률이 낮다. 실제로 풀어보니 시간이 많이 걸렸다. 도중에 다른 문제로 교체한 사람들이 있어서 그런 듯 하다.


모든 크루들의 시간을 분으로 변경한다는 것은 처음부터 정해져 있던 내용이고 그 이후의 행동이 중요한데


vector나 list를 자동 정렬해주거나 removeif 메소드를 지원하는게 장점인 것 같다. C++은 포기하고 자바만 파두는게 나을지도 모르겠다.


콘은 무조건 막차만 타야 하기 때문에 앞 차량에 태울 수 있는 경우 모두 태워서 보내야 한다.


막차 시간 이후에 오는 사람은 고려할 필요가 없기 때문에 모두 보내버리면 된다.


그 후엔 막차 버스를 기준으로 허용량을 넘는가 아닌가만 고려하면 시간은 걸리지만 어려운 문제는 아니었다.



package kakao02;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import java.util.Vector;
import java.util.List;

public class kakao02 {

	public static void main(String[] args) {
		String a = Solution(1, 1, 5, new String[] { "08:00", "08:01", "08:02", "08:03" });
		String b = Solution(2, 10, 2, new String[] { "09:10", "09:09", "08:00" });
		String c = Solution(2, 1, 2, new String[] { "09:00", "09:00", "09:00", "09:00" });
		String d = Solution(1, 1, 5, new String[] { "00:01", "00:01", "00:01", "00:01", "00:01" });
		String e = Solution(1, 1, 1, new String[] { "23:59" });
		String f = Solution(10, 60, 45, new String[] { "23:59", "23:59", "23:59", "23:59", "23:59", "23:59", "23:59",
				"23:59", "23:59", "23:59", "23:59", "23:59", "23:59", "23:59", "23:59", "23:59" });
		System.out.println(a);
		System.out.println(b);
		System.out.println(c);
		System.out.println(d);
		System.out.println(e);
		System.out.println(f);
	}

	public static String Solution(int n, int t, int m, String[] timetable) {

		Vector<Integer> time = new Vector<Integer>();
		for (int i = 0; i < timetable.length; ++i) {// 크루들의 도착 시간을 int 형태로 Vector에 저장
			String[] temp = timetable[i].split(":");
			time.add(Integer.parseInt(temp[0]) * 60 + Integer.parseInt(temp[1]));
		}
		Collections.sort(time);// 도착 시간을 오름차순으로 정렬

		Vector<Integer> busTime = new Vector<Integer>();// 버스의 도착 시간을 저장하기 위한 배열
		busTime.add(540);// 첫 시작은 540 == "09:00"
		for (int i = 1; i < n; ++i) {// 운행 횟수만큼 추가된 시간이 더해진 운행시간 값을 저장.
			busTime.add(540 + t * i);
		}
		int lastBusTime = busTime.get(busTime.size() - 1);// 버스 막차 시간 확인

		time.removeIf(i -> i > lastBusTime);// 막차시간 이후에 오는 크루는 고려할 필요 없음. 예제6

		if (n > 1) {
			for (int i = 0; i < busTime.size() - 1; ++i) {// 콘은 막차를 타야 한다. 그 이전에 태울 수 있는 사람은 모두 삭제.
				for (int j = 0; j < m; ++j) {
					if (time.size() != 0)
						if (busTime.get(i) >= time.get(0)) {
							time.remove(0);
						}
				}
			}
		}

		if (m <= time.size())// 남은 인원수가 막차 허용량보다 많은 경우
		{
			for (int i = 0; i < m; ++i) {
				if (lastBusTime >= time.get(0)) {
					if (i < m - 1) {// 막차 허용량 안에 들어갈 경우 앞 사람부터 삭제
						time.remove(0);
					} else {//크루가 버스에 탈 수 있는데 마지막 허용량일 경우
						int lastBusHour = time.get(0) / 60;//해당 크루의 시간을 받아오고
						
						int lastBusMin = (time.get(0) % 60);//해당 크루의 분을 받아온 후에, -1분 해준다.
						if (lastBusMin == 0) {
							lastBusMin = 59;
							lastBusHour -= 1;
						} else
							lastBusMin--;
						
						String timeText = String.format("%02d", lastBusHour);//String이 아니라 int를 입력해야 한다.
						timeText += ":";
						timeText += String.format("%02d", lastBusMin);
						return timeText;
					}
				} else if (i < m) {//허용량이 남았지만 더이상 크루들이 타지 못하는 경우. 위에서 removeif를 했기에 쓸 일은 없다.
					//마지막 버스 시간에 도착하면 된다.
					int lastBusHour = lastBusTime / 60;
					int lastBusMin = lastBusTime % 60;
					String timeText = String.format("%02d", lastBusHour);
					timeText += ":";
					timeText += String.format("%02d", lastBusMin);
					return timeText;
				}
			}
		} else// 남은 인원수가 막차 허용량보다 적은 경우. 마지막 버스 도착 시간에 맞춰서 타면 된다.
		{
			int lastBusHour = lastBusTime / 60;
			int lastBusMin = lastBusTime % 60;
			String timeText = String.format("%02d", lastBusHour);
			timeText += ":";
			timeText += String.format("%02d", lastBusMin);
			return timeText;
		}
		return null;

	}
}

3. 캐시(난이도: 하)




지도개발팀에서 근무하는 제이지는 지도에서 도시 이름을 검색하면 해당 도시와 관련된 맛집 게시물들을 데이터베이스에서 읽어 보여주는 서비스를 개발하고 있다.
이 프로그램의 테스팅 업무를 담당하고 있는 어피치는 서비스를 오픈하기 전 각 로직에 대한 성능 측정을 수행하였는데, 제이지가 작성한 부분 중 데이터베이스에서 게시물을 가져오는 부분의 실행시간이 너무 오래 걸린다는 것을 알게 되었다.
어피치는 제이지에게 해당 로직을 개선하라고 닦달하기 시작하였고, 제이지는 DB 캐시를 적용하여 성능 개선을 시도하고 있지만 캐시 크기를 얼마로 해야 효율적인지 몰라 난감한 상황이다.

어피치에게 시달리는 제이지를 도와, DB 캐시를 적용할 때 캐시 크기에 따른 실행시간 측정 프로그램을 작성하시오.

입력 형식

  • 캐시 크기(cacheSize)와 도시이름 배열(cities)을 입력받는다.
  • cacheSize는 정수이며, 범위는 0 ≦ cacheSize ≦ 30 이다.
  • cities는 도시 이름으로 이뤄진 문자열 배열로, 최대 도시 수는 100,000개이다.
  • 각 도시 이름은 공백, 숫자, 특수문자 등이 없는 영문자로 구성되며, 대소문자 구분을 하지 않는다. 도시 이름은 최대 20자로 이루어져 있다.

출력 형식

  • 입력된 도시이름 배열을 순서대로 처리할 때, “총 실행시간”을 출력한다.

조건

  • 캐시 교체 알고리즘은 LRU(Least Recently Used)를 사용한다.
  • cache hit일 경우 실행시간은 1이다.
  • cache miss일 경우 실행시간은 5이다.

입출력 예제

캐시크기(cacheSize) 도시이름(cities) 실행시간
3 [“Jeju”, “Pangyo”, “Seoul”, “NewYork”, “LA”, “Jeju”, “Pangyo”, “Seoul”, “NewYork”, “LA”] 50
3 [“Jeju”, “Pangyo”, “Seoul”, “Jeju”, “Pangyo”, “Seoul”, “Jeju”, “Pangyo”, “Seoul”] 21
2 [“Jeju”, “Pangyo”, “Seoul”, “NewYork”, “LA”, “SanFrancisco”, “Seoul”, “Rome”, “Paris”, “Jeju”, “NewYork”, “Rome”] 60
5 [“Jeju”, “Pangyo”, “Seoul”, “NewYork”, “LA”, “SanFrancisco”, “Seoul”, “Rome”, “Paris”, “Jeju”, “NewYork”, “Rome”] 52
2 [“Jeju”, “Pangyo”, “NewYork”, “newyork”] 16
0 [“Jeju”, “Pangyo”, “Seoul”, “NewYork”, “LA”] 25



LRU가 뭔지 대충 검색해봤고, 거기서 명시한 조건만 만족하면 되는건가 해서 풀어보았다. 답은 맞는데 LRU가 제대로 된건지 흠..

처음에 임의의 배열을 받는다고 할때 컴파일러에서 배열을 어떻게 입력을 하길래 크기를 인지하고 입력을 받아야 하지? 하고 생각을 했었는데

카카오 모의 테스트에 들어가보니 별도의 solution 함수를 수정할 수 있게 되어져 있고 해당 함수에 배열이 인자값으로 들어오는 형태였다.

이걸 몰라서 몇 시간을 고생했었는지..


package kakao02;

import java.util.Scanner;
public class kakao02 {

	public static void main(String[] args)
	{
		String[] arr = new String[]{"Jeju", "Pangyo", "Seoul","NewYork", "LA", "Jeju","Pangyo", "Seoul", "NewYork", "LA"};
		String[] arr2 = new String[] {"Jeju", "Pangyo", "Seoul", "NewYork", "LA"};
		System.out.println(Solution(arr2));
	}
	public static int Solution(String[] cities)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		//cacheSize = new int[t];
		int time = 0;
		int bufferCount = 0;
		String[] cacheSize = new String[n];
		int[] cacheNum = new int[n];
		
		for(int i = 0; i<cities.length;++i)
		{
			boolean check = false;
			for(int j = 0; j<n;++j)
			{
				if(cities[i].toUpperCase().equals(cacheSize[j]))
				{
					time++;
					check = true;
					cacheNum[j] = i;
					break;
				}
			}
			if(!check) {			
				if(bufferCount < n) {
					cacheSize[bufferCount] = cities[i].toUpperCase();
					cacheNum[bufferCount] = i;
					bufferCount++;
				}
				else
				{
					int lastNum = 1000000;
					int count = -1;
					for(int j = 0; j < n;++j)
					{
						if(lastNum > cacheNum[j])
						{
							lastNum = cacheNum[j];
							count = j;
						}
					}
					if(n != 0)
					{
					cacheNum[count] = i;
					cacheSize[count] = cities[i].toUpperCase();
					}
				}
				time+=5;
			}
		}
		
		
		return time;
	}
}


2. 다트 게임(난이도: 하)

카카오톡에 뜬 네 번째 별! 심심할 땐? 카카오톡 게임별~

카카오톡 게임별의 하반기 신규 서비스로 다트 게임을 출시하기로 했다. 다트 게임은 다트판에 다트를 세 차례 던져 그 점수의 합계로 실력을 겨루는 게임으로, 모두가 간단히 즐길 수 있다.
갓 입사한 무지는 코딩 실력을 인정받아 게임의 핵심 부분인 점수 계산 로직을 맡게 되었다. 다트 게임의 점수 계산 로직은 아래와 같다.

  1. 다트 게임은 총 3번의 기회로 구성된다.
  2. 각 기회마다 얻을 수 있는 점수는 0점에서 10점까지이다.
  3. 점수와 함께 Single(S), Double(D), Triple(T) 영역이 존재하고 각 영역 당첨 시 점수에서 1제곱, 2제곱, 3제곱 (점수^1 , 점수^2 , 점수^3 )으로 계산된다.
  4. 옵션으로 스타상(*) , 아차상(#)이 존재하며 스타상(*) 당첨 시 해당 점수와 바로 전에 얻은 점수를 각 2배로 만든다. 아차상(#) 당첨 시 해당 점수는 마이너스된다.
  5. 스타상(*)은 첫 번째 기회에서도 나올 수 있다. 이 경우 첫 번째 스타상(*)의 점수만 2배가 된다. (예제 4번 참고)
  6. 스타상(*)의 효과는 다른 스타상(*)의 효과와 중첩될 수 있다. 이 경우 중첩된 스타상(*) 점수는 4배가 된다. (예제 4번 참고)
  7. 스타상(*)의 효과는 아차상(#)의 효과와 중첩될 수 있다. 이 경우 중첩된 아차상(#)의 점수는 -2배가 된다. (예제 5번 참고)
  8. Single(S), Double(D), Triple(T)은 점수마다 하나씩 존재한다.
  9. 스타상(*), 아차상(#)은 점수마다 둘 중 하나만 존재할 수 있으며, 존재하지 않을 수도 있다.

0~10의 정수와 문자 S, D, T, *, #로 구성된 문자열이 입력될 시 총점수를 반환하는 함수를 작성하라.

입력 형식

“점수|보너스|[옵션]”으로 이루어진 문자열 3세트.
예) 1S2D*3T

  • 점수는 0에서 10 사이의 정수이다.
  • 보너스는 S, D, T 중 하나이다.
  • 옵선은 *이나 # 중 하나이며, 없을 수도 있다.

출력 형식

3번의 기회에서 얻은 점수 합계에 해당하는 정수값을 출력한다.
예) 37

입출력 예제

예제 dartResult answer 설명
1 1S2D*3T 37 1^1 * 2 + 2^2 * 2 + 3^3
2 1D2S#10S 9 1^2 + 2^1 * (-1) + 10^1
3 1D2S0T 3 1^2 + 2^1 + 0^3
4 1S*2T*3S 23 1^1 * 2 * 2 + 2^3 * 2 + 3^1
5 1D#2S*3S 5 1^2 * (-1) * 2 + 2^1 * 2 + 3^1
6 1T2D3D# -4 1^3 + 2^2 + 3^2 * (-1)
7 1D2S3T* 59 1^2 + 2^1 * 2 + 3^3 * 2


문제 해설

문제에서 토큰화를 이용해서 풀라고 했는데 단어 단위로 문제를 풀었다. 한 번 푼 내용은 보존하고 다시 풀어야 할 것 같다.


단어 단위로 끊을 경우엔 별 어려움이 없다. 각 글자가 특수기호인지 아닌지를 구분하여 숫자를 넘겨주고, 그대로 활용하면 된다. 문제점이라면 java를 안쓴지 오래되서 입력법도 해깔렸다.


문자열 문제 다루는 것에는 C++보다 Java가 쉽더라.



package kakao02;

import java.util.Scanner;
public class kakao02 {

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		String dart;
		dart = sc.next();
		int arr[] = new int[3];
		int result = 0;
		int count = 0;
		String tempNum ="";
		for(int i = 0; i<dart.length();++i)
		{
			if(dart.charAt(i) =='S')
			{
				arr[count] = Integer.parseInt(tempNum);
				tempNum = "";
				arr[count] = (int)Math.pow(arr[count], 1);
				count++;
			}
			else if(dart.charAt(i)=='D')
			{
				arr[count] = Integer.parseInt(tempNum);
				tempNum = "";
				arr[count] = (int)Math.pow(arr[count], 2);
				count++;
			}
			else if(dart.charAt(i)=='T')
			{
				arr[count] = Integer.parseInt(tempNum);
				tempNum = "";
				arr[count] = (int)Math.pow(arr[count], 3);
				count++;
			}
			else if(dart.charAt(i)=='*')
			{
				if(count==1)
				{
					arr[count-1] *=2;
				}
				else
				{
					arr[count-2]*=2;
					arr[count-1]*=2;
				}
			}
			else if(dart.charAt(i)=='#')
			{
				arr[count-1]*= -1;
			}
			else
			{
				tempNum+=dart.charAt(i);
			}
		}
		result = arr[0] + arr[1]+arr[2];
		System.out.println(result);
	}
}

2017.09.16 카카오 신입공채 1 코딩 테스트 문제


1. 비밀 지도(난이도: )


네오는 평소 프로도가 비상금을 숨겨놓는 장소를 알려줄 비밀지도를 손에 넣었다. 그런데  비밀지도는 숫자로 암호화되어 있어  위치를 확인하기 위해서는 암호를 해독해야 한다. 다행히 지도 암호를 해독할 방법을 적어놓은 메모도 함께 발견했다.

 





1. 지도는  변의 길이가 n 정사각형 배열 형태로,  칸은 “공백”(“ “) 또는 “”(“#”)  종류로 이루어져 있다.

2. 전체 지도는  장의 지도를 겹쳐서 얻을  있다. 각각 “지도 1” “지도 2”라고 하자. 지도 1 또는 지도 2  어느 하나라도 벽인 부분은 전체 지도에서도 벽이다. 지도 1 지도 2에서 모두 공백인 부분은 전체 지도에서도 공백이다.

3. “지도 1” “지도 2” 각각 정수 배열로 암호화되어 있다.

4. 암호화된 배열은 지도의  가로줄에서  부분을 1, 공백 부분을 0으로 부호화했을  얻어지는 이진수에 해당하는 값의 배열이다.



네오가 프로도의 비상금을 손에 넣을  있도록, 비밀지도의 암호를 해독하는 작업을 도와줄 프로그램을 작성하라.

입력 형식

입력으로 지도의   크기 n  2개의 정수 배열 arr1, arr2 들어온다.

  1 ≦ n ≦ 16

  arr1, arr2 길이 n 정수 배열로 주어진다.

  정수 배열의  원소 x 이진수로 변환했을 때의 길이는 n 이하이다. , 0 ≦ x ≦ 2^n - 1 만족한다.

출력 형식

원래의 비밀지도를 해독하여 "#", 공백으로 구성된 문자열 배열로 출력하라

 

출력 형식

원래의 비밀지도를 해독하여 "#", 공백으로 구성된 문자열 배열로 출력하라.

 

입출력 예제

매개변수

n

5

arr1

[9, 20, 28, 18, 11]

arr2

[30, 1, 21, 17, 28]

출력

["#####","# # #", "### #", "# ##", "#####"]

매개변수

n

6

arr1

[46, 33, 33 ,22, 31, 50]

arr2

[27 ,56, 19, 14, 14, 10]

출력

["######", "###  #", "##  ##", " #### ", " #####", "### # "]

 

 

 취업 춘비를 위해 알고리즘을 하나씩 풀고 있다. 문자열 문제는 짜증나서 자바로 풀고 있지만 그 외의 수열 계산 문제는 C++로 풀려고 노력한다.


문제 풀이를 보니 비트 연산자 문제였었다. 비트 연산자가 뭐였지 하고 한참 생각하다가 검색해보고 풀게 되었다.


다음에는 검색없이 비트 연산자를 기억해낼 수 있기를..


result의 경우에는 arr2 하나하나 받을 때마다 바로 연산을 해줘도 된다.


arr2가 들어오자마자 arr와 비트연산자를 해서 arr에 다시 집어넣는 것이 메모리 절약에 더 좋을 것 같다.


if(((int)pow(2, n-1-i) & result[j] == (int)pow(2, n-1-i))의 부분은 비트 연산자를 통해 특정 자리의 이진수만 남겨놓으려 했을 때


같다면 해당 자리에 이진수가 존재하는 것이고 없으면 아니라는 판단하에 작성했다.

 

#include 
#include <stdio.h>
#include<string>
using namespace std;
int main()
{
	int arr[16];
	int arr2[16];
	int result[16];
	string wall[16] = { "" };
	int n;
	//1 0000# 2 000#0 4 00#00 8 0#000 16 #0000
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		cin>> arr[i];
	}
	for (int i = 0; i < n; ++i)
	{
		cin>> arr2[i];
	}
	for (int i = 0; i < n; ++i)
	{
		result[i] = arr[i] | arr2[i];
	}
	for (int j = 0; j < n; ++j)
	{
		for (int i = 0; i < n; ++i)
		{
			if (((int)pow(2, n - 1 - i) & result[j]) == (int)pow(2, n - 1 - i))
			{
				wall[j] += "#";
			}
			else
			{
				wall[j] += " ";
			}
		}
	}
	for (int i = 0; i < n; ++i)
	{
		cout << wall[i] << endl;
	}

    return 0;
}


+ Recent posts