https://school.programmers.co.kr/learn/courses/30/lessons/150365

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 미로 정보를 입력받는다.

2. 사전 순으로 가장 빠른 미로 탈출 경로를 반환한다.

 

<해법>

1. 완전탐색 X

=> 평범한 길찾기 문제라는 생각이 듭니다. 하지만 이 문제는 k가 최대 2500이므로, 완전탐색으로 풀 때 최악의 경우 O(4^2500)의 시간복잡도를 가집니다. 따라서, 완전탐색 알고리즘은 배제해야합니다.

 

2. 불가능한 경우 찾기

=> 먼저 "impossible"한 경우부터 생각해보겠습니다.

첫번째로, 출발지점에서 끝지점까지 갈 수 있는 최단거리보다 k가 작을 경우 불가능합니다. 여기서, 출발지점에서 끝지점까지 최단거리를 계산하는 방법은 매우 간단합니다. 지도 상에 장애물이 없으므로, 맨해튼 거리계산법(ㅣx - rㅣ+ ㅣy - cㅣ)를 이용해서 구할 수 있습니다. 따라서, if(최단거리 > k) answer = "impossible" 입니다.

두번째로, (k - 최단거리) % 2 == 1 일 때 불가능합니다. 이유는 k번을 채우기 위해서는 어디선가 왔다갔다를 해야하기 때문입니다. 단순히 k번을 이용한 거리를 구하라고 한다면, 출발지점에서 끝지점까지 간 후 끝지점에서 바로 옆칸으로 왔다갔다하면 구할 수 있습니다. 그런데 이 때, '왔다갔다'를 해야하므로 2번이 필요합니다. 만약 도착지점에서 k가 1이라면 불가능합니다. 따라서, if((k - 최단거리) % 2 == 1) answer = "impossible" 입니다.

 

3. 사전순으로 가장 빠른 경로 찾기

=> 위 경우를 제외하고서는 k번 이동하여 출발지점부터 끝지점까지 갈 수 있습니다. 이제 사전순으로 가장 빠른 경로를 찾아야합니다. 사전순으로 가장 빠르려면, 앞자리가 가장 빨라야합니다. 이 말은 'd(아래)로 갈 수 있으면, d로 가는게 가장 좋다.' 입니다. 또, 'd -> l -> r -> u 순으로 갈 수있는 곳을 탐색해서 간다.' 가 됩니다. 

그렇다면 이제 해당 방향으로 가도 되는지 안되는지 판단하는 일만 남았습니다. 해당 방향으로 가도 되려면, 먼저 맵을 벗어나면 안됩니다. 그리고 또 한가지는 위에 "impossible"한 경우가 아니면 됩니다. 이 말은 다음 위치에서 최단거리 <= 남은 k 면 된다는 뜻입니다. 이렇게 하면 greedy하게 사전순으로 가장 빠른 방향으로 이동하며 정답을 구할 수 있습니다.

 

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
#include <string>
#include <vector>
 
using namespace std;
 
char d[] = { 'd''l''r''u' };
int dx[] = { 1,0,0,-1 };
int dy[] = { 0,-1,1,0 };
 
bool isInner(int x, int y, int n, int m) {
    if (x <= 0 || y <= 0 || x > n || y > m) return false;
    return true;
}
 
string solution(int n, int m, int x, int y, int r, int c, int k) {
    string answer = "";
 
    int shortest = abs(x - r) + abs(y - c);
    if (shortest > k || (k - shortest) % 2) answer = "impossible";
    else {
        while (k--) {
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i];
                int ny = y + dy[i];
                if (!isInner(nx, ny, n, m)) continue;
                int nshortest = abs(nx - r) + abs(ny - c);
                if (nshortest > k) continue;
                x = nx, y = ny;
                answer += d[i];
                break;
            }
        }
    }
 
    return answer;
}

 

Greedy 알고리즘에 대해 알아볼 수 있었습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/150366

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 표를 편집하는 명령어를 입력받는다.

