You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: notes/dynamic_programming.md
+15-12Lines changed: 15 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -288,30 +288,33 @@ We build a two-dimensional table $L[0..m][0..n]$ using the above recurrence.
288
288
289
289
**Time Complexity**: $O(mn)$
290
290
291
-
### Practical Considerations in Dynamic Programming
291
+
### Practical Considerations
292
292
293
293
#### Identifying DP Problems
294
294
295
-
Not all problems are amenable to dynamic programming. To determine if DP is appropriate:
296
-
297
-
- Can the problem's optimal solution be constructed from optimal solutions to its subproblems?
298
-
- Are the same subproblems being solved multiple times?
295
+
* If the problem asks for the number of *ways* to do something, DP usually works because smaller counts combine into larger ones; without it, counting paths in a grid would require enumerating every route.
296
+
* If the task is to find the *minimum* or *maximum* value under constraints, DP is useful because it compares partial solutions; without it, knapsack would require checking every subset of items.
297
+
* If the same *inputs* appear again during recursion, DP saves time by storing answers; without it, Fibonacci numbers would be recomputed many times.
298
+
* If the solution depends on both the *current step* and *remaining resources* (time, weight, money, length), DP fits naturally; without it, scheduling tasks within a time limit would require brute force.
299
+
* If the problem works with *prefixes, substrings, or subsequences*, DP is often a match because these can be built step by step; without it, longest common subsequence would need exponential checking.
300
+
* If choices at each step must be explored and combined carefully, DP provides structure; without it, coin change with mixed denominations cannot guarantee the fewest coins.
301
+
* If the state space can be stored in a *table or array*, DP is feasible; without this, problems with infinitely many possibilities (like arbitrary real numbers) cannot be handled.
299
302
300
303
#### State Design and Transition
301
304
302
-
- Choose variables that capture the essence of subproblems.
303
-
- Clearly define how to move from one state to another.
305
+
* A well-chosen *state* defines what each subproblem represents, while a poorly chosen one leaves the formulation incomplete; for example, `dp[i][w]` in the knapsack problem captures value using `i` items and capacity `w`.
306
+
* A correct *transition* connects states consistently, while skipping this leads to undefined progress; in knapsack, the choice to include or exclude an item gives the formula for moving between states.
304
307
305
308
#### Complexity Optimization
306
309
307
-
- Reduce the storage requirements by identifying and storing only necessary states.
308
-
- Prune unnecessary computations, possibly using techniques like memoization with pruning.
310
+
* Reducing *memory usage*by discarding unnecessary states makes solutions efficient, while failing to do so can waste resources; for example, knapsack space can shrink from `O(nW)` to `O(W)` with a one-dimensional array.
311
+
* Using *pruning* to skip impossible paths speeds up computation, while omitting it allows redundant work; in recursive search with memoization, branches exceeding a current best value can be safely ignored.
309
312
310
313
#### Common Pitfalls
311
314
312
-
- Leads to missing subproblems or incorrect dependencies.
313
-
- Can cause incorrect results or infinite recursion.
314
-
- Failing to handle special inputs can result in errors.
315
+
* Missing *base cases* causes results to fail, while including them ensures correct foundations; in grid path counting, setting `dp[0][0] = 1` allows all later counts to build properly.
316
+
* Updating *dependencies* in the wrong order leads to invalid reuse, while correct order avoids errors; in knapsack with a 1D array, iterating weights backward prevents an item from being counted twice.
317
+
* Ignoring *edge inputs* results in crashes or incorrect answers, while handling them ensures robustness; for example, knapsack with zero capacity must return a value of zero instead of failing.
0 commit comments