코딩테스트

[카카오 코딩테스트] 두 큐 합 같게 만들기

시제이 2024. 4. 25. 20:36
728x90
반응형

 

그리디 알고리즘 & 투 포인터 문제이다.

 

 

 

 

 

 

 

 

 

 

 

처음 생각했던 접근방식(오류)

 

이 문제를 봤을 때 친절한 예제 설명 덕분에 어떻게 풀어야할지 힌트를 얻을 수 있었다. 

각 큐의 원소 합이 같아야하기 때문에, 두개의 큐의 전체 합을 알면 2로 나누면 되므로 각각의 큐가 도달해야하는 합을 알 수 있다.

사소해보이지만 이 개념이 문제 풀이를 위한 핵심 개념이고, 막상 바로 떠오르지 않을 수 있었는데 문제에서 친절하게 알려주었다.

그래서 이 다음 떠오른 생각은 그리디 알고리즘으로, 결국 각각의 두 큐의 합이 같아야 하므로,

q1 < q2 (합)일경우, q2에서 꺼내고 q1에 넣고, q1 > q2 (합)일경우 반대로 하면 될것이라 생각했다.

 

그러나 예제의 설명에 지나치게 의존했던 탓일까,

첫번째 예제에서 q1 < q2인데도 q1에서 먼저 꺼내는 것을 보고 예외사항이 발생할 수 있다고 생각했다.

조금만 더 생각했더라면 아까 논리적으로 두 큐의 합이 같아야 하므로, 결국 큰 쪽 큐에서 반드시 꺼내야하는 상황이 발생하는데,

그걸 놓치고 단순히 그리디 알고리즘으로 풀 수 없고 완전 탐색(N이 30만인데도 불구하고)을 해야겠다고 생각했다.

 

이 문제는 투 포인터로도 풀 수 있는데, 이 문제를 풀기 직전에 투 포인터 문제를 풀었는데도 떠오르지 않았던 이유는 

아직 투 포인터를 응용하는데 있어서 미숙하기 때문이라고 생각한다. 

두개의 큐를 합쳐서 하나의 원형 배열로 생각하면, 큐에서 꺼내고 다시 넣는 작업은 각각의 포인터의 인덱스를 조정하는 것과 같고,

숫자의 합이 큰 큐에서 꺼내고 삽입하는 과정을 거친다. 합이 전체 합의 절반이면 기록하고, 최소한의 횟수만 리턴한다.

따라서 논리적인 흐름은 그리디와 같다.

 

밑에는 잘못된 코드이다. 

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
#include <iostream>
#include <queue>
 
using namespace std;
 
int ret;
int minimum = 987654321;
 
 
//어.. 근데 무한루프에 빠지는 것을 어떻게 방지하지?
void recur(queue<int> q1, queue<int> q2, int sum, int cnt){
 
    if(sum == ret){
        minimum = min(minimum, cnt);
        return;
    }
    if(q1.empty() || q2.empty()){
        return;
    }
 
    //q1 원소빼서 q2 넣기
    int removed = q1.front(); q1.pop(); 
    q2.push(removed);
    recur(q1, q2, sum-removed, cnt+1);
    int backToQ1 = q2.front(); q2.pop();
    q1.push(removed);
 
    //q2 원소빼서 q1 넣기
    int removed2 = q2.front(); q2.pop(); 
    q1.push(removed2);
    recur(q1, q2, sum+removed2, cnt+1);
    int backToQ2 = q1.front(); q1.pop();
    q2.push(removed2);
 
}
 
int main(){
 
    queue<int> q1, q2;
    
    q1.push(3); q1.push(2); q1.push(7); q1.push(2);
    q2.push(4); q2.push(6); q2.push(5); q2.push(1);
 
    //queue1 = 14
    //queue2 = 16
    ret = 15;
    recur(q1, q2, 140);   
    cout << minimum;
 
 
    return 0;
}
cs

 

나름 예제를 대입해서 한번 백트래킹을 써보려고 했는데, depth가 2만을 훌쩍 넘어갈 수 있으므로 실행해보면 스택오버플로우가 발생한다.

 

 

 

 

 

합당한 접근방식(그리디)

 

아까 설명한 그리디 알고리즘으로 풀어보았다.

여기서 주목할 점은 실패조건인데, q1 기준으로 길이가 n이므로, 값이 이동할 때 최대 2n만큼 이동한다.

그렇다면 다시 q2에서 q1으로 이동하는 길이는 2n이고, 총 4n동안 반복하면 처음과 같아지므로 이때까지 두 큐의 합이 같지 않다면 -1.

그리고 수학적인 센스가 있다면 두 큐의 합이 홀수일 경우 두 큐의 합이 같게 만들 수 없으므로 이때도 -1을 반환한다.

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
#include <string>
#include <vector>
#include <queue>
 
using namespace std;
 
int solution(vector<int> queue1, vector<int> queue2) {
    
    long long sumQ1 = 0;
    long long sumQ2 = 0;
    queue<int> q1, q2;
    for(auto q: queue1){
        sumQ1 += q;
        q1.push(q);
    }
    for(auto q: queue2){
        sumQ2 += q;
        q2.push(q);
    }
    
    if((sumQ1+sumQ2)%2 !=0){
        return -1;
    }
    int maxIteration = (q1.size()+q2.size())*2;
    for(int cnt=0; cnt<maxIteration; cnt++){
        if(sumQ1 == sumQ2){
            return cnt;
        }else if(sumQ1 > sumQ2){
            q2.push(q1.front());
            sumQ1 -= q1.front();
            sumQ2 += q1.front();
            q1.pop();
        }else {
            q1.push(q2.front());
            sumQ1 += q2.front();
            sumQ2 -= q2.front();
            q2.pop();
        }
    }
    
    return -1;
}
cs

 

 

 

 

 

 

 

반응형