1. 개요

이 기사에서는 주어진 수보다 작은 2의 가장 큰 거듭제곱을 찾는 방법을 살펴보겠습니다.

우리의 예에서는 샘플 입력 9를 사용할 것입니다. 2 0 은 1이며, 주어진 입력보다 작은 2의 거듭제곱을 찾을 수 있는 최소 유효 입력은 2입니다. 따라서 1보다 큰 입력만 다음과 같이 간주합니다. 유효한.

2. 순진한 접근

1인 2 0 부터 시작 하고 입력보다 작은 숫자를 찾을 때까지 계속해서 2를 곱합니다 .

public long findLargestPowerOf2LessThanTheGivenNumber(long input) {
    Assert.isTrue(input > 1, "Invalid input");
    long firstPowerOf2 = 1;
    long nextPowerOf2 = 2;
    while (nextPowerOf2 < input) {
        firstPowerOf2 = nextPowerOf2;
        nextPowerOf2 = nextPowerOf2 * 2;
    }
    return firstPowerOf2;
}

샘플 입력 = 9에 대한 코드를 이해합시다 .

firstPowerOf2 의 초기 값 은 1이고 nextPowerOf2 는 2입니다. 보시다시피 2 < 9는 true이고 while 루프 내부로 들어갑니다.

첫 번째 반복의 경우 firstPowerOf2 는 2이고 nextPowerOf2 는 2 * 2 = 4입니다. 다시 4 < 9이므로 while 루프를 계속할 수 있습니다.

두 번째 반복의 경우 firstPowerOf2 는 4이고 nextPowerOf2 는 4 * 2 = 8입니다. 이제 8 < 9이므로 계속 진행하겠습니다.

세 번째 반복의 경우 firstPowerOf2 는 8이고 nextPowerOf2 는 8 * 2 = 16입니다. while 조건 16 < 9는 거짓이므로 while 루프에서 빠져 나옵니다. 8은 9보다 작은 2의 가장 큰 거듭제곱입니다.

코드의 유효성을 검사하기 위해 몇 가지 테스트를 실행해 보겠습니다.

assertEquals(8, findPowerOf2LessThanTheGivenNumber(9));
assertEquals(16, findPowerOf2LessThanTheGivenNumber(32));

우리 솔루션의 시간 복잡도는 O(log 2 (N)) 입니다. 우리의 경우 log 2 (9) = 3번 반복했습니다.

3. Math.log 사용하기

로그 밑수 2는 숫자를 재귀적으로 2로 나눌 수 있는 횟수를 제공합니다 . 즉, 숫자의 로그 22 의 거듭제곱을 제공합니다 . 이를 이해하기 위해 몇 가지 예를 살펴보겠습니다.

log 2 (8) = 3 및 log 2 (16) = 4. 일반적으로 y = log 2 x 여기서 x = 2 y 임을 알 수 있습니다 .

따라서 2로 나누어 떨어지는 숫자를 찾으면 숫자가 2의 완전 거듭제곱인 시나리오를 피하기 위해 1을 뺍니다.

Math.log 는 로그 10 입니다. log 2 (x)를 계산하려면log 2 (x)=log 10 (x)/log 10 (2)공식을 사용할 수 있습니다.

코드에 넣어 보겠습니다.

public long findLargestPowerOf2LessThanTheGivenNumberUsingLogBase2(long input) {
    Assert.isTrue(input > 1, "Invalid input");
    long temp = input;
    if (input % 2 == 0) {
        temp = input - 1;
    }
    long power = (long) (Math.log(temp) / Math.log(2));
    long result = (long) Math.pow(2, power);
    return result;
}

샘플 입력을 9로 가정하면 temp 의 초기 값 은 9입니다.

9 % 2는 1이므로 임시 변수는 9입니다.  여기서는 나머지 9/2를 제공하는 모듈로 나누기를 사용합니다.

log 2 (9) 를 찾기 위해 log 10 (9) / log 10 (2) = 0.95424 / 0.30103 ~= 3을 수행합니다.

이제 결과 는 2 3 즉 8입니다.

코드를 확인해 보겠습니다.

assertEquals(8, findLargestPowerOf2LessThanTheGivenNumberUsingLogBase2(9));
assertEquals(16, findLargestPowerOf2LessThanTheGivenNumberUsingLogBase2(32));

실제로 Math.pow 는 접근 1에서 했던 것과 동일한 반복을 수행할 것입니다. 따라서 이 솔루션에서도 시간 복잡도는 O(Log 2 (N)) 이라고 말할 수 있습니다 .

4. 비트와이즈 기법

이 접근 방식에서는 비트 시프트 기술을 사용합니다. 먼저 숫자를 나타내는 4비트가 있다는 점을 고려하여 2의 거듭제곱에 대한 이진 표현을 살펴보겠습니다.

