[프로그래머스][LV.2] 다음 큰 숫자 | python3

2025. 2. 6. 19:56프로그래머스/LV.2

 

문제 링크: 다음 큰 숫자

 

문제 설명

문제 설명

자연수 n이 주어졌을 때, n의 다음 큰 숫자는 다음과 같이 정의 합니다.

조건 1. n의 다음 큰 숫자는 n보다 큰 자연수 입니다.
조건 2. n의 다음 큰 숫자와 n은 2진수로 변환했을 때 1의 갯수가 같습니다.
조건 3. n의 다음 큰 숫자는 조건 1, 2를 만족하는 수 중 가장 작은 수 입니다.

예를 들어서 78(1001110)의 다음 큰 숫자는 83(1010011)입니다.
자연수 n이 매개변수로 주어질 때, n의 다음 큰 숫자를 return 하는 solution 함수를 완성해주세요.

제한사항
- n은 1,000,000 이하의 자연수 입니다.

 


문제 풀이

n보다 큰 수 중 이진법으로 바꾸었을때 n의 이진수와 1의 갯수가 같은 수 중 가장 작은 수를 구하는 문제이다.

처음에 bin()함수를 사용할까하다가 앞에 부가적인 것도 붙고 해서 그냥 수를 이진법으로 바꾸었을때 1의 개수를 세어주는 함수를 만들었다. 

n을 2로 나누면서 나머지가 1이되면 1씩 변수에 더한다. 맨 마지막 몫 1(이진수 맨 앞의 1)을 더해줘야 하기 때문에 변수는 1부터 시작한다.

함수를 이용하여 n의 이진수의 1의 갯수를 세서 변수에 넣어준다. 그리고 while문을 통해 1씩 더해주며 n의 이진수 갯수와 비교하여 answer값을 구한다.

 

내코드

def bin_one(n):
    chk=1
    if n==1:
        return 1
    while n>1:
        if n%2:
            chk+=1
        n//=2
    return chk

def solution(n):
    answer = n+1
    n_one=bin_one(n)
    while n_one!=bin_one(answer):
        answer+=1
    return answer

 

 


더보기

다른사람풀이

def nextBigNumber(n):
    num1 = bin(n).count('1')
    while True:
        n = n + 1
        if num1 == bin(n).count('1'):
            break
    return n

bin도 count 사용이 가능하구나.... 

 

♠ 속도차이

1. bin_one(n) 함수의 성능 차이

첫 번째 코드 (solution)에서 bin_one(n) 함수는 n을 2로 나누면서 1의 개수를 세는 방식입니다.
즉, **O(log n)**의 시간 복잡도를 가집니다.

 

이 함수는 solution에서 while 루프 안에서 계속 호출되므로 성능에 영향을 미칩니다.


2. bin(n).count('1')의 성능

반면, 두 번째 코드 (nextBigNumber)는 bin(n).count('1')를 사용하여 1의 개수를 구합니다.

  • bin(n): 숫자를 이진 문자열로 변환하는데 **O(log n)**의 시간이 걸립니다.
  • .count('1'): 변환된 문자열에서 '1'의 개수를 세는데 **O(log n)**의 시간이 걸립니다.

즉, 이진 변환 + 카운팅까지 포함해도 O(log n) 정도의 성능입니다.


3. 전체 반복문에서의 차이

첫 번째 코드 (solution)에서는 bin_one(answer)를 호출하는데, 이 함수는 O(log n)입니다.
즉, while 루프에서 매번 O(log n) 연산이 필요합니다.

반면, 두 번째 코드 (nextBigNumber)에서는 bin(n).count('1')을 사용하여 단순한 문자열 연산으로 1의 개수를 구합니다.
즉, 첫 번째 코드보다 비교적 연산량이 적습니다.


결론

  • 첫 번째 코드(solution): bin_one(n)이 O(log n)이므로 전체적으로 O(log n * k) (k는 while 루프의 반복 횟수)
  • 두 번째 코드(nextBigNumber): bin(n).count('1')을 사용하여 O(log n * k), 하지만 연산이 간결하여 더 빠름

=> 속도도 더 빠르다고 한다.

 

