0%

문제정의


먼저 배포해야하는 작업의 진행률과 작업속도가 주어진다. 뒤에 있는 개발이 완료되지 않더라도 앞순서의 개발이 완료되면 배포해야한다. 각 배포 시기마다 몇 개의 기능이 배포되는지 계산하는 문제이다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.*;

public class FunctionDevelope {

//프로그래머스 level2 문제풀이 기능 개발

public static void main(String[] args)
{
int[] progresses = {93, 30, 55};
int[] speeds = {1, 30, 5};
Queue<Integer> q = new LinkedList<>();
ArrayList<Integer> answer = new ArrayList<>();
int r_date;
for(int i = 0; i < progresses.length; i++)
{
r_date = (int)Math.ceil((100-progresses[i])/(double)speeds[i]);
q.add(r_date);
}
r_date = q.peek();
int cnt = 0;
while(!q.isEmpty())
{
if(r_date >= q.peek())
{
cnt++;
q.remove();
}
else
{
answer.add(cnt);
cnt = 1;
r_date = q.poll();
}

}
answer.add(cnt);
}

}
일단 각 기능이 언제 개발이 완료하는지 계산해야한다. 이 때, 배포는 하루의 끝에 완료된다는 가정이 있기 때문에 올림으로 처리해야한다. 정수간의 나눗셈은 정수만 반환하므로, 실수로 캐스팅하는 걸 잊어선 안된다. 그리고 큐에 완성일을 추가한다.

이 문제는 쉽게 큐에서 오름차순을 찾아내는 문제이다. 배포일 r_date를 큐의 첫번째 원소로 초기화한다. 그리고 배포되는 기능의 개수를 저장하는 cnt를 같이 선언한다. 큐에서 원소들을 보면서 r_date보다 빠른 것들은 같이 배포해야하는 것들이므로 cnt를 늘린다. 만약 r_date보다 늦게 배포하는 것은 배포일을 따로 하는 것이므로, 그 때 cnt를 어레이리스트에 저장하고 cnt를 1로 초기화한다. 비교할 r_date는 해당 원소로 업데이트한다.

이 문제의 시간복잡도는 큐에 들어갔다 나오는 최대 반복을 생각하면 된다. progresses의 길이를 n이라 했을 때, 총 삽입과 삭제의 수는 2n이다. 따라서 시간복잡도는 \(O(n)\)이 된다.

테스트



이 문제같은 경우 시간을 너무 많이 소요했다. 두번째 조건을 생각하기 힘들었는데, 질문하기에서 누군가가 올려준 테스트케이스 덕분에 통과할 수 있었다.

문제정의


자신의 주가가 언제 적자가 나는지 계산하는 문제이다. 예를 들면 문제에서 가격이 4일 때 주식을 사면 4초동안 주가가 떨어지지 않으므로 4이다. 필자는 처음에 문제를 잘못 이해하여 하나 빼고 다 틀렸다. 질문하기를 보니 다른 사람도 똑같이 그런 것 같았다. 지문의 개선이 필요해보인다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.*;

public class StockPrice {

//프로그래머스 문제풀이 level2 주식가격

public static void main(String[] args)
{
int[] prices = {1, 2, 3, 2, 3};

int[] answer = new int[prices.length];
int n = prices.length;
for(int i = 0; i < n-1; i++)
{
for(int j = i+1; j < n; j++)
{
if(prices[i] > prices[j])
{
answer[i] = j-i;
break;
}
}
if(answer[i] == 0)
answer[i] = prices.length-(i+1);
}
}

}
스택과 큐를 활용하라는 문제라고 나와있긴 하지만, 필자는 활용하진 않았다. 최대 길이가 만개이기 때문에 \(n^2\)의 시간복잡도도 충분히 돌아갈거라 생각했기 때문이다. 코드는 간단하다. 각 주식 가격에서 처음으로 주가가 떨어지는 포인트를 발견하면, 그 두 값의 차를 기록하는 것이다. 만약 if문을 거치치 않았다면 끝까지 주가가 떨어지지 않는다는 것이므로 전체에서 인덱스+1을 빼준다.

이 문제의 시간복잡도는 \(O(n^2)\)이다.

테스트



이걸 스택 큐로 분류해 놓은 이유를 잘 모르겠다. 스택/큐를 이용하면 더 빠른 시간안에 풀 수 있는것일까?

