[이코테] 동적계획법 문제 풀이

shin·2022년 7월 29일
0

CodingTest 문제 풀이

목록 보기
16/79

1. 개미 전사 문제

1) 문제 설명

  • 개미 전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 합니다. 메뚜기 마을에는 여러 개의 식량 창고가 있는데 식량 창고는 일직선으로 이어져 있습니다.

  • 각 식량 창고에는 정해진 수의 식량을 저장하고 있으며 개미 전사는 식량 창고를 선택적으로 약탈하여 식량을 빼앗을 예정입니다. 이때 메뚜기 정찰병들은 일직선 상에 존재하는 식량 창고 중에서 서로 인접한 식량 창고가 공격 받으면 바로 알아챌 수 있습니다.

  • 따라서 개미 전사가 정찰병에게 들키지 않고 식량 창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량 창고를 약탈해야 합니다.

  • 예를 들어 식량 창고 4개가 다음과 같이 존재한다고 가정합시다.

    {1, 3, 1, 5}

  • 이때 개미 전사는 두 번째 식량 창고와 네 번째 식량 창고를 선택했을 때 최댓값인 총 8개의 식량을 빼앗을 수 있습니다. 개미 전사는 식량 창고가 이렇게 일직선 상일 때 최대한 많은 식량을 얻기를 원합니다.

  • 개미 전사를 위해 식량 창고 N개에 대한 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성하세요.

2) 문제 조건

  • 풀이 시간 : 30분

  • 시간 제한 : 1초

  • 메모리 제한 : 128MB

  • 입력 조건 :

    • 첫째 줄에 식량 창고의 개수 N이 주어집니다. (3 <= N <= 100)
    • 둘째 줄에 공백을 기준으로 각 식량 창고에 저장된 식량의 개수 K가 주어집니다. (0 <= K <= 1,000)
  • 출력 조건 : 첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하세요.

  • 입력 예시 :

    4
    1 3 1 5

  • 출력 예시 :

    8

3) 문제 해결 아이디어

  • N = 4일 때, 식량을 선택할 수 있는 경우의 수는 8가지

  • 7번째 경우에서 8만큼의 식량을 얻을 수 있으므로 최적의 해는 8

  • ai = i번째 식량 창고까지의 최적의 해(얻을 수 있는 식량의 최댓값)

    • 이렇게 정의하면 다이나믹 프로그래밍을 적용할 수 있음
  • 왼쪽부터 차례대로 식량창고를 턴다고 했을 때, 특정한 i번째 식량창고에 대해서 털지 안 털지의 여부를 결정하면, 아래 2가지 경우 중에서 더 많은 식량을 털 수 있는 경우를 선택하면 됩니다.

  • ai = i번째 식량 창고까지의 최적의 해(얻을 수 있는 식량의 최댓값)

  • ki = i번째 식량 창고에 있는 식량의 양

  • 점화식은 다음과 같음

  • 한 칸 이상 떨어진 식량 창고는 항상 털 수 있으므로 (i - 3)번째 이하는 고려할 필요가 없음

4) 강의 풀이

# 정수 N을 입력 받기
n = int(input())
# 모든 식량 정보 입력 받기
k = list(map(int, input().split()))

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100

# 다이나믹 프로그래밍 진행 (보텀업)
d[0] = k[0]
d[1] = max(k[0], k[1])
for i in range(2, n):
  d[i] = max(d[i - 1], d[i - 2] + k[i])

print(d[n - 1])

2. 1로 만들기 문제

1) 문제 설명

  • 정수 X가 주어졌을 때, 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지입니다.

    1. X가 5로 나누어 떨어지면, 5로 나눕니다.
    2. X가 3으로 나누어 떨어지면, 3으로 나눕니다.
    3. X가 2로 나누어 떨어지면, 2로 나눕니다.
    4. X에서 1을 뺍니다.
  • 정수 X가 주어졌을 때, 연산 4개를 적절히 사용해서 값을 1로 만들고자 합니다. 연산을 사용하는 횟수의 최솟값을 출력하세요. 예를 들어 정수가 26이면 다음과 같이 계산해서 3번의 연산이 최솟값입니다.

    26 -> 25 -> 5 -> 1

2) 문제 조건

  • 풀이 시간 : 20분

  • 시간 제한 : 1초

  • 메모리 제한 : 128MB

  • 입력 조건 : 첫째 줄에 정수 X가 주어집니다. (1 <= X <= 30,000)

  • 출력 조건 : 첫째 줄에 연산을 하는 횟수의 최솟값을 출력합니다.

  • 입력 예시

    26

  • 출력 예시

    3

3) 나의 풀이