2. 명령어를 실행한 결과를 반환한다.

 

<해법>

1. 알고리즘 선택하기

=> 문제를 읽고 가장 먼저 Union-Find 알고리즘이 떠오릅니다. 아래는 Union-Find 알고리즘에 따라 구현 방법에 대해 정리한 생각입니다.

(1) 'UPDATE r c value'

: 표의 (r, c)의 부모를 찾아 value를 바꾼다.

(2) 'UPDATE value1 value2'

: 표의 모든 value1을 value2로 바꾼다. 표의 크기는 50x50 = 2500이고 명령어의 갯수는 최대 1000개이므로, O(2500x1000)의 시간복잡도로 풀이 가능하다.

(3) 'MERGE r1 c1 r2 c2'

: (r2, c2)의 부모를 (r1, c1)의 부모로 바꾼다. 데이터는 규칙에 따라 바꾼다.

(4) 'PRINT r c'

: (r, c)의 부모를 찾아 value를 출력한다.

(5) 'UNMERGE r c'

: 여기서 막힙니다. 표의 모든 셀과 (r, c)의 부모가 같은지 확인해야하는데, 이 경우 O(2500x2500x1000)의 시간복잡도가 되기 때문입니다(Union-Find의 find함수가 최악의 경우 O(N)이기 때문). 그렇다면 어떻게 해야할까요?

 

2. UNMERGE 해결하기

=> 생각해보면, Union-Find 알고리즘은 Union과 Find에만 관심이 있는 알고리즘입니다. 저희 문제와 같이 UNMERGE하는 부분에는 관심이 없습니다. 따라서, 이 알고리즘의 좋은 부분만 가져와 저희의 문제에 맞게 조금 변형해서 사용해야합니다. UNMERGE 명령어에서 가장 큰 문제점은 셀의 부모가 제 멋대로라는 점입니다.

위 그림처럼 부모를 찾기위해 계속해서 타고들어가야 하기 때문에 3,4,5번이 모두 1번 셀이라는 것을 단번에 알 수 없습니다. 그렇다면 3,4,5번이 모두 2번처럼 1로 표시되면 어떨까요? 정말 간단해집니다. 모든 셀을 돌며 부모가 1인것만 다시 되돌리면 되기 때문입니다. 그리고 이렇게 되면 O(2500x1000)의 시간복잡도가 되므로 풀 수 있습니다. 

 

3. MERGE 교체하기

=> 3,4,5번의 부모가 모두 1로 표현되려면, MERGE부터 바꿔야합니다. 기존 Union-Find의 경우는 부모만 바꾸어 주었다면, 저희는 부모의 자식들 또한 바꾸어주어야합니다.

위 그림처럼 교체하면 MERGE는 O(2500x1000)의 시간복잡도를 가지게 되므로 풀 수 있습니다.

 

4. 구현 전 정리

=> 아래는 구현 전 정리한 생각입니다.

(1) 셀은 1차원 배열로 바꿔서 생각한다. 부모를 정수로 편하게 관리할 수 있기 때문.

(2) 부모 배열과 데이터 배열 두 개를 관리한다.

(3) 빈 데이터는 "EMPTY"를 넣는다. 데이터는 알파벳 소문자와 숫자로만 이루어져있으므로 가능하고, 출력할 때 편리하다.

자세한 구현은 아래 코드를 참고해주세요.

 

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <string>
#include <vector>
#include <sstream>
 
using namespace std;
 
int link[2501];
string value[2501];
 