문제정의


다리의 길이와 버틸 수 있는 최대 무게, 그리고 지나가려는 트럭의 무게가 주어진다. 트럭이 '순서대로' 다리를 건너가려고 할 때, 걸리는 최소 시간을 구하라.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.*;

public class CrossBridge {

//프로그래머스 문제풀이 level2 다리를 지나는 트럭

public static void main(String[] args)
{
int bridge_length = 5;
int weight = 5;
int[] truck_weights = {2,2,2,2,1,1,1,1,1};
int answer = 0;
Queue<Integer> q = new LinkedList<>();
int item = 0, w = 0, out = 0;
int in_time = 1;
while(item < truck_weights.length)
{
while((w+truck_weights[item] > weight)
|| (q.isEmpty() ? false : in_time >= q.peek()+bridge_length))
{
w -= truck_weights[out++];
in_time = q.poll() + bridge_length;
}
q.add(in_time);
w += truck_weights[item];

in_time++;
item++;
}
answer = --in_time + bridge_length;

System.out.println(answer);
}

}

처음에 필자는 저 순서대로를 못 읽고, 정렬을 했다가 주구장창 틀렸다. 역시 문제는 꼼꼼히 읽자. 필자는 각 트럭별로 들어오는 시간을 계산하여, 가장 마지막 시작시간에 다리의 길이를 더해주는걸로 정답을 내었다. 변수 설명을 하자면, item은 각 트럭의 id인덱스 번호이다(지금 생각해보니 truck_id가 더 나았을 것 같다.). w는 현재 다리가 버티고 있는 무게이고, out은 현재 다리를 빠져나간 트럭의 인덱스를 나타낸다. in_time은 트럭이 다리로 진입할 때의 시간이다. 이제 트럭을 큐에 넣었다 빼며, 맨 마지막으로 들어오는 트럭의 진입시간(in_time)을 계산하면 된다. 일단 while문을 넘기고 24-28번째 줄을 들여다보자. 큐에 진입하는 시간을 넣고, 다음 트럭이 오는 in_time을 계산하기 위해 item과 in_time을 1씩 증가하였다. 기본적으로 weight가 무한대라면 저것만 쓰면 될 테지만, 다리가 버틸 수 있는 무게가 있기에 while문이 필요한 것이다. while문의 첫번째 조건은 이해가 간다. 들어올 트럭의 무게를 현재 무게에 합했을 때 한계치를 넘을 경우, 앞에 있는 트럭이 빠져나가고 넣어야 한다. 뒤에 있는 조건문이 어려울 수도 있는데 이를 설명하기 위해 코드에서 쓴 테스트 케이스를 그림으로 설명하겠다.



트럭의 무게들과 첫번째 조건만을 활용했을 경우 나타나는 오류를 나타낸 그림이다. 아래가 각 시간마다 다리에 있는 트럭의 위치를 나타낸 것이니 참고하면 되겠다. while문의 두번째 조건을 무시하고 첫번째 조건만을 신경쓴 채로 큐에 원소를 넣고 빼고 하다보면 idx = 6일 때 이상한 모습을 볼 수 있다. 6이 진입할 때 무게는 3이어야 하는데 5가 된 것이다. 3번 트럭이 빠지지 않은 것이 문제인데, 첫번째 조건만 가지고는 이것을 걸러낼 수 없다. 큐에서 원소를 뺄 때, 무게만 고려하는 것이 아니라 현재 in_time시간에 빠져나간 트럭들을 제거해주는 것도 같이 해야한다. 그래서 두번째 조건이 나온 것이다. 큐가 만약에 비어있다면 false를 반환하고, 아니라면 in_time에 나가야하는 트럭이 있는지 유무를 확인한 뒤, 그 원소가 큐에 남아있다면 큐에서 그 원소를 삭제한다. 모든 원소의 진입 시간이 계산되었으니, 정답을 계산한다. 마지막 반복문을 돌 때, in_time이 1 증가되었으니 이를 1줄여주고, 다리 길이를 더하면 정답이다.

이 문제의 시간복잡도를 구하기 힘들어할 수도 있는데, 어렵지 않다. 큐에 들어갔다 나오는 최대 반복을 생각하면 된다. truck_weights의 길이를 n이라 했을 때, 다 들어왔다가 다 나간다해도 2n이다. 따라서 시간복잡도는 \(O(n)\)이 된다.

