ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • PART02 - CHAPTER 03. 그리디
    algorithm/이것이 취업을 위한 코딩테스트다 2023. 3. 24. 12:46

    1. 당장 좋은 것만 선택하는 그리디

    - 그리디 알고리즘은 어떠한 문제가 있을 때 단순 무식하게, 탐욕적으로 문제를 푸는 알고리즘이다.

        ㆍ현재 상황에서 지금 당장 좋은 것만 고르는 방법

    - 그리디 알고리즘을 이용하면 매 순간 가장 좋아 보이는 것을 선택하며,  현재의 선택이 나중에 미칠 영향에 대해서는 고려하지 않는다.

     

    - 정렬, 최단 경로 등의 알고리즘 유형은 이미 알고리즘의 사용 방법을 정확히 알고 있어야만 해결 가능한 경우가 많다.

    - 예를 들어 여러 개의 데이터를 빠르게 정렬해야 하는 문제는 정렬 라이브러리의 사용 방법을 알고 있어야한다.

        또 다른 예시로 최단 경로를 빠르게 찾아야 하는 문제는 플로이드 워셜 혹은 다익스트라 알고리즘과 같은 특정 알고리즘을 미리 알고 있어야한다.

     

    - 그리디 알고리즘은 사전 지식 없이도 풀 수 있는 문제이지만, 많은 유형을 접해보고 문제를 풀어보며 훈련을 해야한다.

    - 그리디 알고리즘은 기준에 따라 좋은 것을 선택하는 알고리즘이므로 문제에서 '가장 큰 순서대로', '가장 작은 순서대로'와 같은 기준을 알게 모르게 제시해준다.

    - 대체로 이 기준은 정렬 알고리즘을 사용했을 때 만족시킬 수 있으므로 그리디 알고리즘 문제는 자주 정렬 알고리즘과 짝을 이뤄 출제된다.

     

    예제 3-1 거스르돈

    n = 1260
    count = 0
    
    # 큰 단위의 화폐부터 차례대로 확인
    coin_types = [500, 100, 50, 10]
    
    for coin in coin_types:
    	count += n//coin # 해당 화폐로 거슬러 줄 수 있는 동전의 개수 세기
        n %= coin
        
    print(count)

    그리디 알고리즘의 정당성

    - 대부분의 그리디 알고리즘 문제에서는 이처럼 문제 풀이를 위한 최소한의 아이디어를 떠올리고 이것이 정당한지 검토할 수 있어야 답을 도출할 수 있다.

    - 어떤 코딩 테스트 문제를 만났을 때, 바로 문제 유형을 파악하기 어렵다면 그리디 알고리즘을 의심하고, 문제를 해결할 수 있는 탐욕적인 해결법이 존재하는지 고민해보자.

    - 만약 오랜 시간을 고민해도 그리디 알고리즘으로 해결 방법을 찾을 수 없다면, 그때는 이후의 장에서 다루게 될 다이나믹 프로그래밍이나 그래프 알고리즘 등으로 문제를 해결할 수 있는지를 재차 고민해보는 것도 한 방법이다.

     

    * 기업의 코딩 테스트를 안정적으로 통과하려면 다음에 소개되는 문제 각각에 대해 문제별 아이디어를 떠올리고 코드로 작성하기까지 시간이 30분 이내로 소요되어야 한다.

     

    2. [실전문제] 큰 수의 법칙

    - 이문제를 풀려면 가장 먼저 반복되는 수열에 대해서 파악해야 한다.

    # N, M, K를 공백으로 구분하여 입력받기
    n, m, k = map(int, input().split())
    # N개의 수를 공백으로 구분하여 입력받기
    data = list(map(int, input().split()))
    
    data.sort()	# 입력받은 수 정렬
    first = data[n-1]	# 가장 큰 수
    second = data[n-2]	# 두 번째로 큰 수
    
    # 가장 큰 수가 더해지는 횟수 계산
    count = int(m / (k+1)) * k
    count += m % (k+1)
    
    result = 0
    result += (count) * first	# 가장 큰 수 더하기
    result += (m - count) * second	# 두 번째로 큰 수 더하기
    
    print(result)	# 최종 답안 출력

    - m : 더해지는 횟수  , k : 반복가능한 횟수 , m/(k+1) : 한 사이클의 횟수 , m/(k+1) * k :  한 사이클의 횟수 x 반복횟수

       m%(k+1) : 사이클의 남은 횟수

     

    3. [실전문제] 숫자 카드 게임

    # N, M을 공백으로 구분하여 입력받기
    n, m = map(int, input().split())
    
    result = 0
    # 한 줄씩 입력받아 확인
    for i in range(n):
        data = list(map(int, input().split()))
        # 현재 줄에서 '가장 작은 수' 찾기
        min_value = min(data)
        # '가장 작은 수'들 중에서 가장 큰 수 찾기
        result = max(result, min_value)
    
    print(result)
    # N, M을 공백으로 구분하여 입력받기
    n, m = map(int, input().split())
    
    result = 0
    # 한 줄씩 입력받아 확인
    for i in range(n):
        data = list(map(int, input().split()))
        # 현재 줄에서 '가장 작은 수' 찾기
        min_value = 10001
        for a in data:
            min_value = min(min_value, a)
        # '가장 작은 수'들 중에서 가장 큰 수 찾기
        result = max(result, min_value)
     
    print(result)

    4. [실전문제] 1이 될 때까지

    - 과정이 아니라 결과에 집중하자!

    - '최대한 많이 나누기'

    - 단순하게 푸는 답안 예시

    n, k = map(int, input().split())
    result = 0
    
    # N이 K이상이라면 K로 계속 나누기
    while n >= k:
        # N이 K로 나누어 떨어지지 않는다면 N에서 1씩 빼기
        while n % k != 0:
            n -= 1
            result += 1
        # K로 나누기
        n //= k
        result += 1
        
    # 마지막으로 남은 수에 대하여 1씩 빼기
    while n > 1:
        n -= 1
        result += 1
        
    print(result)

        ㆍ문제에서 N의 범위가 10만 이하이므로, 이처럼 일일이 1씩빼도 문제를 해결할 수 있다.

        ㆍ하지만 N이 100억 이상의 큰 수가 되는 경우를 가정했을 때에도 빠르게 동작할려면, N이 K의 배수가 되도록 효율적으로 한 번에 빼는 방식으로 소스 코드를 작성할 수 있다.

    # N, K를 공백으로 구분하여 입력받기
    n, k = map(int, input().split())
    result = 0
    
    while True:
    	# (N == K로 나누어떨어지는 수)가 될 때까지 1씩 빼기
        target = (n//k) * k
        result += (n-target)
        n = target
        # N이 K보다 작을 때(더 이상 나눌 수 없을 때) 반복문 탈출
        if n < k :
        	break
        # K로 나누기
        result += 1
        n //= k
    
    result += (n-1)
    print(result)

     

    댓글

Designed by Tistory.