def nextBigNumber(n):
    answer = 0
    int2bin = "{0:b}".format(n)
    count = int2bin.count('1')

    if count == len(int2bin):
        answer = '0'+int2bin
        return int(answer,2)

    if count - 1 == len(int2bin):
        for i in range(0, count):
            answer = answer + '1'
        return int('0' + answer,2)

    r_int2char = int2bin[::-1]
    index = 0
    flag = 0
    if r_int2char[0:1] == '0':
        for i in range(1, len(int2bin)):
            if index != 0:
                break
            if r_int2char[i:].find('1') == 0:
                flag = 1
            if r_int2char[i:].find('0') == 0 and flag == 1:
                index = i
    else:
        for i in range(0, len(int2bin)):
            if index != 0:
                break
            if r_int2char[i:].find('0') == 0:
                index = i

    realIndex = len(int2bin) - index -1 
    answer = int2bin[0:realIndex]
    answer = answer + '1'
    rCount = count - answer.count('1')

    banswer = ''
    for i in range(0,rCount):
        banswer = banswer + '1'

    for i in range(0, len(int2bin)):
        if (len(answer) + len(banswer)) == len(int2bin):

            return int(answer + banswer,2)
        else:
            banswer = '0' + banswer

    return answer

1. 2진수 변환 및 1의 개수 세기

 
int2bin = "{0:b}".format(n)  # n을 2진수 문자열로 변환
count = int2bin.count('1')   # 2진수에서 1의 개수를 셈
  • n을 이진수 문자열로 변환합니다.
  • 변환된 이진수에서 '1'의 개수를 세어 변수 count에 저장합니다.

2. 예외 처리: 모든 비트가 1일 경우

if count == len(int2bin):
    answer = '0' + int2bin
    return int(answer, 2)
 
  • 만약 n의 이진수 표현이 모두 1로만 이루어져 있다면, n보다 큰 숫자 중 가장 작은 값은 10...00 (맨 앞에 1을 추가하고 나머지는 0)입니다.
  • 예를 들어 111(7)의 경우, 가장 가까운 큰 수는 1000(8)이 됩니다.

3. 예외 처리: count - 1 == len(int2bin) 경우

 
if count - 1 == len(int2bin):
    for i in range(0, count):
        answer = answer + '1'
    return int('0' + answer,2)
  • 이 조건은 절대 성립할 수 없습니다. count - 1 == len(int2bin)이 될 경우, 1의 개수가 전체 길이보다 하나 적어야 하는데, 이는 불가능한 상황입니다. (예를 들어 1111(15)는 count=4, len(int2bin)=4이며 count-1=3이므로 성립하지 않음)
  • 불필요한 코드라고 볼 수 있습니다.

4. 오른쪽에서부터 0을 찾아 1과 스왑하는 과정

r_int2char = int2bin[::-1]  # 문자열을 뒤집음 (오른쪽부터 찾기 위함)
index = 0
flag = 0
if r_int2char[0:1] == '0':  # 맨 끝이 0일 경우
    for i in range(1, len(int2bin)):
        if index != 0:
            break
        if r_int2char[i:].find('1') == 0:
            flag = 1
        if r_int2char[i:].find('0') == 0 and flag == 1:
            index = i
else:  # 맨 끝이 1일 경우
    for i in range(0, len(int2bin)):
        if index != 0:
            break
        if r_int2char[i:].find('0') == 0:
            index = i
  • 뒤에서부터 0을 찾고, 1과 교체할 위치를 결정하는 과정입니다.
  • 하지만 r_int2char[i:].find('1') == 0 등의 방식은 비효율적입니다.
  • 단순히 n의 2진수를 오른쪽에서부터 탐색해 처음 등장하는 01을 10으로 바꾸면 되는 문제입니다.

5. 1의 개수를 유지하면서 작은 자리부터 채우기

 
realIndex = len(int2bin) - index - 1 
answer = int2bin[0:realIndex]
answer = answer + '1'
rCount = count - answer.count('1')

banswer = ''
for i in range(0, rCount):
    banswer = banswer + '1'

for i in range(0, len(int2bin)):
    if (len(answer) + len(banswer)) == len(int2bin):
        return int(answer + banswer, 2)
    else:
        banswer = '0' + banswer
  • realIndex를 통해 1을 변경할 위치를 찾습니다.
  • 앞부분(answer)과 나머지 1을 채우는 부분(banswer)을 조합해 새로운 숫자를 만듭니다.