테스트



이 문제같은 경우 시간을 너무 많이 소요했다. 두번째 조건을 생각하기 힘들었는데, 질문하기에서 누군가가 올려준 테스트케이스 덕분에 통과할 수 있었다.

문제정의


1x1cm가 단위인 모눈종이에 그린 사각형의 가로, 세로길이가 주어진다. 여기에 꼭짓점을 잇는 대각선을 그었을 때, 선이 그이지 않은 1x1크기의 사각형의 개수를 구하는 문제이다. 이 문제는 직선의 방정식을 이용하여 문제를 풀었다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class FineRectangle {

//프로그래머스 문제풀이 level2 멀쩡한 사각형

public static void main(String[] args)
{
int w = 8;
int h = 12;

long answer = 0;
double a = h*(-1)/(double)w;
int b = h;

for(int i = 1; i < h; i++)
{
double x = (i-b)/a;
answer += (long)x;
}
answer *= 2;

System.out.print(answer);
}

}
코드의 길이는 짧은 편이다. 일단 필자는 사각형을 2차원 좌표로 나타냈을 때, (0,h) 와 (w,0)을 지나는 직선으로 생각했다. 그러면 기울기 a와 y절편 b를 구할 수 있다. 그리고 y좌표가 1부터 h-1까지(왜냐하면 y좌표가 h이면 어차피 멀쩡한 사각형이 없다.) 각각의 x좌표를 구한다. 그렇다면 그 x좌표가 해당 높이의 대각선이 존재하는 위치이다. 그렇다면 계산한 x가 만약 6.12xxxxx 라면 멀쩡한 사각형은 해당 위치에서 12(6*2)개가 있을 수 있다. 필자는 직각삼각형에 존재하는 사각형을 모두 구한 뒤 이를 2배 했다. 필자와 비슷한 생각을 했다면 아마 프로그래머스에서 테스트 케이스 6번을 틀렸을 수도 있는데, 이는 부동소수점 오차가 영향을 줬기 때문이다. 필자도 x값을 구하는 과정에서 (i/a) - (b/a)를 썼는데 틀렸었다. 그래서 나누기를 최소화하는 방향으로 둘을 합치니 통과가 되었다. 실수형 연산을 할 땐 조심 또 조심하자.

최종적인 시간복잡도는 \(O(h)\)이다.

테스트



최대공약수를 통해서 이 문제를 푼 사람도 있었다. 도대체 그런 규칙은 어떻게 알아내는 지 신기하다. 패턴이 존재하는 것 같은 느낌은 받았지만, 필자는 그 규칙까지 찾아내지 못했다. 덕분에 유클리드 호제법도 익힐 수 있었다.

문제정의


스킬트리의 순서를 만족하는 문자열이 몇개가 되는지 반환하는 문제이다. 롤에서 먼저가야할 템트리를 생각하면 쉬울 것 같다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.*;

public class SkillTree {

//프로그래머스 문제풀이 level2 스킬트리

public static void main(String[] args)
{
String skill = "CBD";
String[] skill_trees = {"BACDE", "CBADF", "AECB", "BDA"};
int answer = 0;
boolean isAdd = true;
HashMap<Character, Integer> map = new HashMap<>();
char[] skill_arr = skill.toCharArray();
int v = 0;
for(char c : skill_arr)
{
map.put(c, v);
v++;
}
for(String s : skill_trees)
{
int pre_v = -1;
char[] skill_tree_arr = s.toCharArray();
for(char c : skill_tree_arr)
{
int value = map.getOrDefault(c, -1);
if(value != -1 && value - pre_v != 1)
{
isAdd = false;
break;
}
else if(value != -1)
pre_v = value;
}
if(isAdd)
answer++;
isAdd = true;
}
System.out.print(answer);
}

}