def solution(x):
    dp = [0] * 30001
    dp[2] = 1
    dp[3] = 1
  
    if x == 1:
        return 0
    elif x == 2 or x == 3:
        return 1  
    
    num = 4
    while num <= x:
        n = num - 1
        if num % 5 == 0:
            if dp[n] > dp[int(num // 5)]:
                n = int(num // 5)
        if num % 3 == 0:
            if dp[n] > dp[int(num // 3)]:
                n = int(num // 3)
        if num % 2 == 0:
            if dp[n] > dp[int(num // 2)]:
                n = int(num // 2)
        dp[num] = dp[n] + 1
        num += 1
    return dp[x]

x = int(input())
print(solution(x))

4) 문제 해결 아이디어

  • 최적 부분 구조와 중복되는 부분 문제를 만족함
  • ai = i를 1로 만들기 위한 최소 연산 횟수
  • 점화식
  • 단, 1을 빼는 연산을 제외하고는 해당 수로 나누어떨어질 때에 한해 점화식을 적용할 수 있음

5) 강의 풀이

def solution(x):
  # 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
  d = [0] * 30001
  # 다이나믹 프로그래밍 진행 (보텀업)
  for i in range(2, x + 1):
    # 현재의 수에서 1을 빼는 경우
    d[i] = d[i - 1] + 1
    # 현재의 수가 2로 나누어 떨어지는 경우
    if i % 2 == 0:
      d[i] = min(d[i], d[i // 2] + 1)
    # 현재의 수가 3으로 나누어 떨어지는 경우
    if i % 3 == 0:
      d[i] = min(d[i], d[i // 3] + 1)
    # 현재의 수가 5로 나누어 떨어지는 경우
    if i % 5 == 0:
      d[i] = min(d[i], d[i // 5] + 1)
  return d[x]

x = int(input()) # 정수 X 입력 받기
print(solution(x))

3. 효율적인 화폐 구성 문제

1) 문제 설명

  • N가지 종류의 화폐가 있습니다. 이 화폐들의 개수를 최소한으로 이용해서 그 가치의 합이 M원이 되도록 하려고 합니다. 이때 각 종류의 화폐는 몇 개라도 사용할 수 있습니다.
  • 예를 들어 2원, 3원 단위의 화폐가 있을 때는 15원을 만들기 위해 3원을 5개 사용하는 것이 가장 최소한의 화폐 개수입니다.
  • M원을 만들기 위한 최소한의 화폐 개수를 출력하는 프로그램을 작성하세요.

2) 문제 조건

  • 풀이 시간 : 30분

  • 시간 제한 : 1초

  • 메모리 제한 : 128MB

  • 입력 조건

    -첫째 줄에 N, M이 주어진다. (1 <= N <= 100, 1 <= M <= 10,000)

    • 이후의 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐의 가치는 10,000보다 작거나 같은 자연수이다.
  • 출력 조건

    • 첫째 줄에 최소 화폐 개수를 출력한다.
    • 불가능할 때는 -1을 출력한다.
  • 입력 예시 1 :

    2 15
    2
    3

  • 출력 예시 1 :

    5

  • 입력 예시 2 :

    3 4
    3
    5
    7

  • 출력 예시 2 :

    -1

3) 문제 해결 아이디어

  • ai = 금액 i를 만들 수 있는 최소한의 화폐 개수
  • k = 각 화폐의 단위
  • 점화식 : 각 화폐 단위인 k를 하나씩 확인하며

N = 3, M = 7이고, 각 화폐의 단위가 2, 3, 5인 경우

  • [Step 0] (초기화)

    • 먼저 각 인덱스에 해당하는 값으로 INF(무한)의 값을 설정함
    • INF은 특정 금액을 만들 수 있는 화폐 구성이 가능하지 않다는 의미를 가짐
    • 본 문제에서는 10,001을 사용할 수 있음
  • [Step 1]

    • 첫 번째 화폐 단위인 2를 확인
    • 점화식에 따라서 다음과 같이 리스트가 갱신됨
  • [Step 2]

    • 두 번째 화폐 단위인 3를 확인
    • 점화식에 따라서 다음과 같이 리스트가 갱신됨
  • [Step 3]

    • 세 번째 화폐 단위인 5를 확인
    • 점화식에 따라서 다음과 같이 최종적으로 리스트가 갱신됨

4) 강의 풀이

# 정수 N, M을 입력 받기
n, m = map(int, input().split())
# N개의 화폐 단위 정보를 입력 받기
array = [int(input()) for _ in range(n)]

# 한 번 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [10001] * (m + 1)

# 다이나믹 프로그래밍 진행 (보텀업)
d[0] = 0
for i in range(n):
  for j in range(array[i], m + 1):
    if d[j - array[i]] != 10001: # (i - k)원을 만드는 방법이 존재하는 경우
      d[j] = min(d[j], d[j - array[i]] + 1)

# 계산된 결과 출력
if d[m] == 10001: # 최종적으로 M원을 만드는 방법이 없는 경우
  print(-1)
else:
  print(d[m])

4. 금광 문제

1) 문제 설명

  • n x m 크기의 금광이 있습니다. 금광은 1 x 1 크기의 칸으로 나누어져 있으며, 각 칸은 특정한 크기의 금이 들어 있습니다.
  • 채굴자는 첫 번째 열부터 출발하여 금을 캐기 시작합니다. 맨 처음에는 첫 번재 열의 어느 행에서든 출발할 수 있습니다. 이후에 m - 1번에 걸쳐서 매번 오른쪽 위, 오른쪽, 오른쪽 아래 3가지 중 하나의 위치로 이동해야 합니다. 결과적으로 채굴자가 얻을 수 있는 금의 최대 크기를 출력하는 프로그램을 작성하세요.

2) 문제 조건

  • 풀이 시간 : 30분

  • 시간 제한 : 1초

  • 메모리 제한 : 128MB

  • 기출 : Flipkart 인터뷰

  • 입력 조건

    • 첫째 줄에 테스트 케이스 T가 입력됩니다. (1 <= T <= 1000)
    • 매 테스트 케이스 첫째 줄에 n과 m이 공백으로 구분되어 입력됩니다. (1 <= n,m <= 20) 둘째 줄에 n x m 개의 위치에 매장된 금의 개수가 공백으로 구분되어 입력됩니다. (1 <= 각 위치에 매장된 금의 개수 <= 100)
  • 출력 조건 : 테스트 케이스마다 채굴자가 얻을 수 있는 금의 최대 크기를 출력합니다. 각 테스트 케이스는 줄 바꿈을 이용해 구분합니다.

  • 입력 예시

    2
    3 4
    1 3 3 2 2 1 4 1 0 6 4 7
    4 4
    1 3 1 5 2 2 4 1 5 0 2 3 0 6 1 2

  • 출력 예시

    19
    16

3) 나의 풀이

# 테스트 케이스 입력 받기
t = int(input())
for i in range(t):
  # n, m 입력 받기
  n, m = map(int, input().split())
  # n x m 개의 위치에 매장된 금의 개수 입력 받기
  maps = list(map(int, input().split()))

  # DP 테이블 생성
  dp = [0] * 401
  for j in range(n):
    # 첫 번째 열의 모든 행의 값은 기존 값으로 넣기
    dp[j * m] = maps[j * m]

  idx = 0
  # 아래쪽 행의 변화를 위쪽 행에도 적용하기 위해 두 번 반복
  for _ in range(2): 
    for idx in range(n * m - 1):
      # 마지막 열 위치라면 이동할 수 없음
      if (idx + 1) >= m and (idx + 1) % m == 0:
        idx += 1
        continue
      else:
        # 오른쪽으로 이동하는 경우
        dp[idx + 1] = max(dp[idx + 1], dp[idx] + maps[idx + 1])
        # 오른쪽 위로 이동하는 경우
        if idx - m + 1 > 0:
          dp[idx - m + 1] = max(dp[idx - m + 1], dp[idx] + maps[idx - m + 1])
        # 오른쪽 아래로 이동하는 경우
        if idx + m + 1 < n * m:
          dp[idx + m + 1] = max(dp[idx + m + 1], dp[idx] + maps[idx + m + 1])
          
  result = 0
  # 마지막 열에서 가장 큰 값을 결과 값으로 선택
  for k in range(n * m):
    if (k + 1) >= m and (k + 1) % m == 0:
      result = max(result, dp[k])
  print(result)

4) 문제 해결 아이디어

  • 금광의 모든 위치에 대하여 다음의 세 가지만 고려하면 됨

    1. 왼쪽 위에서 오는 경우
    2. 왼쪽 아래에서 오는 경우
    3. 왼쪽에서 오는 경우
  • 세 가지 경우 중에서 가장 많은 금을 가지고 있는 경우를 테이블에 갱신해주어 문제를 해결함

  • array[i][j] = i행 j열에 존재하는 금의 양

  • dp[i][j] = i행 j열까지의 최적의 해 (얻을 수 있는 금의 최댓값)

  • 점화식

  • 이때 테이블에 접근할 때마다 리스트의 범위를 벗어나지 않는지 체크해야 함

  • 편의상 초기 데이터를 담는 변수 array를 사용하지 않아도 됨

  • 바로 DP 테이블에 초기 데이터를 담아서 다이나믹 프로그래밍을 적용할 수 있음

5) 강의 풀이

# 테스트 케이스 입력
for tc in range(int(input())):
  # 금광 정보 입력
  n, m = map(int, input().split())
  array = list(map(int, input().split()))

  # 다이나믹 프로그래밍을 위한 2차원 DP 테이블 초기화
  dp = []
  index = 0
  for i in range(n):
    dp.append(array[index:index + m])
    index += m
  # 다이나믹 프로그래밍 진행
  for j in range(1, m):
    for i in range(n):
      # 왼쪽 위에서 오는 경우
      if i == 0: left_up = 0
      else: left_up = dp[i - 1][j - 1]
      # 왼쪽 아래에서 오는 경우
      if i == n - 1: left_down = 0
      else: left_down = dp[i + 1][j - 1]
      # 왼쪽에서 오는 경우
      left = dp[i][j - 1]
      dp[i][j] = dp[i][j] + max(left_up, left_down, left)
  result = 0
  for i in range(n):
    result = max(result, dp[i][m - 1])
  print(result)

5. 병사 배치하기 문제

1) 문제 설명

  • N명의 병사가 무작위로 나열되어 있습니다. 각 병사는 특정한 값의 전투력을 보유하고 있습니다.

  • 병사를 배치할 때는 전투력이 높은 병사가 앞쪽에 오도록 내림차순으로 배치를 하고자 합니다. 다시 말해 앞쪽에 있는 병사의 전투력이 항상 뒤쪽에 있는 병사보다 높아야 합니다.

  • 또한 배치 과정에서는 특정한 위치에 있는 병사를 열외시키는 방법을 이용합니다. 그러면서도 남아 있는 병사의 수가 최대가 되도록 하고 싶습니다.

  • 예를 들어, N = 7일 때 나열된 병사들의 전투력이 다음과 같다고 가정하겠습니다.

  • 이때 3번 병사와 6번 병사를 열외시키면, 다음과 같이 남아 있는 병사의 수가 내림차순의 형태가 되며 5명이 됩니다. 이는 남아 있는 병사의 수가 최대가 되도록 하는 방법입니다.

  • 병사에 대한 정보가 주어졌을 때, 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외시켜야 하는 병사의 수를 출력하는 프로그램을 작성하세요.

2) 문제 조건

  • 풀이 시간 : 40분

  • 시간 제한 : 1초

  • 메모리 제한 : 256MB

  • 기출 : 핵심 유형

  • 입력 조건 :

    • 첫째 줄에 N이 주어집니다. (1 <= N <= 2,000)
    • 둘째 줄에 각 병사의 전투력이 공백으로 구분되어 차례대로 주어집니다. 각 병사의 전투력은 10,000,000보다 작거나 같은 자연수입니다.
  • 출력 조건 : 첫째 줄에 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외시켜야 하는 병사의 수를 출력합니다.

  • 입력 예시

    7
    15 11 4 8 5 2 4

  • 출력 예시

    2

3) 나의 풀이

n = int(input())
array = list(map(int, input().split()))

count = 0
for i in range(1, n):
  if array[i - 1] < array[i]:
    count += 1
    i += 1
    
print(count)

4) 문제 해결 아이디어

  • 이 문제의 기본 아이디어는 가장 긴 증가하는 부분 수열(Longest Increasing Subsequence, LIS)로 알려진 전형적인 다이나믹 프로그래밍 문제의 아이디어와 같음

  • 예를 들어 하나의 수열 array = {4, 2, 5, 8, 4, 11, 15}이 있다고 하면, 이 수열의 가장 긴 증가하는 부분 수열은 {4, 5, 8, 11, 15}가 됨

  • 본 문제는 가장 긴 감소하는 부분 수열을 찾는 문제로 치환할 수 있으므로, LIS 알고리즘을 조금 수정하여 적용함으로써 정답을 도출할 수 있음

  • 가장 긴 증가하는 부분 수열 (LIS) 알고리즘

    • D[i] = array[i]를 마지막 원소로 가지는 부분 수열의 최대 길이
    • 점화식
    • 예시

5) 풀이 수정

n = int(input())
array = list(map(int, input().split()))

# 순서를 뒤집어 '최장 증가 부분 수열' 문제로 변환
array.reverse()

# 다이나믹 프로그래밍을 위한 1차원 DP 테이블 초기화
dp = [1] * (n)

# 가장 긴 증가하는 부분 수열(LIS) 알고리즘 수행
for i in range(1, n):
  for j in range(0, i):
    if array[j] < array[i]:
      dp[i] = max(dp[i], dp[j] + 1)

# 열외해야 하는 병사의 최소 수를 출력
print(n - max(dp))

Dynamic Programming 이론 정리

출처 : 이것이 취업을 위한 코딩 테스트다 with 파이썬

profile
Backend development

0개의 댓글