6. 코드 개선

이 코드를 더 간결하고 효율적으로 만들 수 있습니다.

개선된 코드

python
복사편집
def nextBigNumber(n): count = bin(n).count('1') # 1의 개수 구하기 while True: n += 1 # 다음 숫자 확인 if bin(n).count('1') == count: # 1의 개수가 같다면 반환 return n
def nextBigNumber(n):
    count = bin(n).count('1')  # 1의 개수 구하기
    while True:
        n += 1  # 다음 숫자 확인
        if bin(n).count('1') == count:  # 1의 개수가 같다면 반환
            return n

개선된 코드의 장점

  1. 가독성 향상: 원래 코드보다 훨씬 간결하고 직관적입니다.
  2. 버그 수정: 원본 코드에는 불필요한 예외 처리 및 논리 오류가 있었지만, 이 방식은 직접적인 반복문을 사용하여 문제를 해결합니다.
  3. 이해하기 쉬움: 주어진 수 n보다 크면서 1의 개수가 같은 가장 작은 숫자를 찾는 문제이므로, 단순히 n+1부터 검사하는 것이 직관적입니다.

코드가 안읽혀서 gpt한테 부탁함. 읽으면 배울 점이 많은 것 같다.

def nextBigNumber(n, count = 0):
    return n if bin(n).count("1") is count else nextBigNumber(n+1, bin(n).count("1") if count is 0 else count)

1. 함수 정의 및 기본 매개변수 설정

def nextBigNumber(n, count=0):
  • n: 현재 탐색 중인 숫자
  • count: n의 이진수에서 1의 개수를 저장하는 변수 (초기값 0)

2. 재귀 조건

return n if bin(n).count("1") is count else ...
  • 현재 n의 2진수에서 1의 개수가 count와 같다면 n을 반환합니다.
  • 즉, count와 같은 1의 개수를 가진 n을 찾으면 종료됩니다.

3. 다음 숫자로 재귀 호출

nextBigNumber(n+1, bin(n).count("1") if count is 0 else count)
  • count == 0인 경우:
    • 첫 번째 호출에서 count가 0이므로 bin(n).count("1")로 n의 1의 개수를 계산하여 count에 저장합니다.
  • count != 0인 경우:
    • 이미 count 값이 설정되어 있으므로 유지합니다.
  • 그런 다음 n+1을 넣고 재귀 호출하여 1의 개수가 같은 가장 작은 n을 찾을 때까지 반복합니다.

4. 예제 실행

예제 1: n = 78

  1. bin(78) = '0b1001110' → 1의 개수 = 4
  2. nextBigNumber(79, 4) 호출
    • bin(79) = '0b1001111' → 1의 개수 = 5 (다름)
  3. nextBigNumber(80, 4) 호출
    • bin(80) = '0b1010000' → 1의 개수 = 2 (다름)
  4. nextBigNumber(81, 4) 호출
    • bin(81) = '0b1010001' → 1의 개수 = 3 (다름)
  5. nextBigNumber(82, 4) 호출
    • bin(82) = '0b1010010' → 1의 개수 = 3 (다름)
  6. nextBigNumber(83, 4) 호출
    • bin(83) = '0b1010011' → 1의 개수 = 4 (같음) → 반환

출력: 83


5. 개선할 점

  • is 대신 ==을 사용해야 합니다.is는 객체 비교(메모리 주소 비교)이고, count는 정수이므로 ==을 사용하는 것이 맞습니다.
return n if bin(n).count("1") == count else nextBigNumber(n+1, bin(n).count("1") if count == 0 else count)
  • 재귀 호출이 많아질 경우 스택 오버플로우 위험이 있습니다. 대신 while 루프를 사용하면 반복 호출 없이 해결할 수 있습니다.
def nextBigNumber(n):
    count = bin(n).count("1")
    while True:
        n += 1
        if bin(n).count("1") == count:
            return n

6. 요약

  • 재귀를 이용한 방식으로 n보다 크면서 1의 개수가 같은 가장 작은 수를 찾는 코드입니다.
  • bin(n).count("1")로 1의 개수를 비교하며 n+1씩 증가시키는 방식입니다.
  • is 대신 ==을 사용해야 하며, 반복문을 쓰면 더 효율적입니다.