필자는 해시를 이용하여 문제를 풀었다. 자세한 사항은 부분부분 보면서 따라가자.
1
2
3
4
5
6
7
8
9
boolean isAdd = true;
HashMap<Character, Integer> map = new HashMap<>();
char[] skill_arr = skill.toCharArray();
int v = 0;
for(char c : skill_arr)
{
map.put(c, v);
v++;
}
isAdd는 문자열이 조건을 만족하는지 여부를 저장하는 변수이다. 해쉬를 쓰기 위해 해시 맵을 선언했다. skill의 문자 하나하나를 hash에 매핑 시킨다. 예를 들어 "CBD"가 주어진다면, [C:0, B:1, D:2]와 같이 매핑된다. 이렇게 매핑해두면 각 키의 값을 비교하여 1씩 증가하는 지 확인하면 된다. 매핑하지 않은 키는 -1을 반환하도록 하여 이 연산에서 빠지도록 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for(String s : skill_trees)
{
int pre_v = -1;
char[] skill_tree_arr = s.toCharArray();
for(char c : skill_tree_arr)
{
int value = map.getOrDefault(c, -1);
if(value != -1 && value - pre_v != 1)
{
isAdd = false;
break;
}
else if(value != -1)
pre_v = value;
}
if(isAdd)
answer++;
isAdd = true;
위에서 설명한 부분의 코드이다. pre_v의 초기값으로 -1을 넣어준 뒤, 주어진 스킬트리에서 문자를 하나씩 꺼내본다. 만약 맵에 있는데 초기값과 1차이가 나지 않는다면, 조건을 만족하지 않으므로 isAdd를 false하고 반복문을 탈출한다. 맵에 있는 값이면 pre_v를 업데이트 해주는 것을 잊지 않도록 하자. 다음엔 isAdd를 통해 정답을 세나간다. 최종적인 시간복잡도를 계산해보면 skill_trees의 길이를 n이라하고 skill_trees에 있는 문자열의 길이를 m이라하면, \(O(n*m)\)이다.

테스트



정규식을 통해 문제를 푼 사람도 있어서 신기했다. skill_trees의 각 원소에서 skill이 아닌 문자들을 지운뒤, indexOf를 활용하여 순서를 유지하는지 확인하였다. 이럴 경우 코드 수가 많이 줄어들어 가독성이 좋았다. 나도 정규식을 좀 더 공부해 봐야겠다.

문제정의


10진법을 1,2,4에 존재하는 나라의 규칙에 따라 새로 나타내는 문제이다. 규칙을 찾아내는 것이 관건인데, 필자는 다른 사람들이랑 다른 관점으로 풀었다(코드 길이는 훨씬 길다).

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class Country124Num {

//프로그래머스 문제풀이 level2 124 나라의 숫자

public static void main(String[] args)
{
int n = 6;
StringBuilder buff = new StringBuilder();
while(n > 0)
{
int r = n % 3;
n/=3;
buff.append(String.valueOf(r));
}
char[] arr = buff.reverse().toString().toCharArray();
for(int i = arr.length-1; i >= 0; i--)
{
if(arr[i] == '0')
{
int p = i;
for(int j = i; j >=0; j--)
{
if(arr[j] != '0')
{
p = j;
break;
}
}
if(p != i)
{
for(int k = p; k < i; k++)
{
int pre_num = Integer.parseInt(String.valueOf(arr[k]));
char pre_char = Character.forDigit(pre_num-1, 10);
arr[k] = pre_char;

int aft_num = Integer.parseInt(String.valueOf(arr[k+1]));
char aft_char = Character.forDigit(aft_num+3, 10);
arr[k+1] = aft_char;

}
}
}
}
int start_idx = 0;
for(int i = 0; i < arr.length; i++)
{
if(arr[i] != '0')
{
start_idx = i;
break;
}

}
for(int i = start_idx; i < arr.length; i++)
{
if(arr[i] == '3')
arr[i] = '4';
}
String answer = new String(arr);
answer = answer.substring(start_idx, arr.length);

System.out.println(answer);
}

}
부분부분 따라가보자.
1
2
3
4
5
6
7
while(n > 0)
{
int r = n % 3;
n/=3;
buff.append(String.valueOf(r));
}
char[] arr = buff.reverse().toString().toCharArray();
일단 정상적인 3진법으로 만든다. 기본적으로 이 문제는 3진법의 응용문제이다. 왜냐하면 사용하는 숫자가 총 3개이기 때문이다. 이를 대응관계로 보면 3진법의 수는 {0, 1, 2}인데, 이 문제의 경우 {4, 1, 2}로 대응된다. 그러면 3진법으로 바꿔서 4,1,2로만 바꾸면 되지 않느냐 할 수 있는데 그럴 수 없다. 3의 배수가 아닌 수는 앞서 말했던 방법이 통한다. 하지만 6을 예시로 들어보자 6의 경우 3진법으로 바꾸면 20이다. 이를 대응 관계에 비춰 바꿔보면 24이지만, 정답은 14이다. 0이 존재하지 않기 때문에 이를 처리하기 위한 방법이 필요하다. 실은 이 부분에서 꽤 고생을했는데 필자가 생각한 방법은 이렇다. 1. 3진수로 바꾼 배열을 뒤에서부터 보면서 0이 있는지 확인한다. 2. 0이 있으면 앞에서부터 빌려올 수 있는 수가 있는지 확인한다. 3. 빌려올 수 있는 수가 있다면 빌려온다. 이 때 내려받는 수는 10진법에선 10이지만, 지금은 3진법이므로 3이 되겠다. 4. 3을 빌려준 수는 1 깎인다. 5. 0을 다 없애고 나면 3인 수를 4로 바꿔준다.

이해를 돕기 위해 그림을 첨부한다.

초등학생 때 했던 10진수 빼기를 생각하면 된다.

이 부분에서 시간복잡도는 3으로 나눴을 때의 자릿수만큼이므로 \(O(log3(n))\)이다.

말이 길었는데 이것만 이해하면 나머지는 코드만 읽으면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
for(int i = arr.length-1; i >= 0; i--)
{
if(arr[i] == '0')
{
int p = i;
for(int j = i; j >=0; j--)
{
if(arr[j] != '0')
{
p = j;
break;
}
}
if(p != i)
{
for(int k = p; k < i; k++)
{
int pre_num = Integer.parseInt(String.valueOf(arr[k]));
char pre_char = Character.forDigit(pre_num-1, 10);
arr[k] = pre_char;

int aft_num = Integer.parseInt(String.valueOf(arr[k+1]));
char aft_char = Character.forDigit(aft_num+3, 10);
arr[k+1] = aft_char;

}
}
}
}
3진수로 바꾼 수에서 뒤에서부터 보면서 0이 있는지 확인한다. 만약 0이 있다면, 자신의 앞에 있는 수에서 3을 빌릴 수 있는지 확인한다. 3을 빌릴 수 있는 숫자가 발견되면, 그 인덱스를 기억해 놨다가. 빌리고 빼는 것을 반복한다. 위 코드에서 보이는 k를 iterator로 쓰는 for문이 바로 그 부분이다(3,4번과정). 이 분의 시간 복잡도는 \(O(log3(n))\)인데, 해봤자 \(2log3(n)\)이기 때문이다. 앞쪽에서 빌리면 해당수와 앞쪽 사이에는 0이 절대로 존재할 수 없기 때문인데, 혹시 더 이해가 안간다면 이메일을 통해 언제든지 질문하기를 바란다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(int i = 0; i < arr.length; i++)
{
if(arr[i] != '0')
{
start_idx = i;
break;
}

}
for(int i = start_idx; i < arr.length; i++)
{
if(arr[i] == '3')
arr[i] = '4';
}
거의 다왔다. 4,5과정을 거쳐도 코드적으로 해결해야하는 것이 있는데 자릿수가 있다. 아까 그림을 보면 알 수 있듯이 원래 9의 자릿수는 3자리였지만 변환 후의 자릿수는 2자릿수로 문자열을 편집해줄 필요가 있다. 앞에서 부터 0이 안나오는 인덱스를 저장해두어 나중에 substring으로 잘라준다. 그리고 3을 4로 치환한다(5번과정). 이 부분도 시간복잡도는 \(O(log3(n))\)이다.

따라서 최종 시간복잡도는 \(O(log3(n))\) 이다.

테스트



60줄을 넘었는데 다른 풀이는 고작 10줄안에 끝나서 절망했다. 하지만 할 수 있는건 계속 문제를 풀어나가는 수 밖에 없다. n에 나머지 연산을 해주고 나눌 때 3의 배수이면 -1을 해줬는데, 자릿수를 올리지 않기 위해서 그랬다고 하지만, 경험적으로 풀어봐야 이해할듯 말듯하고, 원리에 대해 명쾌하게 설명해주는 블로그가 없어서 아쉬웠다. 이 부분은 좀 더 고민해보고 이 포스트에 보충해서 쓰도록 하겠다.

문제정의


다트게임을 해서 총 몇점을 득점했는지 계산하는 문제이다. 점수는 0~10점이며 옵션에 따라 점수가 바뀔 수 있다. S는 해당 점수의 1제곱, D는 제곱, T는 세제곱을 하는 것이며, 은 이전 점수와 해당 점수의 2배(만약에 이전 점수가 없으면 해당 점수만 2배 한다.)이다. #은 해당 점수를 빼는 것이다. 과 #은 중첩될 수 있다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.util.*;

public class DartGame {

//프로그래머스 문제풀이 level1 다트 게임
public static void main(String[] args)
{
String dartResult = "1S2D*3T";
int answer = 0;
char[] arr = dartResult.toCharArray();
Stack<Integer> s = new Stack<Integer>();
for(int i = 0; i < arr.length; i++)
{
if(Character.isDigit(arr[i]))
{
int n = Integer.parseInt(String.valueOf(arr[i]));
if(Character.isDigit(arr[i+1]))
{
n = 10;
i++;
}
if(arr[i+1] == 'S')
s.push(Integer.parseInt(String.valueOf(n)));
else if(arr[i+1] == 'D')
s.push((int)Math.pow(n, 2));
else
s.push((int)Math.pow(n, 3));
}
else if(arr[i] == '*')
{
if(s.size() == 1)
{
int n = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n*2);
}
else
{
int n1 = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
int n2 = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n2*2);
s.push(n1*2);
}
}
else if(arr[i] == '#')
{
int n = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n*(-1));
}

}
while(!s.empty())
{
answer += s.peek();
s.pop();
}
System.out.print(answer);
}

}

