Introduction

본 포스트는 알고리즘 학습에 대한 정리를 재대로 하기 위하여 남기는 것입니다. 더불어 기본 내용은 나동빈 저의 〖이것이 취업을 위한 코딩 테스트다〗라는 교재 및 유튜브 강의의 내용에서 발췌했고, 그 외 추가적인 궁금 사항들을 검색 및 정리해둔 것입니다.

효율적인 화폐 구성

문제 설명

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

문제 조건

  1. 난이도 : 중상
  2. 풀이시간 : 30분
  3. 시간 제한 : 1초
  4. 메모리 제한 : 128MB
  • 입력 조건 :
    1. 첫째 줄에 N, M이 주어진다. (1<= N <= 100, 1 <= M <= 10,000)
    2. 이후 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐의 가치는 10,000보다 작거나 같은 자연수이다.
  • 출력 조건 :
    1. 첫째 줄에 최소 화폐 개수를 출력한다.
    2. 불가능 할 때는 -1 을 출력한다.
  • 입출력 예시 :
    # 입력 예시 1
    2 15
    2
    3
    # 출력 예시 1
    5
    # 입력 예시 2
    3 4
    3
    5
    7
    # 출력 예시 2
    -1

문제 해결 아이디어

  • 𝑎ᵢ = 금액 𝒊를 만들 수 있는 최소한의 화폐 개수

  • 𝒌 = 각 화폐의 단위

  • 점화식 : 각 화폐 단위인 𝒌를 하나씩 확인하며

    • a_{i - k} 를 만드는 방법이 존재 하는 경우 𝑎ᵢ = min(aᵢ, a_{i - k} + 1)
    • a_{i - k} 를 만드는 방법이 존재하지 않은 경우 𝑎ᵢ = INF
  • N = 3, M = 7, 화폐 단위가 2, 3, 5인 경우 확인해봅시다.

  • step 0

    • 각 인덱스에ㅎ 해당하는 값으로 INF의 값을 설정합니다.
    • INF는 특정 금액을 만 들 수 있는 화폐 구성이 가능하다는 의미를 가집니다.
    • 본 문제는 10,001을 사용할 수 있습니다. 이렇게 한 이유는 만들 수 있는 최대의 금액이 10,000이고, 화폐 최소 단위 1을 기준으로 1만개로 만들 수있는 값(MAX)의 1 더한 값으로, 기준 범위를 초과한 INF값으로 설정할 수 있습니다.
    인덱스 값 0 1 2 3 4 5 6 7
    0 10001 10001 10001 10001 10001 10001 10001
  • step 1

    • 첫 번째 화폐 단위가 2를 확인합니다.
    • 점화식에 따라서 다음 리스트 값들이 갱신 됩니다.
    인덱스 값 0 1 2 3 4 5 6 7
    0 10001 1 10001 2 10001 3 10001
    • 2원은 1개로 만들 수 있음
    • 4원은 2원 2개로 만들 수 있음
    • 나머지 값들 중 만들 수 없는 것들에 대해선 그대로 INF 상태를 둔다.(따라서 7원도 만들 수 없다.)
  • step 2

    • 두 번째 화폐 단위인 3를 확인합니다.
    인덱스 값 0 1 2 3 4 5 6 7
    0 10001 1 1 2 2 2 3
    • 5원 만들기 위한 개수 : 2개 (2원 + 3원)
    • 7원 만들기 위한 개수 : 3개 (2원 + 2원 + 3원)
  • step 3

    • 세 번째 화폐 단위인 5를 확인한다.
    인덱스 값 0 1 2 3 4 5 6 7
    0 10001 1 1 2 1 2 3
    • 7원 만들기 위한 개수 : 2원 + 5원

답안 예시(Python)

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

# DP 테이블 작성 및 초기 값으로 INF 값을 작성하기
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:
			d[j] = min(d[j]. d[j - array[i]] + 1)

# 결과 출력 시 해당 금액이 INF로 만드는 방법이 없는 경우
if d[m] == 10001:
	print(-1)
else :
	print(d[m])

답안 예시(C++)

#include <bit/stdc++.h>

using namespace std;

int n, m;
vector<int> arr;

int main(void)
{
	cin >> n >> m;
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		arr.push_back(x);
	}
	vector<int> d(m + 1, 10001);
	d[0] = 0;
	for (int i = 0; i < n; i++)
		for (int j = arr[i] != 10001)
		{
			if (d[j - arr[i] != 10001])
				d[j] = min(d[j], d[j - arr[i]] + 1);
		}
	if (d[m] == 10001)
		cout << -1 <<'\n';
	else
		cout << d[m] << '\n';
}