2 0 1 0001
2 1 2 0010
2 2 4 0100
2 3 8 1000

자세히 살펴보면 1의 바이트를 왼쪽으로 이동하여 2의 거듭제곱을 계산할 수 있음을 관찰할 수 있습니다 . 즉. 2 2 는 1 x 2 자리에 대한 왼쪽 시프트 바이트입니다.

비트 시프트 기술을 사용하여 코딩해 보겠습니다.

public long findLargestPowerOf2LessThanTheGivenNumberUsingBitShiftApproach(long input) {
    Assert.isTrue(input > 1, "Invalid input");
    long result = 1;
    long powerOf2;
    for (long i = 0; i < Long.BYTES * 8; i++) {
        powerOf2 = 1 << i;
        if (powerOf2 >= input) {
            break;
        }
        result = powerOf2;
    }
    return result;
}

위의 코드 에서는 8바이트 또는 64비트를 사용하는 데이터 유형으로 long 을 사용하고 있습니다. 그래서 우리는 2의 거듭제곱을 2 64 까지 계산할 것 입니다. 비트 시프트 연산자 <<사용하여 2의 거듭제곱을 찾습니다. 샘플 입력 9의 경우 4 번째 반복 powerOf2 = 16 및 result = 8 의 값은 루프에서 16 > 9로 나뉩니다 . 입력 .

코드가 예상대로 작동하는지 확인해 보겠습니다.

assertEquals(8, findLargestPowerOf2LessThanTheGivenNumberUsingBitShiftApproach(9));
assertEquals(16, findLargestPowerOf2LessThanTheGivenNumberUsingBitShiftApproach(32));

이 접근 방식최악의 시간 복잡도 는 첫 번째 접근 방식에서 본 것과 유사하게 다시 O(log 2 (N)) 입니다. 그러나 이 접근 방식은 곱셈에 비해 비트 시프트 연산이 더 효율적 이기 때문에 더 좋습니다 .

5. 비트 AND

다음 접근 방식에서는 이 공식 2 n AND 2 n -1 = 0 을 사용할 것 입니다.

작동 방식을 이해하기 위해 몇 가지 예를 살펴보겠습니다.

4의 이진 표현은 0100 이고 3은 0011 입니다.

이 두 숫자에 대해 비트 AND 연산수행해 보겠습니다 . 010000110000 입니다. 2의 거듭제곱과 그보다 작은 수에 대해서도 동일하게 말할 수 있습니다. 각각 1000 , 0111 로 표현되는 16(2 4 )과 15를 취합시다 . 다시 말하지만, 이 두 가지에 대한 비트별 AND 결과는 0입니다. 또한 이 2를 제외한 다른 숫자에 대한 AND 연산은 0이 되지 않을 것이라고 말할 수 있습니다.

비트 AND를 사용하여 이 문제를 해결하는 코드를 살펴보겠습니다.

public long findLargestPowerOf2LessThanTheGivenNumberUsingBitwiseAnd(long input) { 
    Assert.isTrue(input > 1, "Invalid input");
    long result = 1;
    for (long i = input - 1; i > 1; i--) {
        if ((i & (i - 1)) == 0) {
            result = i;
            break;
        }
    }
    return result;
}

위의 코드에서 우리는 입력보다 작은 숫자를 반복합니다. 숫자와 숫자-1의 비트 AND를 찾을 때마다 숫자가 2의 거듭제곱이라는 것을 알기 때문에 루프에서 빠져 나옵니다. 이 경우 샘플 입력 9의 경우 루프에서 빠져 나옵니다. 시 저는 8 = I = 1 (7) -.

이제 몇 가지 시나리오를 확인하겠습니다.

assertEquals(8, findLargestPowerOf2LessThanTheGivenNumberUsingBitwiseAnd(9));
assertEquals(16, findLargestPowerOf2LessThanTheGivenNumberUsingBitwiseAnd(32));

이 접근 방식 의 최악의 시간 복잡도 는 입력이 정확한 거듭제곱 2일 때 O(N/2)입니다 . 우리가 볼 수 있듯이 이것은 가장 효율적인 솔루션은 아니지만 올 수 있으므로 이 기술을 아는 것이 좋습니다. 유사한 문제를 해결할 때 편리합니다.

6. 결론

우리는 주어진 숫자보다 작은 2의 가장 큰 거듭제곱을 찾는 다양한 접근 방식을 보았습니다. 또한 비트 연산이 경우에 따라 계산을 단순화할 수 있는 방법도 확인했습니다.

이 기사에 대한 단위 테스트가 포함된 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .

Generic footer banner