부분부분 살펴보자. 일단 스트링을 문자배열로 쪼갠다음 이 문자를 하나씩 보면서 계산할 것이다. *옵션을 계산하기 위해 스택도 함께 선언하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(Character.isDigit(arr[i]))
{
int n = Integer.parseInt(String.valueOf(arr[i]));
if(Character.isDigit(arr[i+1]))
{
n = 10;
i++;
}
if(arr[i+1] == 'S')
s.push(Integer.parseInt(String.valueOf(n)));
else if(arr[i+1] == 'D')
s.push((int)Math.pow(n, 2));
else
s.push((int)Math.pow(n, 3));
}
에 대해서 처리해주는 부분이다. 일단 들어온 문자가 숫자인지 확인한다. 이때 10에 대한 처리를 따로 한다. 똑같은 1이 들어와도 1일 수도 있고, 10일 수도 있기 때문이다. 이를 알기 위해 다음 문자도 숫자인지 확인하고 숫자라면 n에 10을 대입한다. 그리고 S,D,T에 따른 제곱처리를 하고 스택에 쌓는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else if(arr[i] == '*')
{
if(s.size() == 1)
{
int n = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n*2);
}
else
{
int n1 = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
int n2 = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n2*2);
s.push(n1*2);
}
}

숫자와 S,D,T에 대한 처리를 모두 했으니, 다음은 에 대해서 처리하자. 은 이전 점수와 현재 점수를 2배 처리하는 것이다. 이전 점수가 없을 수도 있으니 스택의 사이즈에 따라 달리한다.