금광

문제 설명

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

➡︎ 얻을 수 있는 최대 금의 크기 : 19

문제 조건

  1. 난이도 : 중하
  2. 풀이시간 : 30분
  3. 시간 제한 : 1초
  4. 메모리 제한 : 128MB
  5. 기출 : Flipkart 인터뷰
  • 입력 조건 :
    1. 첫째 줄에 테스트 케이스 T가 입력됩니다.(1 <= T <= 1000)
    2. 매 테스트 케이스 첫번째 줄에 n, m이 공백으로 구분되 입력됩니다.(1 <= n, m <= 20)
    3. 둘 째 줄에는 n × 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 6 1 2
    # 출력 예시
    19
    16

문제 해결 아이디어

  • 금광의 모든 위치에 대해선 다음의 세가지 경우만 고려 됩니다.

    1. 왼쪽 위
    2. 왼쪽 아래
    3. 왼쪽 중간에서 오는 경우
  • 따라서 세가지 경우 중 가장 많이 금을 가지는 경우 + 현재 위치의 금 값으로 테이블을 갱신해주는 방식으로 최대 값을 구할 수 있습니다.

    ↘︎
    ⭐️
    ↗︎
  • array[i][j] = i행 j열에 존재하는 금의 양

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

  • 점화식 :

dp[i][j] = array[i][j] + max(dp[i - 1][j - 1], dp[i][j - 1], dp[i + 1][j - 1])

  • 단, 테이블 접근 시 리스트 범위를 벗어나지 않도록 체크합니다.
  • 편의상 초기 데이터를 담는 변수 array를 사용하지 않고 dp 테이블에 초기 값을 담아 사용해도 됩니다.
  • 그림으로 확인해보는 금광 문제 해결 과정
1 3 3
2 1 4
0 6 4
⬇︎
DP 테이블 초기화
1 5
2 3
0 8
1. (1, 2) 칸은 각 (1, 1), (2, 1) 중 큰값 + 자기 자신을 더해 값을 표시 합니다.
2. (2, 2) 칸은 각 (1, 1), (2, 1), (3, 1) 중 큰 값 + 자기 자신으로 값을 표시합니다.
.
.
.
(반복)

답안 예시(Python)

# 테스트 케이스 횟수에 따라 반복
for tc in range(int(intput())):
	n, m = map(int, input().split())
	array = list (map(int, input().split()))
	# 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 rnage(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, left_down)
	result = 0
	for i in range(n)
		result = max(result, dp[i][m - 1])
print(result)

답안 예시(C++)

#include <bits/stdc++.h>

using namespace std;

int testCase, n, m;
int arr[20][20];
int dp[20][20];

int main(void)
{
	cin >> testCase;
	for (int tc = 0; tc < testCase; tc++)
	{
		cin >> n >> m
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < m; j++)
				cin >> arr[i][j];
		}
	}
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			dp[i][j] = arr[i][j];
	for (int j = 1; j < m; j ++)
	{
		for (int i = 0; i < n; i++)
		{
			int leftUp, leftDown, left;
			if (i == 0)
				leftUp = 0;
			else
				leftUp = dp[i - 1][j - 1];
			if (i == n - 1)
				leftDown = 0;
			else
				leftDown = dp[i + 1][j - 1];
			left = dp[i][j - 1];
			dp[i][j] = dp[i][j] + max(leftUp, max(leftDown, left));
		}
	}
	int result = 0;
	for (int i = 0; i < n; i++)
		result = max(result, dp[i][m - 1]);
	cout << result << '\n';
}

병사 배치하기

