[프로그래머스/ JS] 두 큐 합 같게 만들기

코린·2023년 6월 28일
0

알고리즘

목록 보기
21/44
post-thumbnail

문제 설명

길이가 같은 두 개의 큐가 주어집니다. 하나의 큐를 골라 원소를 추출(pop)하고, 추출된 원소를 다른 큐에 집어넣는(insert) 작업을 통해 각 큐의 원소 합이 같도록 만들려고 합니다. 이때 필요한 작업의 최소 횟수를 구하고자 합니다. 한 번의 pop과 한 번의 insert를 합쳐서 작업을 1회 수행한 것으로 간주합니다.

큐는 먼저 집어넣은 원소가 먼저 나오는 구조입니다. 이 문제에서는 큐를 배열로 표현하며, 원소가 배열 앞쪽에 있을수록 먼저 집어넣은 원소임을 의미합니다. 즉, pop을 하면 배열의 첫 번째 원소가 추출되며, insert를 하면 배열의 끝에 원소가 추가됩니다. 예를 들어 큐 [1, 2, 3, 4]가 주어졌을 때, pop을 하면 맨 앞에 있는 원소 1이 추출되어 [2, 3, 4]가 되며, 이어서 5를 insert하면 [2, 3, 4, 5]가 됩니다.

다음은 두 큐를 나타내는 예시입니다.

queue1 = [3, 2, 7, 2] queue2 = [4, 6, 5, 1]

두 큐에 담긴 모든 원소의 합은 30입니다. 따라서, 각 큐의 합을 15로 만들어야 합니다. 예를 들어, 다음과 같이 2가지 방법이 있습니다.

  1. queue2의 4, 6, 5를 순서대로 추출하여 queue1에 추가한 뒤, queue1의 3, 2, 7, 2를 순서대로 추출하여 queue2에 추가합니다. 그 결과 queue1은 [4, 6, 5], queue2는 [1, 3, 2, 7, 2]가 되며, 각 큐의 원소 합은 15로 같습니다. 이 방법은 작업을 7번 수행합니다.
  2. queue1에서 3을 추출하여 queue2에 추가합니다. 그리고 queue2에서 4를 추출하여 queue1에 추가합니다. 그 결과 queue1은 [2, 7, 2, 4], queue2는 [6, 5, 1, 3]가 되며, 각 큐의 원소 합은 15로 같습니다. 이 방법은 작업을 2번만 수행하며, 이보다 적은 횟수로 목표를 달성할 수 없습니다.

따라서 각 큐의 원소 합을 같게 만들기 위해 필요한 작업의 최소 횟수는 2입니다.

길이가 같은 두 개의 큐를 나타내는 정수 배열 queue1queue2가 매개변수로 주어집니다. 각 큐의 원소 합을 같게 만들기 위해 필요한 작업의 최소 횟수를 return 하도록 solution 함수를 완성해주세요. 단, 어떤 방법으로도 각 큐의 원소 합을 같게 만들 수 없는 경우, -1을 return 해주세요.


제한사항

  • 1 ≤ queue1의 길이 = queue2의 길이 ≤ 300,000
  • 1 ≤ queue1의 원소, queue2의 원소 ≤ 10 9
  • 주의: 언어에 따라 합 계산 과정 중 산술 오버플로우 발생 가능성이 있으므로 long type 고려가 필요합니다.

입출력 예

queue1queue2result
[3, 2, 7, 2][4, 6, 5, 1]2
[1, 2, 1, 2][1, 10, 1, 2]7
[1, 1][1, 5]-1

문제풀이

제가 원래 생각한 방식으로 풀려고 시도했더니 시간초과가 나서 문제를 틀렸습니다.

결국은 구글링을 통해서 문제풀이 방식을 찾았습니다. 다들 투포인터(다중포인터)를 사용하셨습니다.

투포인터 알고리즘?

  • 리스트에 순차적으로 접근해야 할 때 두 개의 점의 위치를 기록하면서 처리하는 알고리즘
  • 정렬되어있는 두 리스트의 합집합에도 사용됨. 병합정렬(merge sort)의 counquer 영역의 기초가 되기도 합니다.

예시

[1 , 2 , 3 , 4 , 5]

위와 같은 배열이 있다고 한다.

<초기 단계>

시작점과 끝점이 첫번째 원소의 인덱스를 가리키도록 합니다.

⬇️(start)

[1 , 2 , 3 , 4 , 5]

⬆️(end)

→ 현재의 부분 합은 1 카운트는 0

<Step 1>

이전 단계에서의 부분합이 1 → end를 증가시킵니다.

⬇️(start)

[1 , 2 , 3 , 4 , 5]

  ⬆️(end)

→ 현재의 부분 합은 3 카운트는 0

<Step 2>

부분합이 3 → end 를 증가시킵니다.

⬇️(start)

[1 , 2 , 3 , 4 , 5]

  ⬆️(end)