1
2
3
4
5
6
else if(arr[i] == '#')
{
int n = Integer.parseInt(String.valueOf(s.peek()));
s.pop();
s.push(n*(-1));
}

#인경우 스택의 top원소를 빼내어 -1을 곱한 뒤, 다시 집어넣는다. 이 과정을 dartResult가 끝날 때 까지 하므로 여기서의 시간복잡도는 dartResult의 길이를 n이라 할 때, \(O(n)\)이다.

1
2
3
4
5
while(!s.empty())
{
answer += s.peek();
s.pop();
}
다트 게임의 점수를 모두 합산하면 정답이다. 이 부분도 \(O(n)\)이다. 따라서 최종 시간복잡도는 \(O(n)\)이다.

테스트



프로그래머스 마지막 level1을 풀었다. 그래도 올해가 끝나기 전에 풀어서 기쁘다. 앞으로는 더 어려운 문제들이 있겠지만 포기하지말고 끝까지 풀어보자. 물러서지 말자!

문제정의


유저가 스테이지를 어디까지 완료하였는지 담아놓은 배열 stage와 총 스테이지 개수 N이 주어질 때, 실패율을 계산하여 내림차순으로 정렬한다. 만약 실패율이 같은 경우 스테이지 번호가 작은 순서를 우선으로 한다. 실패율을 계산하는 수식은 다음과 같다.

