1. N을 1, 2, 3의 합으로 나타내기
예를 들어 N=5면 다음과 같이 나타낸다.
4
=1+1+1+1
=1+1+2
=1+2+1
=2+1+1
=2+2
=1+3
=3+1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public static int solve(int n) {
int[] a = new int[n];
a[0] = 0;
a[1] = 1;
a[2] = 1;
a[3] = 2;
//from idx=4 to idx=n
for(int i = 4; i < n; i++) {
a[i] = a[i - 2] + a[i - 3] + a[i - 4];
}
return a[n - 1];
}
|
cs |
인덱스가 헷갈리면 아래 코드를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public static int solve(int n) {
int[] a = new int[n + 1];
a[1] = 1;
a[2] = 2;
a[3] = 4;
for(int i = 4; i <= n; i++) {
a[i] = a[i - 1] + a[i - 2] + a[i - 3];
}
return a[n];
}
|
cs |
주어진 N을 1+(N−1),2+(N−2),3+(N−3)으로 보면 N−1을 만드는 경우의 수, N−2를 만드는 경우의 수, N−3를 만드는 경우의 수의 합이 된다.
나는 a[i]를 'i를 1,2,3의 합으로 나타내는 경우의 수'로 정의했으므로 위와 같은 풀이가 성립한다.
만약에 1,2,3이 아닌 3,4,5의 합으로 나타내는 것이 문제였다면 a[i]=a[i−3]+a[i−4]+a[i−5]가 됐을 것.
Time complexity: O(n)
2. 치즈

테이블의 각 요소를 그 위치까지 오면서 먹을 수 있는 치즈의 최대 개수로 정의하면,
즉 a[i][j]의 값을 (1,1)부터 (i,j)까지 이동하면서 먹을 수 있는 치즈의 최대 개수라 하면 다음과 같이 풀 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static int solve(int n, int[][] a) {
//a[i][j] = (i, j)까지 오면서 먹을 수 있는 치즈의 최대 개수
//Initialization
for(int i = 1; i < n; i++) {
a[i][0] += a[i - 1][0]; //1st column
a[0][i] += a[0][i - 1]; //1st row
}
/*
* 현재 위치로 오는 경로는 왼쪽으로부터 오거나 밑에서 올라오거나
* 따라서 그 둘 중 큰 쪽을 선택한다.
*/
for(int i = 1; i < n; i++) {
for(int j = 1; j < n; j++) {
a[i][j] += Math.max(a[i - 1][j], a[i][j - 1]);
}
}
return a[n - 1][n - 1];
}
|
cs |
각 위치로 도달하는 방법은 왼쪽에서 오거나 밑에서 오는 것 뿐이다.
따라서 A[i][j]는 A[i−1][j]와 A[i][j−1] 중 더 큰 값이다.
Base case는 테이블의 왼쪽과 아래 가장자리로, 이 부분은 아래에서 올라오거나 왼쪽에서 오는 경우 중 하나만 적용된다.
즉 왼쪽의 가장자리는 아래에서부터 더해 a[i][0]+=a[i−1][0]로 초기화하고,
아래의 가장자리는 a[0][i]+=a[0][i−1]로 초기화한다.
Time complexity: O(n2)
3. 동전