→ 현재의 부분 합은 6 카운트는 0

<Step 3>

부분합 6 → start를 증가시킵니다.

  ⬇️(start)

[1 , 2 , 3 , 4 , 5]

  ⬆️(end)

→ 현재의 부분 합은 5 카운트는 1 (부분합이 5이기 때문에)

이걸 계속 반복합니다.

<마지막>

시작점과 끝점이 첫번째 원소의 인덱스를 가리키도록 합니다.

  ⬇️(start)

[1 , 2 , 3 , 4 , 5]

  ⬆️(end)

→ 현재의 부분 합은 5

그래서 이 개념을 우리 문제에 적용해본다면

주황색은 q1 포인터 초록색은 q2 포인터 입니다. (출처)

[초기단계] : 같은 합일 때가 10 이고 sum1이 같은 합 즉 half 보다 작습니다.

121211012

sum1 : 6 sum2: 14

                          half: 10

                        sum1 < half

[2단계] : queue2의 시작점을 옮깁니다. 여전히 sum1은 half 보다 작습니다.

121211012

sum1 : 7 sum2: 13

                               half: 10

                             sum1 < half

[3단계] : queue2의 시작점을 또 옮깁니다. sum1이 half 보다 큽니다.

121211012

sum1 : 17 sum2: 3

                               half: 10

                             sum1 > half

[4단계] : queue1의 시작점을 옮깁니다. 여전히 sum1이 half 보다 큽니다.

121211012
         sum1 : 16                                                    sum2: 3

                               half: 10

                             sum1 > half

.

.

.

이런식으로 계속 반복하다 보면 아래와 같은 결과가 나옵니다.

[마지막단계] : queue1의 시작점을 옮깁니다. 여전히 sum1이 half 보다 큽니다.

121211012
                                                              sum1 :10 sum2: 10

                               half: 10

                             sum1 == half && sum2 == half

이렇게 결과 값을 찾을 수 있습니다.

잘못생각한점

<원래 제가 생각한 풀이 방식>

일단 큐1,큐2에 있는 모든 수를 더해서 나누기 2 해서 같은 합을 찾습니다.

큐1의 합, 큐2의 합을 각각 구합니다.

큐1 혹은 큐2 중에 같은 합 보다 큰 경우에 해당 큐에 있는 값을 빼서 다른 큐에 넣어주는 것을 반복해서 큐1의 합과 큐2의 합이 모두 같은 합에 도달하는 경우 반복문을 빠져나오도록 설계했습니다.

반복문을 돌면서 원소의 값 중 하나라도 같은 합 값보다 큰 것이 있다면 -1을 반환하도록 했습니다.

잘못 쓴 코드

function solution(queue1, queue2) {
    
    //하나 빼서 하나 넣으면 두 큐의 값이 같도록 하기
    //한번의 팝과 한번의 인서트를 묶어서 작업을 1회 한것으로 간주
    //팝하면 배열의 첫번째 원소가 추출
    //인서트하면 배열의 끝에 원소 추가
    
    //모든 값의 합을 구하고 
    
    //큐 1번,2번의 합,같은합
    let addQ1=0,addQ2=0,midQ=0;
    
    for(let i=0;i<queue1.length;i++){
        addQ1+=queue1[i];
        addQ2+=queue2[i];
    }
    midQ= (addQ1+addQ2)/2;
    
    //같은 합이 정수가 아니라면 -1 반환
    if(!Number.isInteger(midQ)) return -1;
    
    var answer = 0;
    
    while(true){
        
        if(addQ1 > midQ){
            let a = queue1.shift();
           
            if(a>midQ){ return -1;}
            
            queue2.push(a);
            addQ1 -= a;
            addQ2 += a;
            
        }
        else if(addQ2 > midQ){
            let a = queue2.shift();
            
            if(a>midQ){ return -1;}
            
            queue1.push(a);
            addQ1 += a;
            addQ2 -= a;
        }
        else{ return -1;}
      
        answer++;
        
        if(addQ1 === midQ && addQ2 === midQ) break;
    }
    
    return answer;
}

결과 코드

function solution(queue1, queue2) {
    
    let sum1 = queue1.reduce((prev,cur)=>prev+cur,0);
    let sum2 = queue2.reduce ((prev,cur)=>prev+cur,0);
    
    const q = [...queue1,...queue2];
    const half = (sum1 + sum2 )/2;
    let q1Pointer = 0;
    let q2Pointer = queue1.length;
    
    for(let cnt =0 ; cnt<queue1.length * 3 ; cnt++){
        if(sum1 === half){
            return cnt;
        }
        sum1 = 
            sum1 > half
        ? sum1 - q[q1Pointer++ % q.length]
        : sum1 + q[q2Pointer++ % q.length];
    }
    
    return -1;
    
}

.
.
.
.
.
.

잡담

공부는 언제나 힘드네영...

profile
안녕하세요 코린입니다!

0개의 댓글