랜선 자르기 LAN cable cut

notion image

문제

집에서 시간을 보내던 오영식은 박성원의 부름을 받고 급히 달려왔다. 박성원이 캠프 때 쓸 N개의 랜선을 만들어야 하는데 너무 바빠서 영식이에게 도움을 청했다.
이미 오영식은 자체적으로 K개의 랜선을 가지고 있다. 그러나 K개의 랜선은 길이가 제각각이다. 박성원은 랜선을 모두 N개의 같은 길이의 랜선으로 만들고 싶었기 때문에 K개의 랜선을 잘라서 만들어야 한다. 예를 들어 300cm 짜리 랜선에서 140cm 짜리 랜선을 두 개 잘라내면 20cm는 버려야 한다. (이미 자른 랜선은 붙일 수 없다.)
편의를 위해 랜선을 자르거나 만들 때 손실되는 길이는 없다고 가정하며, 기존의 K개의 랜선으로 N개의 랜선을 만들 수 없는 경우는 없다고 가정하자. 그리고 자를 때는 항상 센티미터 단위로 정수길이만큼 자른다고 가정하자. N개보다 많이 만드는 것도 N개를 만드는 것에 포함된다. 이때 만들 수 있는 최대 랜선의 길이를 구하는 프로그램을 작성하시오.

입력

첫째 줄에는 오영식이 이미 가지고 있는 랜선의 개수 K, 그리고 필요한 랜선의 개수 N이 입력된다. K는 1이상 10,000이하의 정수이고, N은 1이상 1,000,000이하의 정수이다. 그리고 항상 K ≦ N 이다. 그 후 K줄에 걸쳐 이미 가지고 있는 각 랜선의 길이가 센티미터 단위의 정수로 입력된다. 랜선의 길이는 2^31-1보다 작거나 같은 자연수이다.

출력

첫째 줄에 N개를 만들 수 있는 랜선의 최대 길이를 센티미터 단위의 정수로 출력한다.

예제 입력 1

4 11
802
743
457
539

예제 출력 1

200

힌트

802cm 랜선에서 4개, 743cm 랜선에서 3개, 457cm 랜선에서 2개, 539cm 랜선에서 2개를 잘라내 모두 11개를 만들 수 있다.
 

My solution


일단 필요한 랜선의 개수가 11개고 전체 랜선의 길이의 총합이 2541이므로 2541 / 11 = 231이다.
동일한 길이어야 하고, 모든 랜선들이 그 길이로 나눠질 수 있어야 한다. 최대 231cm까지만 11개의 랜선 개수를 채울 수 있을 것이다. (232만 되어도 10개이므로)
 
그럼 231부터 차례대로 내려가면서 조건에 맞는지 검사해본다.
cnt = 231
457 1
802 3
743 3
539 2
>> 9개 나온다. 하지만 이게 11개를 만들고 싶다. break하지 않고 더 찾아본다. cnt—;
(...) while문으로 break될 때까지 배열의 내부를 뒤지는 for문을 반복한다. 시간복잡도 O(n^2) 테스트를 돌리다가 프로그램이 멈췄다.
 
이분탐색 방법을 시도해보았다.
 
231이 최댓값이므로 rt이고 현재 lt(최솟값)는 1이다. mid는 이 중간값인 232 / 2 = 116인데 만약 cnt가 조건에 맞지 않았다면 범위를 조절해준다.
mid를 이용해 나누어봤더니
mid = 116
457 3
802 6
743 6
539 4
>> 19로 N보다 많이 크다. 그럼 lt값을 mid+1로 조절해준다. lt는 0에서 117이 되고 이 값을 기반으로 다음 루프에서 mid = Math.floor((lt+rt)/2);에 의해 mid는 (231 + 117) / 2 = 174가 된다.
mid = 174
457 2
802 4
743 4
539 3
>> 13으로 또 안된다. lt값을 mid+1로 175가 된다. mid는 (231+175) / 2 = 203
mid = 203
457 2
802 3
743 3
539 2
>> 10번이 된다. 이번에는 N보다 작아졌다. 이때는 rt의 범위를 조절해주어야 더 작은 mid 값을 통해 필요한 N값을 찾아낼 수 있다. rt = mid-1 = 202이므로 mid는 (202 + 175) / 2 = 189
mid = 189
457 2
802 4
743 3
539 2
>> 4번만에 N에 일치하는 11을 찾아냈다. 일단 답이 되니까 answer에 mid값을 저장지만 11번에 가능한 최대 거리를 찾기 위해서 lt를 증가시킨다. lt = mid+1 = 190이고 mid = (202 + 190) / 2 = 196
mid = 196
457 2
802 4
743 3
539 2
>> 11이라 answer에 mid값을 저장한다. 11번에 가능한 최대 거리를 찾기 위해 lt = mid + 1 = 197, mid = (202 + 197) / 2 = 199가 된다.
mid = 199
457 2
802 4
743 3
539 2
>> 11이라 answer에 mid값을 저장한다. 11번에 가능한 최대 거리를 찾기 위해 lt = mid + 1 = 200, mid = (202 + 200) / 2 = 201이 된다.
mid = 201
457 2
802 3
743 3
539 2
>> 10이 되버렸다. N보다 작으므로 rt를 증가시킨다. rt = mid - 1 = 200, mid = (200 + 200) / 2 = 200이 된다.
mid = 200
457 2
802 4
743 3
539 2
>> 11이 되었다. 이때 mid값은 answer에 업데이트해주고 lt를 다시 mid+1로 변경해준다. 이때 lt는 201로 rt값인 200보다 커져버리므로 while문의 조건에 따라 종료된다.
 

소스코드

function count(arr, mid) {
    let cnt = 0;
    for (let i = 0; i < arr.length; i++) cnt += Math.floor(arr[i] / mid);
    return cnt;
}

function solution(i) {
    const input = i.toString().trim().split("\n");
    [kn, ...arr] = input;
    [k, n] = kn.split(" ");
    let answer;
    let lt = 1;
    let rt = Math.floor(
        arr.reduce((cur, acc) => Number(cur) + Number(acc), 0) / n
    );
    // lt와 rt가 교차될 때까지 반복
    while (lt <= rt) {
        let mid = parseInt((lt + rt) / 2);
        // cnt가 n과 같거나 크다면 일단 답이 되고 최대 거리를 찾아 lt를 증가시킨다.
        if (count(arr, mid) >= n) {
            answer = mid;
            lt = mid + 1;
        } else rt = mid - 1;
    }
    console.log(answer);
    // return answer;
}

test("solution", () => {
    expect(solution("4 11\n802\n743\n457\n539")).toStrictEqual(200);
});