이 문제는 greedy algorithm으로도 풀 수 있(다고 들은 것 같)다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public static void main(String[] args) {
int k = 365;
int[] coins = new int[] {1, 4, 7, 13, 28, 52, 91, 365};
int[] a = new int[k + 1];
System.out.println(solve(a, coins, k));
}
/*
* 최소 금액이 1원보다 큰 경우 나타낼 수 없는 K가 발생한다
* 나타낼 수 없는 금액은 어떻게 처리하지...
*/
public static int solve(int[] a, int[] coins, int k) {
a[0] = 0; //sentinel
for(int i = 1; i < coins[0]; a[i++] = 0);
for(int i = coins[0]; i <= k; i++) {
a[i] = a[i - coins[0]] + 1;
for(int j = 1; j < coins.length && coins[j] <= i; j++) {
a[i] = Math.min(a[i], a[i - coins[j]] + 1);
}
}
return a[k];
}
|
ㅁcs |
a[i] = Nadiria의 동전으로 i원을 나타낼 때 필요한 동전의 최소 개수.
15행:
Base case.
최소 금액 미만의 금액은 나타낼 수 없으므로 0.
16행:
i원을 나타내는 데에 필요한 동전의 수는 i에서 어떤 동전 coin[j]의 금액을 뺀 금액을 나타내는 데에 필요한 경우의 수에 coin[j]의 수 1을 더한 것으로 나타낼 수 있다.
즉 i원을 나타내는 데에 필요한 동전의 수는
min{
(1원짜리 동전 1개 + (k-1)원을 나타내는 동전의 수),
(4원짜리 동전 1개 + (k-4)원을 나타내는 동전의 수),
(7원짜리 동전 1개 + (k-7)원을 나타내는 동전의 수),
(13원짜리 동전 1개 + (k-13)원을 나타내는 동전의 수),
(28원짜리 동전 1개 + (k-28)원을 나타내는 동전의 수),
(52원짜리 동전 1개 + (k-52)원을 나타내는 동전의 수),
(91원짜리 동전 1개 + (k-91)원을 나타내는 동전의 수),
(365원짜리 동전 1개 + (k-365)원을 나타내는 동전의 수),
}
다.
Time complexity: O(K)
주어진 금액 K에 선형적으로 비례함
4. 최대합 부분배열

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static void main(String[] args) {
int[] a = new int[] {31, -41, 59, 26, -53, 58, 97, -93, -23, 84};
System.out.println(solve(a));
}
public static int solve(int[] a) {
//m[i] = sum(a[0..i])
int[] m = new int[a.length];
m[0] = a[0];
for(int i = 1; i < m.length; i++) {
m[i] = Math.max(a[i] + m[i - 1], a[i]);
}
return max(m, 0);
}
|
cs |
7행의 주석에 써놨듯이 m[i]는 a[0..i]에서 합이 최대인 부분배열의 합이다.
따라서 주어진 n에 대해 m의 길이는 n이다.
a를 한 요소 씩 훑으면 다음의 두 선택지가 있다.
1) 현재까지 만들어진 부분배열의 끝에 a[i]를 추가한다.
2) 현재까지 만들어진 부분배열은 모두 버리고 a[i]부터 다시 부분배열을 만든다.
첫 번째 경우 m[i]는 m[i−1]+a[i]가 된다.
즉 i에서 현재까지 만들어진 부분배열의 합은 m[i−1]이므로 여기에 a[i]를 더하면 된다.
두 번째 경우 m[i]는 a[i]가 된다. 현재까지 만들어진 부분배열 m[i−1]은 버리고 a[i]부터 다시 부분배열을 만드는 것이다.
합을 최대로 하는 것이 목적이므로 두 경우 중 값이 더 큰 경우를 선택한다.
이 과정을 i=n까지 반복하고 나면 m[i]에는 A[0..i]에서 최대합 부분배열의 합이 저장되어 있다.
문제에서 원하는 것은 가장 큰 합이므로 m[0..i]의 최대값이 정답이 된다.
Time complexity: O(n)
배열 m을 채움 -> O(n)
m에서 최대값을 찾음 -> O(n)회
따라서 시간 복잡도는 O(n)이 된다.
5. 최대곱 부분배열

딱 보고 최대합 부분배열이랑 똑같이 하면 되겠다 싶었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static void main(String[] args) {
int[] a = new int[] {0, 14, 0, -7, 0, 5, 0, -6, 12, -7, 0};
System.out.println(solve(a));
}
public static int solve(int[] a) {
int[] max = new int[a.length];
int[] min = new int[a.length];
max[0] = min[0] = a[0];
for(int i = 1; i < a.length; i++) {
int[] temp = new int[] { a[i]*max[i - 1], a[i], a[i]*min[i - 1] };
max[i] = max(temp, 0);
min[i] = min(temp, 0);
}
return max(max, 0);
}
|
cs |
어림도 없지
핵심은 두 개의 배열이다.
0 이상의 수만 있다면 최대합 부분배열이랑 똑같이 해도 상관 없지만 문제에서는 음수도 포함하고 있다.
이 음수가 문제다. 음수끼리 곱하면 더 큰 양수가 될 수 있기 때문에 이 경우를 따로 생각해줘야 한다.
때문에 두 개의 배열이 필요하다. 한 배열에는 최대값을, 한 배열에는 최소값을 저장한다.
최소값에 음수를 곱하면 최대값이 될 수 있기 때문이다.
따라서 다음의 세 선택지가 있다.
1) 현재까지 만들어진 최대값(양수)에 a[i]를 곱한다.
2) 현재까지 만들어진 부분배열을 버리고 a[i]부터 다시 부분배열을 만든다.
3) 현재까지 만들어진 최소값(음수)에 a[i]를 곱한다.
max에는 이 세 선택지 중 값이 가장 큰 경우, min에는 가장 값이 작은 경우가 저장된다.
따라서 max에 의해 양수와 양수의 곱이 최대인 경우를, min에 의해 음수와 음수의 곱이 최대인 경우를 모두 생각할 수 있다.
Time complexity: O(n)
'Programming > 과제' 카테고리의 다른 글
[과제] Divide and conquer (1) | 2021.05.21 |
---|---|
Pancake sorting by Recursion (0) | 2021.03.21 |
재귀호출을 이용한 최소값 찾기, 총합 구하기, 선택 정렬 (0) | 2021.03.21 |
Pancake sorting by Iteration (0) | 2021.03.16 |
재귀함수를 이용한 최소공배수 구하기 (0) | 2021.03.08 |