(스테이지에 도달했으나 아직 클리어하지 못한 플레이어의 수) / (스테이지에 도달한 플레이어 수)

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.*;

public class FailRate {

//프로그래머스 문제풀이 level1 실패율
public static void main(String[] args)
{
int N = 4;
int[] stages = {4, 4, 4, 4, 4};
Integer[] answer = new Integer[N];
int[] clear = new int[N+2];
double[] fail_rate = new double[N+1];
for(int stage : stages)
{
for(int i = 1; i < stage; i++)
clear[i]++;
}
clear[0] = stages.length;
for(int i = 1; i <= N; i++)
{
if(clear[i-1] == 0)
fail_rate[i] = 0;
else
fail_rate[i] = (double)(clear[i-1]-clear[i]) / (double)clear[i-1];
}
for(int i = 1; i <= N; i++)
answer[i-1] = i;
Arrays.sort(answer, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
if(fail_rate[o1] < fail_rate[o2])
return 1;
else if(fail_rate[o1] > fail_rate[o2])
return -1;
else
return Integer.compare(o1, o2);
}
});
}

}
부분부분 살펴보자
1
2
3
4
5
for(int stage : stages)
{
for(int i = 1; i < stage; i++)
clear[i]++;
}
스테이지를 클리어하는 사람 수를 저장해두는 배열 clear를 선언한다. stage-1이 여태까지 클리어해온 스테이지이므로 1부터 stage-1까지 clear를 1씩 올린다. 이 부분에서 시간복잡도는 stages의 길이를 m이라 할 때 \(O(N*m)\)이다.

1
2
3
4
5
6
7
8
clear[0] = stages.length;
for(int i = 1; i <= N; i++)
{
if(clear[i-1] == 0)
fail_rate[i] = 0;
else
fail_rate[i] = (double)(clear[i-1]-clear[i]) / (double)clear[i-1];
}

실패율을 계산하는 부분이다. i번째 스테이지에 도달한 사람은 clear[i-1]이다. 3레벨에 도달하는 사람이 2레벨을 통과한 사람인 것처럼(아직 3단계를 통과한 사람들은 아니다!). 지금은 성공률이 아닌 실패율을 계산하는 것이므로 이 사람들 중 통과한 사람의수가 분자에 들어가야한다. 따라서 clear[i-1]-clear[i]가 분자에 올라간다. 스테이지를 통과한 사람이 없을 경우엔 실패율을 0으로 한다. 이 부분에서 시간복잡도는 \(O(N)\)이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int i = 1; i <= N; i++)
answer[i-1] = i;
Arrays.sort(answer, new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
if(fail_rate[o1] < fail_rate[o2])
return 1;
else if(fail_rate[o1] > fail_rate[o2])
return -1;
else
return Integer.compare(o1, o2);
}
});

정렬방식이 특이하게 주어진 경우에는 비교자를 직접 정의하는 것이 좋다. 대신 자바에서는 Integer, Double과 같은 Wrapper 클래스만 비교자 정의를 허용한다. 따라서 int라면 Integer로 꼭 바꿔주자. 자세한 비교 코드는 위 코드를 참고하자. 이 부분에서 시간복잡도는 \(O(NlogN)\)이다.

따라서 최종적인 시간복잡도는 \(O(N*m)\)이다.

테스트



문제정의


암호를 해독하는 문제이다. 10진수 배열 2개를 받아 2진수로 변환한다. 같은 인덱스에 해당하는 원소끼리 bit연산(or)을 한다. 연산 결과로 나온 2진수에서 1은 #으로 0은 공백으로 처리하여 문자열 배열에 저장한다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class SecretMap {

