1"""
2๋์ ํ๋ก๊ทธ๋๋ฐ (Dynamic Programming) ๊ธฐ์ด
3Basic Dynamic Programming
4
5๋ณต์กํ ๋ฌธ์ ๋ฅผ ์์ ํ์ ๋ฌธ์ ๋ก ๋๋์ด ํด๊ฒฐํ๋ ์๊ณ ๋ฆฌ์ฆ ๊ธฐ๋ฒ์
๋๋ค.
6"""
7
8from typing import List
9from functools import lru_cache
10
11
12# =============================================================================
13# 1. ํผ๋ณด๋์น ์์ด (์ธ ๊ฐ์ง ๋ฐฉ๋ฒ)
14# =============================================================================
15
16# ๋ฐฉ๋ฒ 1: ์ฌ๊ท (๋นํจ์จ์ - O(2^n))
17def fibonacci_recursive(n: int) -> int:
18 """์ฌ๊ท: ์ง์ ์๊ฐ ๋ณต์ก๋"""
19 if n <= 1:
20 return n
21 return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
22
23
24# ๋ฐฉ๋ฒ 2: ๋ฉ๋ชจ์ด์ ์ด์
(Top-down DP)
25def fibonacci_memo(n: int, memo: dict = None) -> int:
26 """๋ฉ๋ชจ์ด์ ์ด์
: O(n)"""
27 if memo is None:
28 memo = {}
29 if n in memo:
30 return memo[n]
31 if n <= 1:
32 return n
33 memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
34 return memo[n]
35
36
37# ๋ฐฉ๋ฒ 2b: lru_cache ๋ฐ์ฝ๋ ์ดํฐ ์ฌ์ฉ
38@lru_cache(maxsize=None)
39def fibonacci_cached(n: int) -> int:
40 """lru_cache ์ฌ์ฉ: O(n)"""
41 if n <= 1:
42 return n
43 return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
44
45
46# ๋ฐฉ๋ฒ 3: ํ
๋ทธ๋ ์ด์
(Bottom-up DP)
47def fibonacci_tabulation(n: int) -> int:
48 """ํ
๋ทธ๋ ์ด์
: O(n) ์๊ฐ, O(n) ๊ณต๊ฐ"""
49 if n <= 1:
50 return n
51 dp = [0] * (n + 1)
52 dp[1] = 1
53 for i in range(2, n + 1):
54 dp[i] = dp[i - 1] + dp[i - 2]
55 return dp[n]
56
57
58# ๋ฐฉ๋ฒ 4: ๊ณต๊ฐ ์ต์ ํ
59def fibonacci_optimized(n: int) -> int:
60 """๊ณต๊ฐ ์ต์ ํ: O(n) ์๊ฐ, O(1) ๊ณต๊ฐ"""
61 if n <= 1:
62 return n
63 prev2, prev1 = 0, 1
64 for _ in range(2, n + 1):
65 curr = prev1 + prev2
66 prev2, prev1 = prev1, curr
67 return prev1
68
69
70# =============================================================================
71# 2. ๊ณ๋จ ์ค๋ฅด๊ธฐ (Climbing Stairs)
72# =============================================================================
73def climb_stairs(n: int) -> int:
74 """
75 ํ ๋ฒ์ 1์นธ ๋๋ 2์นธ ์ค๋ฅผ ์ ์์ ๋
76 n๊ฐ์ ๊ณ๋จ์ ์ค๋ฅด๋ ๊ฒฝ์ฐ์ ์
77 """
78 if n <= 2:
79 return n
80 prev2, prev1 = 1, 2
81 for _ in range(3, n + 1):
82 curr = prev1 + prev2
83 prev2, prev1 = prev1, curr
84 return prev1
85
86
87# =============================================================================
88# 3. ๋์ ๊ตํ (Coin Change)
89# =============================================================================
90def coin_change(coins: List[int], amount: int) -> int:
91 """
92 ์ฃผ์ด์ง ๋์ ์ผ๋ก ๊ธ์ก์ ๋ง๋๋ ์ต์ ๋์ ๊ฐ์
93 ๋ถ๊ฐ๋ฅํ๋ฉด -1 ๋ฐํ
94 """
95 dp = [float('inf')] * (amount + 1)
96 dp[0] = 0
97
98 for i in range(1, amount + 1):
99 for coin in coins:
100 if coin <= i and dp[i - coin] != float('inf'):
101 dp[i] = min(dp[i], dp[i - coin] + 1)
102
103 return dp[amount] if dp[amount] != float('inf') else -1
104
105
106def coin_change_ways(coins: List[int], amount: int) -> int:
107 """
108 ์ฃผ์ด์ง ๋์ ์ผ๋ก ๊ธ์ก์ ๋ง๋๋ ๊ฒฝ์ฐ์ ์
109 """
110 dp = [0] * (amount + 1)
111 dp[0] = 1
112
113 for coin in coins:
114 for i in range(coin, amount + 1):
115 dp[i] += dp[i - coin]
116
117 return dp[amount]
118
119
120# =============================================================================
121# 4. 0/1 ๋ฐฐ๋ญ ๋ฌธ์ (Knapsack)
122# =============================================================================
123def knapsack_01(weights: List[int], values: List[int], capacity: int) -> int:
124 """
125 0/1 ๋ฐฐ๋ญ ๋ฌธ์ : ๊ฐ ์์ดํ
์ ๋ฃ๊ฑฐ๋ ์ ๋ฃ๊ฑฐ๋
126 ์ต๋ ๊ฐ์น ๋ฐํ
127 """
128 n = len(weights)
129 dp = [[0] * (capacity + 1) for _ in range(n + 1)]
130
131 for i in range(1, n + 1):
132 for w in range(capacity + 1):
133 # ํ์ฌ ์์ดํ
์ ๋ฃ์ง ์๋ ๊ฒฝ์ฐ
134 dp[i][w] = dp[i - 1][w]
135 # ํ์ฌ ์์ดํ
์ ๋ฃ์ ์ ์์ผ๋ฉด
136 if weights[i - 1] <= w:
137 dp[i][w] = max(dp[i][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])
138
139 return dp[n][capacity]
140
141
142def knapsack_01_optimized(weights: List[int], values: List[int], capacity: int) -> int:
143 """0/1 ๋ฐฐ๋ญ (๊ณต๊ฐ ์ต์ ํ - 1D ๋ฐฐ์ด)"""
144 dp = [0] * (capacity + 1)
145
146 for i in range(len(weights)):
147 # ์ญ์์ผ๋ก ์ํ (๊ฐ์ ์์ดํ
์ค๋ณต ์ฌ์ฉ ๋ฐฉ์ง)
148 for w in range(capacity, weights[i] - 1, -1):
149 dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
150
151 return dp[capacity]
152
153
154# =============================================================================
155# 5. ์ต์ฅ ๊ณตํต ๋ถ๋ถ ์์ด (LCS)
156# =============================================================================
157def longest_common_subsequence(text1: str, text2: str) -> int:
158 """
159 ๋ ๋ฌธ์์ด์ ์ต์ฅ ๊ณตํต ๋ถ๋ถ ์์ด ๊ธธ์ด
160 """
161 m, n = len(text1), len(text2)
162 dp = [[0] * (n + 1) for _ in range(m + 1)]
163
164 for i in range(1, m + 1):
165 for j in range(1, n + 1):
166 if text1[i - 1] == text2[j - 1]:
167 dp[i][j] = dp[i - 1][j - 1] + 1
168 else:
169 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
170
171 return dp[m][n]
172
173
174def get_lcs_string(text1: str, text2: str) -> str:
175 """LCS ๋ฌธ์์ด ๋ณต์"""
176 m, n = len(text1), len(text2)
177 dp = [[0] * (n + 1) for _ in range(m + 1)]
178
179 for i in range(1, m + 1):
180 for j in range(1, n + 1):
181 if text1[i - 1] == text2[j - 1]:
182 dp[i][j] = dp[i - 1][j - 1] + 1
183 else:
184 dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
185
186 # ์ญ์ถ์
187 lcs = []
188 i, j = m, n
189 while i > 0 and j > 0:
190 if text1[i - 1] == text2[j - 1]:
191 lcs.append(text1[i - 1])
192 i -= 1
193 j -= 1
194 elif dp[i - 1][j] > dp[i][j - 1]:
195 i -= 1
196 else:
197 j -= 1
198
199 return ''.join(reversed(lcs))
200
201
202# =============================================================================
203# 6. ์ต์ฅ ์ฆ๊ฐ ๋ถ๋ถ ์์ด (LIS)
204# =============================================================================
205def longest_increasing_subsequence(nums: List[int]) -> int:
206 """
207 ์ต์ฅ ์ฆ๊ฐ ๋ถ๋ถ ์์ด ๊ธธ์ด (O(nยฒ))
208 """
209 if not nums:
210 return 0
211
212 n = len(nums)
213 dp = [1] * n # dp[i] = nums[i]๋ก ๋๋๋ LIS ๊ธธ์ด
214
215 for i in range(1, n):
216 for j in range(i):
217 if nums[j] < nums[i]:
218 dp[i] = max(dp[i], dp[j] + 1)
219
220 return max(dp)
221
222
223def lis_binary_search(nums: List[int]) -> int:
224 """
225 ์ต์ฅ ์ฆ๊ฐ ๋ถ๋ถ ์์ด ๊ธธ์ด (O(n log n))
226 ์ด๋ถ ํ์ ํ์ฉ
227 """
228 from bisect import bisect_left
229
230 if not nums:
231 return 0
232
233 tails = [] # tails[i] = ๊ธธ์ด i+1์ธ LIS์ ๋ง์ง๋ง ์์ ์ต์๊ฐ
234
235 for num in nums:
236 pos = bisect_left(tails, num)
237 if pos == len(tails):
238 tails.append(num)
239 else:
240 tails[pos] = num
241
242 return len(tails)
243
244
245# =============================================================================
246# 7. ์ต๋ ๋ถ๋ถ ๋ฐฐ์ด ํฉ (Kadane's Algorithm)
247# =============================================================================
248def max_subarray_sum(nums: List[int]) -> int:
249 """
250 ์ฐ์ ๋ถ๋ถ ๋ฐฐ์ด์ ์ต๋ ํฉ (์นด๋ฐ์ธ ์๊ณ ๋ฆฌ์ฆ)
251 O(n) ์๊ฐ, O(1) ๊ณต๊ฐ
252 """
253 if not nums:
254 return 0
255
256 max_sum = curr_sum = nums[0]
257
258 for num in nums[1:]:
259 curr_sum = max(num, curr_sum + num)
260 max_sum = max(max_sum, curr_sum)
261
262 return max_sum
263
264
265# =============================================================================
266# 8. House Robber
267# =============================================================================
268def rob(nums: List[int]) -> int:
269 """
270 ์ธ์ ํ ์ง์ ํธ ์ ์์ ๋ ์ต๋ ๊ธ์ก
271 """
272 if not nums:
273 return 0
274 if len(nums) <= 2:
275 return max(nums)
276
277 prev2, prev1 = nums[0], max(nums[0], nums[1])
278
279 for i in range(2, len(nums)):
280 curr = max(prev1, prev2 + nums[i])
281 prev2, prev1 = prev1, curr
282
283 return prev1
284
285
286# =============================================================================
287# ํ
์คํธ
288# =============================================================================
289def main():
290 print("=" * 60)
291 print("๋์ ํ๋ก๊ทธ๋๋ฐ (DP) ๊ธฐ์ด ์์ ")
292 print("=" * 60)
293
294 # 1. ํผ๋ณด๋์น
295 print("\n[1] ํผ๋ณด๋์น ์์ด")
296 n = 10
297 print(f" fib({n}) = {fibonacci_tabulation(n)}")
298 print(f" ์ฒ์ 10๊ฐ: {[fibonacci_optimized(i) for i in range(10)]}")
299
300 # 2. ๊ณ๋จ ์ค๋ฅด๊ธฐ
301 print("\n[2] ๊ณ๋จ ์ค๋ฅด๊ธฐ")
302 for n in [2, 3, 5]:
303 ways = climb_stairs(n)
304 print(f" {n}๊ฐ ๊ณ๋จ: {ways}๊ฐ์ง")
305
306 # 3. ๋์ ๊ตํ
307 print("\n[3] ๋์ ๊ตํ")
308 coins = [1, 2, 5]
309 amount = 11
310 min_coins = coin_change(coins, amount)
311 ways = coin_change_ways(coins, amount)
312 print(f" ๋์ : {coins}, ๊ธ์ก: {amount}")
313 print(f" ์ต์ ๋์ ์: {min_coins}")
314 print(f" ๊ฒฝ์ฐ์ ์: {ways}")
315
316 # 4. 0/1 ๋ฐฐ๋ญ
317 print("\n[4] 0/1 ๋ฐฐ๋ญ ๋ฌธ์ ")
318 weights = [2, 3, 4, 5]
319 values = [3, 4, 5, 6]
320 capacity = 5
321 max_value = knapsack_01(weights, values, capacity)
322 print(f" ๋ฌด๊ฒ: {weights}, ๊ฐ์น: {values}")
323 print(f" ์ฉ๋: {capacity}, ์ต๋ ๊ฐ์น: {max_value}")
324
325 # 5. LCS
326 print("\n[5] ์ต์ฅ ๊ณตํต ๋ถ๋ถ ์์ด (LCS)")
327 text1, text2 = "ABCDGH", "AEDFHR"
328 length = longest_common_subsequence(text1, text2)
329 lcs_str = get_lcs_string(text1, text2)
330 print(f" ๋ฌธ์์ด1: {text1}")
331 print(f" ๋ฌธ์์ด2: {text2}")
332 print(f" LCS ๊ธธ์ด: {length}, LCS: '{lcs_str}'")
333
334 # 6. LIS
335 print("\n[6] ์ต์ฅ ์ฆ๊ฐ ๋ถ๋ถ ์์ด (LIS)")
336 nums = [10, 9, 2, 5, 3, 7, 101, 18]
337 length = longest_increasing_subsequence(nums)
338 length_fast = lis_binary_search(nums)
339 print(f" ๋ฐฐ์ด: {nums}")
340 print(f" LIS ๊ธธ์ด: {length} (O(nยฒ)), {length_fast} (O(n log n))")
341
342 # 7. ์ต๋ ๋ถ๋ถ ๋ฐฐ์ด ํฉ
343 print("\n[7] ์ต๋ ๋ถ๋ถ ๋ฐฐ์ด ํฉ (Kadane)")
344 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
345 max_sum = max_subarray_sum(nums)
346 print(f" ๋ฐฐ์ด: {nums}")
347 print(f" ์ต๋ ํฉ: {max_sum}")
348
349 # 8. House Robber
350 print("\n[8] House Robber")
351 houses = [2, 7, 9, 3, 1]
352 max_money = rob(houses)
353 print(f" ์ง๋ณ ๊ธ์ก: {houses}")
354 print(f" ์ต๋ ๊ธ์ก: {max_money}")
355
356 print("\n" + "=" * 60)
357 print("DP ์ ๊ทผ ๋ฐฉ์ ๋น๊ต")
358 print("=" * 60)
359 print("""
360 | ๋ฐฉ์ | ๋ฐฉํฅ | ๊ตฌํ | ์ฅ์ |
361 |---------------|----------|---------------|------------------------|
362 | ๋ฉ๋ชจ์ด์ ์ด์
| Top-down | ์ฌ๊ท + ์บ์ | ํ์ํ ๋ถ๋ถ๋ง ๊ณ์ฐ |
363 | ํ
๋ทธ๋ ์ด์
| Bottom-up| ๋ฐ๋ณต๋ฌธ + ๋ฐฐ์ด | ์คํ ์ค๋ฒํ๋ก์ฐ ์์ |
364
365 DP ๋ฌธ์ ํด๊ฒฐ ๋จ๊ณ:
366 1. ์ํ ์ ์: dp[i]๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง
367 2. ์ ํ์ ๋์ถ: dp[i]๋ฅผ ์ด์ ์ํ๋ก ํํ
368 3. ์ด๊ธฐ๊ฐ ์ค์ : base case
369 4. ๊ณ์ฐ ์์ ๊ฒฐ์ : ์์กด์ฑ์ ๋ฐ๋ผ
370 5. ์ ๋ต ๋์ถ: dp ํ
์ด๋ธ์์ ๋ต ์ถ์ถ
371 """)
372
373
374if __name__ == "__main__":
375 main()