18_dp.py

Download
python 376 lines 11.0 KB
  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()