//프로그래머스 문제풀이 level1 비밀지도
public static void main(String[] args)
{
int n = 5;
int[] arr1 = {9, 20, 28, 18, 11};
int[] arr2 = {30, 1, 21, 17, 28};

String[] answer = new String[n];
String[] map1 = new String[n];
String[] map2 = new String[n];
for(int i = 0; i < n; i++)
{
map1[i] = Integer.toBinaryString(arr1[i]);
map2[i] = Integer.toBinaryString(arr2[i]);
}
for(int i = 0; i < n; i++)
{

char[] code1_char = map1[i].toCharArray();
char[] code2_char = map2[i].toCharArray();
int[] code1 = new int[n];
int[] code2 = new int[n];
int k = 0;
for(int j = n-code1_char.length; j < n; j++)
{
code1[j] = Integer.parseInt(String.valueOf(code1_char[k]));
k++;
}
k = 0;
for(int j = n-code2_char.length; j < n; j++)
{
code2[j] = Integer.parseInt(String.valueOf(code2_char[k]));
k++;
}
StringBuffer buff = new StringBuffer();
for(int j = 0; j < n; j++)
{
if(code1[j] == 1 || code2[j] == 1)
buff.append("#");
else
buff.append(" ");
}
answer[i] = buff.toString();
}
}
}
부분부분 살펴보자
1
2
3
4
5
for(int i = 0; i < n; i++)
{
map1[i] = Integer.toBinaryString(arr1[i]);
map2[i] = Integer.toBinaryString(arr2[i]);
}
처음에 10진수를 2진수로 바꿔주는 작업을 한다. 자바에는 2진수로 바꿔주는 메소드가 있으니 이를 활용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char[] code1_char = map1[i].toCharArray();
char[] code2_char = map2[i].toCharArray();
int[] code1 = new int[n];
int[] code2 = new int[n];
int k = 0;
for(int j = n-code1_char.length; j < n; j++)
{
code1[j] = Integer.parseInt(String.valueOf(code1_char[k]));
k++;
}
k = 0;
for(int j = n-code2_char.length; j < n; j++)
{
code2[j] = Integer.parseInt(String.valueOf(code2_char[k]));
k++;
}

살짝 복잡할 수 있는데 천천히 읽어보면 이해할 수 있다. 일단 map1에 들어있는 스트링은 한뭉텅이니 char 배열로 쪼개준다. 배열로 쪼개고 나서 이 2진수의 길이가 n이라고 단정할 수 없기 때문에, for문의 범위가 위와 같다. 만약 n=5인데 만들어진 2진수는 010이면 이는 code의 2부터 집어넣어야한다. code1에는 n-code_char.length부터 넣지만 char 배열에선 0부터 값을 꺼내오는 것이므로 k라는 인덱스르 따로 선언해준다. map2에 대해서도 똑같이 해준다. 이 때 손쉬운 비교를 위해 char를 int형식으로 고쳤다.

1
2
3
4
5
6
7
8
9
StringBuffer buff = new StringBuffer();
for(int j = 0; j < n; j++)
{
if(code1[j] == 1 || code2[j] == 1)
buff.append("#");
else
buff.append(" ");
}
answer[i] = buff.toString();

이제 만들어진 n길이의 2진수로 비교해가며 # 또는 공백을 넣어주면 된다. StringBuffer를 선언하여 append를 해준 뒤, 문자열로 형변환을 한다.

시간복잡도는 2중 for문을 돌기 때문에 \(O(n^2)\)이다.

테스트



다른 사람은 단 3줄안에 이 문제를 푼 것을 보고 약간 자괴감이 들었다. 하지만 성장해 나가고 있는 필자의 모습에 위안을 얻기로 했다. 화이팅!!

문제정의


d는 지원을 바라는 부서들의 예산들이고 총 예산은 budget이다. 최대한 지원해 줄 수 있는 부서가 몇 개인지 반환하는 문제이다.

문제풀이


전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;

public class Budget {

//프로그래머스 문제풀이 level1 예산
public static void main(String[] args)
{
int[] d = {1,3,2,5,4};
int budget = 9;
int answer = 0;
Arrays.sort(d);
for(int money : d)
{
budget -= money;
if(budget < 0)
break;
answer++;
}
System.out.print(answer);
}
}
최대한 많은 부서에게 예산을 지원해주기 위해선 예산 청구가 적은 부서부터 나누어주면 된다. 이를 위해 예산 목록을 오름차순으로 정렬하고 예산이 감당할 수 있는 만큼 돈을 뺀다. 이 문제에서 청구 예산의 일부를 주는 것은 아니었으므로 난이도가 쉬웠다.

시간복잡도는 모든 부서에게 예산을 나눠줄 수 있을 때 d의 크기를 n이라 하면 \(O(n)\)이다.

테스트