문제 설명

  • N명의 병사가 무작위로 나열되어 있습니다. 각 병사는 특정한 값의 전투력을 보유하고 잇습니다.
  • 병사를 배치할 때는 전투력이 높은 병사가 앞쪽에 오도록 내림차순으로 배치를 하고자 합니다. 다시 말해 앞쪽에 있는 병사의 전투력이 항상 뒤쪽 병사보다 높아야 합니다.
  • 또한 배치 과정에서는 특정한 위치에 있는 병사를 열외 시키는 방법을 이용합니다. 그러면서도 남아 있는 병사의 수가 최대가 되도록 하고 싶습니다.
  • 예를 들어 N = 7일 때 나열된 병사들의 전투력은 다음과 같다고 가정합니다.
    병사 번호 1 2 3 4 5 6 7
    전투력 15 11 4 8 5 2 4
  • 이때 3번, 6번 만 열외하면 남아 있는 병사의 수가 내림차순의 형태가 되며 5명이 남게 됩니다. 이는 남아 있는 병사가 최대가 되면서도 내림차순으로 정렬된 형태입니다.
    병사 번호 1 2 4 6 7
    전투력 15 11 8 5 4
  • 병사에 대한 정보가 주어질 때, 남아 있는 병사 의 수가 최대가 되도록 하기 위해 열외시켜야 하는 병사의 수를 출력하는 프로그램을 작성하세요.

문제 조건

  1. 난이도 : 중하
  2. 풀이시간 40분
  3. 시간 제한 : 1초
  4. 메모리 제한 : 256MB
  5. 기출 : 핵심 유형
  • 입력 조건 :
    1. 첫째 줄에 N이 주어집니다(1<= N <= 2,000)
    2. 둘째 줄에 각 병사의 전투력이 공백으로 구분되어 차례대로 주어집니다. 전투력은 10,000,000보다 작거나 같은 자연수입니다.
  • 출력 조건 : 첫째 줄에 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외 시켜야 하는 병사의 수를 출력합니다.
  • 입출력예시 :
    # 입력 예시
    7
    15 11 4 8 5 2 4
    # 출력 예시
    2

문제 해결 아이디어

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

  • 예를 들어 하나의 수열 arr = {4, 2, 5, 8, 4, 11, 15}가 있다고 합시다. 여기서 가장 긴 증가하는 부분 수열은 {4, 5, 8, 11, 15} 입니다.

  • 해당 문제에선 가장 긴 감소하는 부분 수열을 찾는 문제로 치환 할 수 있으므로 LIS 알고리즘을 수정 적용함으로써 정답을 도출 할 수 있습니다.

  • 최장증가부분수열(LIS) 알고리즘을 확인하여 사용하면 됩니다.

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

  • 점화식

모든 O <= j < i에 대하여, D[i] = max(D[i], D[j] + 1) if array[j] < array[i]

초기 상태 i = 0
4 2 5 8 4 11 15
1 1 1 1 1 1 1
i = 1
최대 값이 2일 때이며, 이 경우 앞의 값보다 작으므로 바뀌지 않습니다.
4 2 5 8 4 11 15
1 1 1 1 1 1 1
i = 2
5를 마지막 값으로 보므로, 더 큰 값이 들어가게 됩니다.
4 2 5 8 4 11 15
1 1 2 1 1 1 1
i = 3
4 2 5 8 4 11 15
1 1 2 3 1 1 1
i = 4
4 2 5 8 4 11 15
1 1 2 3 2 1 1
i = 5
4 2 5 8 4 11 15
1 1 2 3 2 4 1
i = 6
4 2 5 8 4 11 15
1 1 2 3 2 4 5
  • 최종적으로 DP 테이블에 남아있는 값중 가장 큰 값인 것을 발견하면, LIS가 가장 긴 경우인 것이라고 볼 수 있습니다.
  • 그러나 최장감부분수열(LIS)알고리즘 형태가 되어야 하므로, 먼저 입력 받은 병사의 정보 순서를 뒤집어 계산을 한 뒤, 그 값을 전체 인원 수에서 빼서 정답을 도출 할 수 있습니다.

답안 예시(Python)

# 초기 값 받기
n = int(input())
array = list(map(int, intpu().split()))

# 리스트 뒤집어 LIS 배열 만들기 위한 준비
array.reverse()

# 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))

답안 예시(C++)

#include <bits/stdc++.h>

using namespace std;

int n;
vector<int> v;

int main(void)
{
	cint >> n;
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		v.push_back(x);
	}
	reverse(v.begin(), v.end());
	int dp[2000];
	for (int i = 0; i < n; i++)
		dp[i] = 1;
	for (int i = 1; i < n< i++)
	{
		for (int j = 0; j < i; j++)
		{
			if (v[j] < v[i])
				dp[i] = max(dp[i], dp[j] + 1);
		}
	}
	int maxValue = 0;
	for (int i = 0; i < n; i++)
		maxVlaue = max(maxValue, dp[i]);
	cout << n - maxValue << '\n';
}

🧑🏻‍💻 알고리즘 박살내기 시리즈🧑🏻‍💻