📌 "불필요한 계산을 줄이고, 효율적으로 최적해를 찾아야만 풀리는 문제들입니다."
출제빈도 : 낮음
평균점수 : 낮음
하나의 큰 문제를 여러개의 작은 문제로 나눠 그 결과를 저장한 다음 큰 문제를 해결할때 다시 사용하는 것
이미 계산한 것을 메모리에 저장하여 중복 계산을 막고, 수행 시간의 효율성을 향상시킨다.
알고리즘의 진행에 따라 탐색해야할 범위를 '동적'으로 결정한다.
겹치는 부분 문제 (Overlapping Subproblems)
: 동일한 작은 문제들이 반복(중복)해서 나타나는 경우
최적 부분 구조 (Optimal Substructure)
: 작은 부분 문제의 최적 결과값을 사용해서 전체 문제의 최적 결과를 낼 수 있는 경우
cf) 피보나치 수열 = 직전 두 항의 합이 다음 항이 되는 수열
: 작은 문제부터 큰 문제로, 반복문
d = [0] * 100
d[1] = 1
d[2] = 1
n = 99
for i in range(3, n + 1):
d[i] = d[i - 1] + d[i - 2]
print(d[n])
: 큰 문제부터 작은 문제로, 재귀호출
d = [0] * 100
def fibo(x):
if x == 1 or x == 2:
return 1
if d[x] != 0:
return d[x]
d[x] = fibo(x-1) + fibo(x-2)
return d[x]
print(fibo(99))
프로그래머스 고득점 Kit - 동적계획법(Dynamic Programming) 문제목록
아이디어가 도저히 생각이 안나서 규칙만 찾아보고 풀었다. 결론적으로 저렇게 끔찍한 코드를 짜고도 시간초과가 안난게 용하다.
1단계
N
을 1번 사용해서 만들 수 있는 수
= [N
]
2단계
N
을 2번 사용해서 만들 수 있는 수
= [NN
, N+N
, N-N
, N*N
, N/N
]
= [NN
,
{N
을 1번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 1번 사용해서 만들 수 있는 수}]
3단계
N
을 3번 사용해서 만들 수 있는 수
= [NNN
, N+(N+N)
, N-(N+N)
, N*(N+N)
, N/(N+N)
, ...]
= [NNN
,
{N
을 1번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 2번 사용해서 만들 수 있는 수},
{N
을 2번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 1번 사용해서 만들 수 있는 수}]
...
n단계
N
을 n번 사용해서 만들 수 있는 수
= [NN..(n번)..N
,
{N
을 1번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 n-1번 사용해서 만들 수 있는 수},
{N
을 2번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 n-2번 사용해서 만들 수 있는 수},
{N
을 3번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 n-3번 사용해서 만들 수 있는 수},
...
{N
을 n-1번 사용해서 만들 수 있는 수} x {+,-,*,/
} x {N
을 1번 사용해서 만들 수 있는 수}]
문제의 최대값이 8이므로 n단계는 사실상 8단계가 끝이다.
현재 단계에서 구할 수 있는 계산결과를 찾기 위해 이전에 계산해둔 값들을 사용하기 때문에 DP문제라고 할 수 있다.
number
를 시작으로 문제를 푸는게 아니라, 진짜 완전 바닥부터 시작하다가 number
가 나오면 코드를 끝내는 방식으로 풀었다.
삼각형의 바닥부터 시작!
그럼 결론적으로 아래와 같은 경로로 지날때 합이 최대가 된다.
지나는 경로의 합의 최대를 구하기 위해서 가능한 경우의 수가 적은 가장 바닥부터 경로합을 구해가며 올라갔다.
문제에서 요구하는 큰 경로의 합을 구하기 위해 작은 경로로 쪼개서 작은 경로의 합을 재활용해 큰 경로의 합을 구했다는 점에서 DP이긴한데, 시간복잡도를 줄이기 위해 사용 가능한 작은 경로의 합들 중에 최대값만 사용했다는 점에서 그리디이기도 한 것 같다.
프로그래머스 > 찾아라 프로그래밍 마에스터 > 사칙연산
[프로그래머스] 사칙연산 - Java
🧱🧱🧱🧱(아직) 넘을 수 없는 레벨4의 벽🧱🧱🧱🧱
정말 많은 사람들의 풀이를 보고 겨우겨우 어거지로 따라쳐서 통과했다..😮💨 그냥 문제 푸는데 필요했던 핵심만 간단하게만 정리하면
# 사용할 자료구조
dp_max[i][j] = [i]~[j]번째 피연산자까지의 연산결과 중 최대값
dp_min[i][j] = [i]~[j]번째 피연산자까지의 연산결과 중 최소값
# i <= k < j
[i]~[j]까지의 연산결과 중 최대값 = 모든 [i][k]~[k+1][j] 결과 중 최대값
[i]~[j]까지의 연산결과 중 최소값 = 모든 [i][k]~[k+1][j] 결과 중 최소값
# 왼쪽 수식이 최대가 되려면
(a + b) => (max + max) # 1. (a + b)가 최대가 되는 경우
(a - b) => (max - min) # 2. (a - b)가 최대가 되는 경우
-(a + b) => -(min + min) # 3. (a + b)가 최소가 되는 경우
-(a - b) => -(min - max) # 4. (a - b)가 최소가 되는 경우
# 코드로 짜면
if "+": # (max + max), -(min + min)
dp_max[i][j] = max(dp_max[i][k] + dp_max[k + 1][j], dp_max[i][j]) # 1
dp_min[i][j] = min(dp_min[i][k] + dp_min[k + 1][j], dp_min[i][j]) # 3
else "-": # (max - min), -(min - max)
dp_max[i][j] = max(dp_max[i][k] - dp_min[k + 1][j], dp_max[i][j]) # 2
dp_min[i][j] = min(dp_min[i][k] - dp_max[k + 1][j], dp_min[i][j]) # 4
테케 몇개가 실패해서 보니까 기본판 생성 과정에서 예외케이스(맨윗줄, 맨왼쪽줄에 웅덩이가 있으면 해당 줄의 다음칸으로는 갈 수 없으니까 모두 0으로 변경해야함)를 발견해서 코드가 좀 구려졌는데.. 최적화하기 귀찮아서 안할거다.
핵심아이디어 : (i,j)
에 올 수 있는 최단경로의 수 = (i-1, j)
에 올 수 있는 최단 경로의 수 + (i, j-1)
에 올 수 있는 최단경로의 수
오른쪽과 아래쪽으로밖에 이동하지 못한다는 건, 모든 경로가 최단경로라는 뜻
시작점(0,0)
부터 시작해서 한칸씩, 이전 단계에서 계산해둔 내 윗칸과 왼쪽칸에 있는 수들을 더해서 칸들을 채우다가 도착지점(m,n)
에 도달하면 종료한다.
도저히 뭐 어쩌라는건지 모르겠어서 다른 사람 풀이를 보고 풀었다.
인접한 두 집은 털지 못하므로, 무조건 한 집 이상씩 건너 뛰어서 털어야 한다. 1번집과 마지막집은 인접해있으므로, 1번집을 털었으면 마지막집은 털지 못한다. 1번집을 털지 않았으면 마지막집까지 털어도 된다.
따라서 두 가지 경우로 나뉜다.
1번집 털기
+ 2번집부터 마지막 바로 전 집까지 털기
1번집 안털기
+ 2번집부터 마지막 집까지 털기
n번집을 털때, 최대로 터는 돈의 액수를 dp[n]
이라고 하면
case 1. n-1번집을 털었으면 n번집은 못터니까 dp[n-1]
case 2. n-1번집을 안털었으면 n번집까지 털 수 있으니까 dp[n-2] + dp[n]
이 중에 더 큰값이 된다.
식으로 나타내면 다음과 같다.
dp[n] = max(dp[n-1], dp[n-2]+dp[n])
이런 흐름으로 1, 2번 경우를 모두 구하면, 1번 경우에는 마지막에서 2번째 집 차례에 최대로 턴 금액인 dp[-2]
를, 2번 경우에는 마지막집 차례에 최대로 턴 금액인 dp[-1]
를 가져와서 비교했을 때 더 큰 값이 답이 된다.