44
55Note that only the integer weights 0-1 knapsack problem is solvable
66using dynamic programming.
7+
8+ This module provides multiple approaches:
9+ - ``knapsack``: Bottom-up DP with O(n*W) space and solution reconstruction.
10+ - ``knapsack_with_example_solution``: Wrapper that returns optimal value and subset.
11+ - ``knapsack_optimized``: Space-optimized bottom-up DP using O(W) space (value only).
12+ - ``mf_knapsack``: Top-down memoized approach (memory function) with no global state.
713"""
814
915
10- def mf_knapsack (i , wt , val , j ):
16+ def mf_knapsack (
17+ i : int ,
18+ wt : list [int ],
19+ val : list [int ],
20+ j : int ,
21+ memo : list [list [int ]] | None = None ,
22+ ) -> int :
1123 """
12- This code involves the concept of memory functions. Here we solve the subproblems
13- which are needed unlike the below example
14- F is a 2D array with ``-1`` s filled up
24+ Solve the 0-1 knapsack problem using top-down memoization (memory function).
25+
26+ Unlike the previous implementation, this version does **not** rely on a global
27+ ``f`` table. The memoization table is passed explicitly or created on first call.
28+
29+ :param i: Number of items to consider (1-indexed).
30+ :param wt: List of item weights.
31+ :param val: List of item values.
32+ :param j: Remaining knapsack capacity.
33+ :param memo: Optional pre-allocated memoization table of shape ``(i+1) x (j+1)``
34+ initialised with ``-1`` for unsolved sub-problems and ``0`` for base cases.
35+ When ``None`` a table is created automatically.
36+ :return: Maximum obtainable value considering items ``1..i`` with capacity ``j``.
37+
38+ Examples:
39+ >>> mf_knapsack(4, [4, 3, 2, 3], [3, 2, 4, 4], 6)
40+ 8
41+ >>> mf_knapsack(0, [1, 2], [10, 20], 5)
42+ 0
43+ >>> mf_knapsack(3, [1, 3, 5], [10, 20, 100], 10)
44+ 130
45+ >>> mf_knapsack(1, [5], [50], 3)
46+ 0
47+ >>> mf_knapsack(1, [5], [50], 5)
48+ 50
1549 """
16- global f # a global dp table for knapsack
17- if f [i ][j ] < 0 :
18- if j < wt [i - 1 ]:
19- val = mf_knapsack (i - 1 , wt , val , j )
20- else :
21- val = max (
22- mf_knapsack (i - 1 , wt , val , j ),
23- mf_knapsack (i - 1 , wt , val , j - wt [i - 1 ]) + val [i - 1 ],
24- )
25- f [i ][j ] = val
26- return f [i ][j ]
50+ if memo is None :
51+ memo = [[0 ] * (j + 1 )] + [[0 ] + [- 1 ] * j for _ in range (i )]
52+
53+ if i == 0 or j == 0 :
54+ return 0
2755
56+ if memo [i ][j ] >= 0 :
57+ return memo [i ][j ]
2858
29- def knapsack (w , wt , val , n ):
59+ if j < wt [i - 1 ]:
60+ memo [i ][j ] = mf_knapsack (i - 1 , wt , val , j , memo )
61+ else :
62+ memo [i ][j ] = max (
63+ mf_knapsack (i - 1 , wt , val , j , memo ),
64+ mf_knapsack (i - 1 , wt , val , j - wt [i - 1 ], memo ) + val [i - 1 ],
65+ )
66+ return memo [i ][j ]
67+
68+
69+ def knapsack (
70+ w : int , wt : list [int ], val : list [int ], n : int
71+ ) -> tuple [int , list [list [int ]]]:
72+ """
73+ Solve the 0-1 knapsack problem using bottom-up dynamic programming.
74+
75+ :param w: Maximum knapsack capacity.
76+ :param wt: List of item weights.
77+ :param val: List of item values.
78+ :param n: Number of items.
79+ :return: A tuple ``(optimal_value, dp_table)`` where ``dp_table`` can be used
80+ for solution reconstruction via ``_construct_solution``.
81+
82+ Examples:
83+ >>> knapsack(6, [4, 3, 2, 3], [3, 2, 4, 4], 4)[0]
84+ 8
85+ >>> knapsack(10, [1, 3, 5, 2], [10, 20, 100, 22], 4)[0]
86+ 142
87+ >>> knapsack(0, [1, 2], [10, 20], 2)[0]
88+ 0
89+ >>> knapsack(5, [], [], 0)[0]
90+ 0
91+ """
3092 dp = [[0 ] * (w + 1 ) for _ in range (n + 1 )]
3193
3294 for i in range (1 , n + 1 ):
@@ -36,10 +98,51 @@ def knapsack(w, wt, val, n):
3698 else :
3799 dp [i ][w_ ] = dp [i - 1 ][w_ ]
38100
39- return dp [n ][w_ ], dp
101+ return dp [n ][w ], dp
40102
41103
42- def knapsack_with_example_solution (w : int , wt : list , val : list ):
104+ def knapsack_optimized (w : int , wt : list [int ], val : list [int ], n : int ) -> int :
105+ """
106+ Solve the 0-1 knapsack problem using space-optimized bottom-up DP.
107+
108+ Uses a single 1-D array of size ``w + 1`` instead of a 2-D ``(n+1) x (w+1)``
109+ table, reducing space complexity from O(n*W) to O(W).
110+
111+ .. note::
112+ This variant returns only the optimal value; it does **not** support
113+ solution reconstruction (i.e. which items are included).
114+
115+ :param w: Maximum knapsack capacity.
116+ :param wt: List of item weights.
117+ :param val: List of item values.
118+ :param n: Number of items.
119+ :return: Maximum obtainable value.
120+
121+ Examples:
122+ >>> knapsack_optimized(6, [4, 3, 2, 3], [3, 2, 4, 4], 4)
123+ 8
124+ >>> knapsack_optimized(10, [1, 3, 5, 2], [10, 20, 100, 22], 4)
125+ 142
126+ >>> knapsack_optimized(0, [1, 2], [10, 20], 2)
127+ 0
128+ >>> knapsack_optimized(5, [], [], 0)
129+ 0
130+ >>> knapsack_optimized(50, [10, 20, 30], [60, 100, 120], 3)
131+ 220
132+ """
133+ dp = [0 ] * (w + 1 )
134+
135+ for i in range (n ):
136+ # Traverse capacity in reverse so each item is used at most once
137+ for capacity in range (w , wt [i ] - 1 , - 1 ):
138+ dp [capacity ] = max (dp [capacity ], dp [capacity - wt [i ]] + val [i ])
139+
140+ return dp [w ]
141+
142+
143+ def knapsack_with_example_solution (
144+ w : int , wt : list [int ], val : list [int ]
145+ ) -> tuple [int , set [int ]]:
43146 """
44147 Solves the integer weights knapsack problem returns one of
45148 the several possible optimal subsets.
@@ -94,13 +197,15 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
94197 raise TypeError (msg )
95198
96199 optimal_val , dp_table = knapsack (w , wt , val , num_items )
97- example_optional_set : set = set ()
200+ example_optional_set : set [ int ] = set ()
98201 _construct_solution (dp_table , wt , num_items , w , example_optional_set )
99202
100203 return optimal_val , example_optional_set
101204
102205
103- def _construct_solution (dp : list , wt : list , i : int , j : int , optimal_set : set ):
206+ def _construct_solution (
207+ dp : list [list [int ]], wt : list [int ], i : int , j : int , optimal_set : set [int ]
208+ ) -> None :
104209 """
105210 Recursively reconstructs one of the optimal subsets given
106211 a filled DP table and the vector of weights
@@ -135,14 +240,20 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
135240 """
136241 Adding test case for knapsack
137242 """
243+ import doctest
244+
245+ doctest .testmod ()
246+
138247 val = [3 , 2 , 4 , 4 ]
139248 wt = [4 , 3 , 2 , 3 ]
140249 n = 4
141250 w = 6
142- f = [[0 ] * (w + 1 )] + [[0 ] + [- 1 ] * (w + 1 ) for _ in range (n + 1 )]
143251 optimal_solution , _ = knapsack (w , wt , val , n )
144252 print (optimal_solution )
145- print (mf_knapsack (n , wt , val , w )) # switched the n and w
253+ print (mf_knapsack (n , wt , val , w ))
254+
255+ # Space-optimized knapsack
256+ print (f"Optimized: { knapsack_optimized (w , wt , val , n )} " )
146257
147258 # testing the dynamic programming problem with example
148259 # the optimal subset for the above example are items 3 and 4
0 commit comments