int flatPos(int r, int c) {
    return (r - 1* 50 + c;
}
 
vector<string> solution(vector<string> commands) {
    vector<string> answer;
 
    //초기화
    for (int i = 1; i <= 2500; i++) {
        link[i] = i;
        value[i] = "EMPTY";
    }
 
    //해법
    for (string &command : commands) {
        vector<string> v;
        stringstream ss(command);
        string token;
        while (ss >> token) v.push_back(token);
 
        if (v[0== "UPDATE") {
            if (v.size() == 4) { //1. UPDATE r c value
                int pos = flatPos(stoi(v[1]), stoi(v[2]));
                string val = v[3];
                int parent = link[pos];
                value[parent] = val;
            }
            else { //2. UPDATE value1 value2
                string val1 = v[1], val2 = v[2];
                for (int i = 1; i <= 2500; i++) {
                    if (value[i] == val1) value[i] = val2;
                }
            }
        }
        else if (v[0== "MERGE") { //3. MERGE r1 c1 r2 c2
            int pos1 = flatPos(stoi(v[1]), stoi(v[2])), pos2 = flatPos(stoi(v[3]), stoi(v[4]));
            int parent1 = link[pos1], parent2 = link[pos2];
            if (parent1 != parent2) {
                for (int i = 1; i <= 2500; i++) {
                    if (link[i] == parent2) link[i] = parent1;
                }
                string val1 = value[parent1], val2 = value[parent2];
                if (val1 == "EMPTY" && val2 != "EMPTY") {
                    value[parent1] = value[parent2];
                }
                else {
                    value[parent2] = value[parent1];
                }
            }
        }
        else if (v[0== "UNMERGE") { //4. UNMERGE r c
            int pos = flatPos(stoi(v[1]), stoi(v[2]));
            int parent = link[pos];
            string val = value[parent];
            for (int i = 1; i <= 2500; i++) {
                if (link[i] == parent) {
                    link[i] = i;
                    value[i] = "EMPTY";
                }
            }
            value[pos] = val;
        }
        else { //5. PRINT r c
            int pos = flatPos(stoi(v[1]), stoi(v[2]));
            int parent = link[pos];
            answer.push_back(value[parent]);
        }
    }
 
    return answer;
}

 

Union-Find에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/150367

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 숫자들을 입력받는다.

2. 각 숫자를 하나의 이진트리로 표현할 수 있는지의 여부를 반환한다.

 

<해법>

1. 이진트리로 표현할 수 있는 수와 없는 수의 규칙 찾기

=> 번뜩이는 알고리즘이 떠오르지 않습니다. 따라서, 예제를 보면서 하나하나씩 직접 해보며 규칙을 찾아야합니다. 

7 = 111 (O)

42 = 0101010 (O)

5 = 101 (X)

그림과 쉽게 비교하기 위해 더미노드(0)도 추가했습니다. 규칙이 조금씩 보이시나요? 읽는 순서를 고려했을 때, 루트노드는 항상 2진수의 중앙에 옵니다. 루트노드에 더미노드(0)이 올 수 있을까요? 없습니다. 루트노드가 0인 수는 0뿐인데, 주어지는 숫자는 1이상 이기 때문입니다. 따라서, 규칙 하나를 찾았습니다. '2진수의 중앙은 0이 될 수 없다.'

 

두 번째 예시를 보며 규칙을 찾아보겠습니다. 

63 = 0111111 (O)

111 = 1101111 (O)

95 = 1011111 (X)

95는 2진수의 중앙이 1인데 불가능하다고 나옵니다. 왜일까요?

위와 같이 그려지기 때문입니다. 규칙 하나를 더 찾았습니다. '0이 1의 부모노드로 존재할 수 없다.' 또한, 그림을 직접 그려보면 트리를 왼쪽, 오른쪽 서브트리로 분할하고 정복해서 풀어야겠다는 느낌을 강하게 받을 수 있습니다. 그렇다면, 이제 어떻게 분할정복할지 알아보겠습니다.

 

2. 분할정복하는 방법

=> 위의 예시들로 몇가지 규칙을 찾았습니다. 찾은 규칙을 바탕으로 자세하게 분할정복하는 방법을 알아보겠습니다. 110 = 1101111을 예시로 들겠습니다.

(1) 분할하는 방법

110(왼쪽 서브트리) / 1(부모) / 111(오른쪽 서브트리) 로 분할합니다. 이후 왼쪽과 오른쪽을 정복한 후 이진트리로 표현할 수 있는지 판단합니다.

(2) 부모가 1인 경우

일단 이진트리로 표현할 수 있습니다. 왼쪽과 오른쪽 서브트리 또한 이진트리로 표현할 수 있다면, 이진트리로 표현할 수 있습니다.

(3) 부모가 0인 경우

부모가 0인 경우는 모두 불가능할까요? 아닙니다.

위와 같이 자식도 모두 0인 경우에는 이진트리로 표현할 수 있기 때문입니다. 따라서, 부모가 0인 경우에는 자식이 모두 0인 경우에는 가능하고, 나머지 경우는 불가능합니다.

 

3. 구현 전 정리

=> 아래는 구현 전 정리한 생각입니다.

(1) 주어진 숫자를 이진수로 만든다. (ex 5 -> 101) (코드의 hexToDec함수 참고)

(2) 만든 이진수를 포화 이진수로 만든다. 포화 이진수란 포화 이진트리로 표현할 수 있는 숫자이다. 포화이진트리의 노드 갯수는 2^n - 1개 입니다. 따라서, 포화이진트리 노드의 갯수에 맞춰서 앞에 0을 추가한다. (ex 11 -> 1011 -> 0001011) (코드의 changeToFullDec함수 참고)

(3) 만든 포화 이진수를 분할정복한다. 왼쪽, 중앙, 오른쪽으로 분할해서 정복한다. (ex 0001011 -> 000 / 1 / 011) (코드의 canDraw함수 참고)

자세한 구현은 아래 코드를 참고해주세요.

 

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
#include <iostream>
#include <string>
#include <vector>
 
using namespace std;
 
string hexToDec(long long num);
string changeToFullDec(string str);
bool isAllZero(string str);
bool canDraw(string str);
 
vector<int> solution(vector<long long> numbers) {
    vector<int> answer;
    for (long long& num : numbers) {
        string dec = hexToDec(num);
        string fullDec = changeToFullDec(dec);
        answer.push_back(canDraw(fullDec));
    }
    return answer;
}
 
string hexToDec(long long num) {
    string ret = "";
    while (num) {
        ret = to_string(num % 2+ ret;
        num /= 2;
    }
    return ret;
}
 
string changeToFullDec(string str) {
    string ret = str;
    int idx = 2;
    while (true) {
        if (str.length() <= idx - 1break;
        idx *= 2;
    }
    for (int i = 0; i < idx - 1 - str.length(); i++) ret = "0" + ret;
    return ret;
}
 
bool canDraw(string str) {
    if (str.length() == 1 || isAllZero(str)) return true;
 
    int midIdx = str.length() / 2;
    string left = str.substr(0, midIdx);
    string right = str.substr(midIdx + 1);
 
    if (str[midIdx] == '1' && canDraw(left) && canDraw(right)) return true;
    else return false;
}
 
bool isAllZero(string str) {
    for (char c : str) {
        if (c != '0'return false;
    }
    return true;
}

 

분할정복에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/150368

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 사용자 정보와 이모티콘 정보를 입력받는다.

2. 행사 목적의 최대치를 반환한다.

 

<해법>

1. 알고리즘 선택하기

=> 문제를 읽고 가장 먼저 떠오르는 알고리즘은 모든 경우를 다 해보는 브루트 포스 입니다. 브루트 포스를 사용할 수 있는지 확인하기 위해 시간복잡도를 계산해야합니다. 10, 20, 30, 40%로 7개의 이모티콘 모두 적용해 보아야하고 모든 사용자 100명에 있어서 7개의 이모티콘을 확인해봐야합니다. 시간 복잡도는 O(4^7 x (100 x 7)) = O(11,468,800) 입니다. 따라서, 브루트 포스 알고리즘을 사용할 수 있고 선택했습니다.

 

2. 구현 전 정리

=> 아래는 구현 전 정리한 생각입니다.

(1) 브루트 포스 알고리즘을 사용한다. 7개의 이모티콘에 10, 20, 30, 40% 할인 비율을 모두 적용시키는 것은 재귀함수로 구현한다.

(2) 이모티콘의 가격은 100의 배수로 주어지므로, 가격 연산은 편하게 진행하면 된다.

자세한 구현은 아래 코드를 참고해주세요.

 

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
#include <iostream>
#include <string>
#include <vector>
 
using namespace std;
 
int N, M;
int rate[7];
vector<vector<int>> u;
vector<int> e;
int ansService, ansProfit;
vector<int> answer;
 
int calcPrice(int price, int discountRate) {
    return (price * (100 - discountRate)) / 100;
}
 
void selectDiscountRate(int idx) {
    if (idx == M) {
        int service = 0, profit = 0;
        for (int i = 0; i < N; i++) {
            int price = 0;
            for (int j = 0; j < M; j++) {
                if (rate[j] >= u[i][0]) price += calcPrice(e[j], rate[j]);
            }
            if (price >= u[i][1]) service++;
            else profit += price;
        }
 
        if (service > ansService) {
            ansService = service, ansProfit = profit;
        }
        else if (service == ansService) {
            if (profit > ansProfit) ansProfit = profit;
        }
        return;
    }
    
    for (int i = 10; i <= 40; i += 10) {
        rate[idx] = i;
        selectDiscountRate(idx + 1);
    }
}
 
vector<int> solution(vector<vector<int>> users, vector<int> emoticons) {
    //초기화
    u = users, e = emoticons;
    N = u.size(), M = e.size();
    ansService = 0, ansProfit = 0;
 
    //해법
    selectDiscountRate(0);
    answer.push_back(ansService);
    answer.push_back(ansProfit);
 
    //반환
    return answer;
}

 

브루트 포스에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/150369

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 배달해야할 박스 갯수와 수거해야할 박스 갯수 정보를 입력받는다.

2. 모든 배달과 수거를 마치고 물류창고로 돌아오는 최소거리를 반환한다.

 

<해법>

1. 알고리즘 선택하기

=> 가장 끝에서 부터 가능한 배달과 수거를 최대로 하면 정답을 구할 수 있습니다. 따라서, 저는 그리디 알고리즘을 선택했습니다.

 

2. 자료구조 선택하기

=> 가장 끝에서 부터 배달과 수거를 진행합니다. 그리고 배달과 수거가 완료된 집은 넘어갑니다. 따라서, 저는 후입선출 자료구조인 스택을 선택했습니다.

 

3. 구현 전 정리

=> 아래는 구현 전 정리한 생각입니다.

(1) 끝 집에서 부터 배달과 수거를 진행한다. 이동한 거리는 max(배달해야할 끝집, 수거해야할 끝집) x 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
65
66
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <algorithm>
 
using namespace std;
 
struct house {
    int idx, boxCnt;
};
 
stack<house> del, pic;
 
long long solution(int cap, int n, vector<int> deliveries, vector<int> pickups) {
    long long answer = 0;
 
    for (int i = 0; i < deliveries.size(); i++) {
        int boxCnt = deliveries[i];
        if (boxCnt) {
            del.push({ i + 1, boxCnt });
        }
    }
    for (int i = 0; i < pickups.size(); i++) {
        int boxCnt = pickups[i];
        if (boxCnt) {
            pic.push({ i + 1, boxCnt });
        }
    }
 
    while (true) {
        if (del.empty() && pic.empty()) break;
 
        int delIdx = del.empty() ? 0 : del.top().idx;
        int picIdx = pic.empty() ? 0 : pic.top().idx;
        answer += static_cast<long long>(max(delIdx, picIdx)) * 2;
 
        int delBoxCnt = 0;
        while (!del.empty()) {
            house& h = del.top();
            if (delBoxCnt + h.boxCnt <= cap) {
                delBoxCnt += h.boxCnt;
                del.pop();
            }
            else {
                h.boxCnt -= (cap - delBoxCnt);
                break;
            }
        }
 
        int picBoxCnt = 0;
        while (!pic.empty()) {
            house& h = pic.top();
            if (picBoxCnt + h.boxCnt <= cap) {
                picBoxCnt += h.boxCnt;
                pic.pop();
            }
            else {
                h.boxCnt -= (cap - picBoxCnt);
                break;
            }
        }
    }
 
    return answer;
}

 

그리디 알고리즘에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/150370

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 현재 날짜, 약관 정보, 개인 정보를 입력받는다.

2. 유효기간이 지난 개인 정보의 번호를 반환한다.

 

<해법>

1. 날짜 문자열 연산 및 대소비교 방법

=> 위 문제는 "개인정보 수집 날짜 + 유효기간  < 오늘 날짜" 를 물어보는 것입니다. 이제 핵심은 날짜 연산 및 대소비교 입니다. 날짜 문자열을 가장 쉽게 연산과 대소비교를 하는 방법은 모두 정수로 바꿔서 계산하는 것입니다. 위 문제의 경우 모두 일 수로 바꾸면 연산과 대소비교를 빠르게 할 수 있습니다. 코드의 dateStringToInt 함수를 참고해주세요. 날짜나 시간 등등 문자열을 쉽고 빠르게 연산하고 대소비교하는 방법은 정수로 바꾸는 것입니다.

 

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
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
 
using namespace std;
 
map<charint> m;
 
int dateStringToInt(string str) {
    int year = stoi(str.substr(04));
    int month = stoi(str.substr(52));
    int day = stoi(str.substr(8));
    return (28 * 12 * year) + (28 * (month - 1)) + day;
}
 
int calcDate(int d, int num) {
    return d + (28 * num) - 1;
}
 
vector<int> solution(string today, vector<string> terms, vector<string> privacies) {
    vector<int> answer;
 
    int td = dateStringToInt(today);
 
    for (string& t : terms) {
        istringstream iss(t);
        char cmd;
        int num;
        iss >> cmd >> num;
        m[cmd] = num;
    }
 
    for (int i = 0; i < privacies.size(); i++) {
        istringstream iss(privacies[i]);
        string s;
        char cmd;
        iss >> s >> cmd;
 
        int d = dateStringToInt(s);
        int expired = calcDate(d, m[cmd]);
        if (expired < td) answer.push_back(i + 1);
    }
 
    return answer;
}

 

문자열에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/118668

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 알고력, 코딩력, 문제들을 입력받는다.

2. 문제들을 풀며 알고력, 코딩력을 향상시킨다.

3. 모든 문제들을 풀 수 있는 알고력과 코딩력을 얻는 최단시간을 반환한다.

 

<해법>

1. 문제를 접근해 나가는 방법(저가 문제를 풀었던, 지극히 개인적인 방법입니다.)

=>

(1) '코딩문제를 어떤 순서로 풀어야할까?'를 먼저 고민해야합니다. 내 알고력과 코딩력에 가장 근접한 문제? 아닙니다. 알고력과 코딩력을 1시간 당으로 계산해서 가장 많이 올려줄 수 있는 문제? 아닙니다. 여러 방법을 생각해보면 모두 아니라는 것을 알 수 있습니다. 그렇다면, 남은 것은 모든 경우를 해보는 방법(브루트포스) 밖에 없습니다.

 

(2) 효율성은 일단 생각하지 않고, 정확성에만 초점을 둔 채 재귀를 이용해 구현합니다.

 

(3) 정확성 부분을 모두 통과가 되었다면, 어떻게 효율성을 높일지 생각해야합니다. 재귀를 이용한 브루트포스이므로 가장 먼저 메모이제이션을 떠올릴 수 있습니다. 따라서, 해당 코드에 메모이제이션을 추가하여 완성합니다.


사실... 저는 정확성 테스트 케이스 제한사항에 있는 '1 <= problems의 길이 <= 6'을 보고 재귀를 이용한 브루트포스를 떠올렸습니다. 그리고, 바로 효율성은 메모이제이션을 떠올릴 수 있었습니다. 위에 해법을 거창하게 써놨지만, 결국 저는 꼼수로 풀었습니다ㅜㅜ.

 

정확성 코드와 효율성 코드를 모두 올리겠습니다. 두 개의 차이점을 보시면서, 메모이제이션을 느끼셨으면 좋겠습니다.

 

- 정확성 코드

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
67
68
69
70
71
72
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#define INF 987654321
 
using namespace std;
 
vector<vector<int>> problems;
int maxAlp, maxCop;
int answer;
 
int solveProblems(int alp, int cop) {
 
    //종료조건
    if (alp >= maxAlp && cop >= maxCop) {
        return 0;
    }
 
    int ret = INF;
 
    for (int i = 0; i < problems.size(); i++) {
        //문제를 풀 수 있는 경우
        if (alp >= problems[i][0&& cop >= problems[i][1]) {
 
            //문제를 풀 필요가 없는 경우
            //(1) alp가 maxAlp를 넘었는데, 문제가 cop를 올려주지 못할 경우
            if (alp >= maxAlp && problems[i][3== 0) {
                continue;
            }
 
            //(2) cop가 maxCop를 넘었는데, 문제가 alp를 올려주지 못할 경우
            if (cop >= maxCop && problems[i][2== 0) {
                continue;
            }
            
            //재귀
            ret = min(ret, 
                      solveProblems(alp + problems[i][2], cop + problems[i][3]) + problems[i][4]);
        }
    }
 
    return ret;
}
 
int solution(int alp, int cop, vector<vector<int>> problems_given) {
 
    //초기화
    problems = problems_given;
    problems.push_back({ 0,0,1,0,1 });
    problems.push_back({ 0,0,0,1,1 });
    maxAlp = 0;
    maxCop = 0;
    answer = -1;
 
    /* 해법 */
    //1. problems의 alp, cop 최댓값 구하기
    for (int i = 0; i < problems_given.size(); i++) {
        if (problems_given[i][0> maxAlp) {
            maxAlp = problems_given[i][0];
        }
        if (problems_given[i][1> maxCop) {
            maxCop = problems_given[i][1];
        }
    }
 
    //2. 브루트포스(재귀)
    answer = solveProblems(alp, cop);
 
    //반환
    return answer;
}

 

- 효율성 코드

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <string>
#include <vector>
#include <algorithm>
#include <string.h>
#include <iostream>
#define INF 987654321
 
using namespace std;
 
vector<vector<int>> problems;
int maxAlp, maxCop;
int cache[151][151];
int answer;
 
int solveProblems(int alp, int cop) {
 
    //캐시 크기 벗어남 방지
    if (alp > maxAlp) {
        alp = maxAlp;
    }
    if (cop > maxCop) {
        cop = maxCop;
    }
 
    //alp, cop가 최대치 달성됐을 경우
    if (alp == maxAlp && cop == maxCop) {
        return 0;
    }
 
    int& ret = cache[alp][cop];
 
    //메모이제이션
    if (cache[alp][cop] != -1) {
        return ret;
    }
 
    ret = INF;
 
    for (int i = 0; i < problems.size(); i++) {
 
        //문제를 풀 수 있는 경우
        if (alp >= problems[i][0&& cop >= problems[i][1]) {
 
            //문제를 풀 필요가 없는 경우
            //(1) alp가 maxAlp가 되었는데, 문제가 cop를 올려주지 못할 경우
            if (alp == maxAlp && problems[i][3== 0) {
                continue;
            }
 
            //(2) cop가 maxCop가 되었는데, 문제가 alp를 올려주지 못할 경우
            if (cop == maxCop && problems[i][2== 0) {
                continue;
            }
 
            //재귀
            ret = min(ret, 
                      solveProblems(alp + problems[i][2], cop + problems[i][3]) + problems[i][4]);
        }
    }
 
    return ret;
}
 
int solution(int alp, int cop, vector<vector<int>> problems_given) {
 
    //초기화
    problems = problems_given;
    problems.push_back({ 0,0,1,0,1 });
    problems.push_back({ 0,0,0,1,1 });
    maxAlp = 0;
    maxCop = 0;
    memset(cache, -1sizeof(cache));
    answer = -1;
 
    /* 해법 */
    //1. problems의 alp, cop 최댓값 구하기
    for (int i = 0; i < problems_given.size(); i++) {
        if (problems_given[i][0> maxAlp) {
            maxAlp = problems_given[i][0];
        }
        if (problems_given[i][1> maxCop) {
            maxCop = problems_given[i][1];
        }
    }
 
    //2. 메모이제이션
    answer = solveProblems(alp, cop);
 
    //반환
    return answer;
}

 

메모이제이션에 대해 알아볼 수 있는 문제였습니다.

https://school.programmers.co.kr/learn/courses/30/lessons/118667

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

<풀이>

1. 두 개의 큐를 입력받는다.

2. 두 개의 큐의 원소의 합이 같아질 때까지 원소를 이동한다.

3. 원소이동작업의 최소회수를 반환한다.

 

<해법>

1. 원소이동을 최소로 하는 방법

=> 작업회수를 최소로 하는 방법부터 생각해야합니다. 최소로 하는 방법은 작업때 마다 원소의 합이 큰 큐에서 원소를 빼서 작은 쪽으로 넣는 방법입니다. 이유는 큰 쪽에서 작은 쪽으로 넘겨야 작업 회수가 최소가 되기 때문입니다. 물론, 작은 쪽에서 먼저 빼고, 큰 쪽에서 빼는 경우가 최소회수가 될 수도 있습니다. 하지만, 이 경우는 큰 쪽에서 먼저 빼는 경우와 회수가 같은 경우일 뿐입니다. 이 부분은 직접 그림을 그려가며 해보시면 충분히 이해하실 수 있습니다.

 

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
65
66
67
68
69
70
71
72
#include <string>
#include <vector>
#include <iostream>
 
using namespace std;
 
int solution(vector<int> queue1, vector<int> queue2) {
 
    //선언
    int answer;                         //-> 정답
    vector<int>::iterator ptr1, ptr2;   //-> 큐 포인터
    long long sum_all, sum1, sum2;      //-> 큐1+큐2합, 큐1합, 큐2합
 
    //초기화
    answer = 0;
    ptr1 = queue1.begin(), ptr2 = queue2.begin();
    sum_all = 0, sum1 = 0, sum2 = 0;
 
    /* 해법 */
    //1. 큐1, 큐2, 큐1+큐2 원소 합 구하기
    for (int i = 0; i < queue1.size(); i++) {
        sum1 += queue1[i];
        sum2 += queue2[i];
    }
    sum_all = sum1 + sum2;
 
    //2. 큐1+큐2 원소 합이 홀수일 경우 -> 불가능
    if (sum_all % 2 != 0) {
        answer = -1;
    }
    else {
        //3. 큐1, 큐2에 있는 두 개의 포인터를 조종하며 원소 합 구하기
        while (true) {
 
            //종료조건1 -> 두 큐의 합이 같을 경우
            if (sum1 == sum2) {
                break;
            }
 
            //종료조건2 -> 포인터1이 큐2 끝까지 갔을 경우 or 포인터2가 큐1 끝까지 갔을 경우(탐색완료)
            if (ptr1 == queue2.end() || ptr1 == queue1.end()) {
                answer = -1;
                break;
            }
 
            //큐의 포인터 옮기기
            if (sum1 > sum2) {
                sum1 -= *ptr1;
                sum2 += *ptr1;
                
                ptr1++;
                if (ptr1 == queue1.end()) {
                    ptr1 = queue2.begin();
                }
            }
            else {
                sum2 -= *ptr2;
                sum1 += *ptr2;
                
                ptr2++;
                if (ptr2 == queue2.end()) {
                    ptr2 = queue1.begin();
                }
            }
 
            answer++;
        }
    }
 
    //반환
    return answer;
}

 

구현에 대해 알아볼 수 있는 문제였습니다.

+ Recent posts