dynamic programming

Work in Progress

Summary

DP fundementals

  • state - where you are currently
  • means - what we have solved upto this point
  • need - what is needed to solve the current state
  • decision - decision to make at the current state
  • combination - how to compute the current state from previous states

DP patterns

PatternStateMeansNeed (sub-problem)DecisionCombination
Linearindex idp[i]previous entriesbest predecessordp[i] = op(dp[i-1], ...)
Gridcell at (r,c)dp[r][c]previous sub-griddirection to arrive fromdp[r][c] = op(dp[r-1][c], dp[r][c-1]
Knapsackindex i and remaining capacitydp[i][rem]dp[i-1][rem], dp[i-1][rem-weight[i]]include or exclude idp[i][rem] = op(dp[i-1][rem],dp[i-1][
Alignmentindices i and j in two sequencesdp[i][j]two sequences and sub-sequence excluding imatch
Intervalsubrange bounded by (l,r)dp[l][right]sub-intervals within the current interval

DP_patterns.png

otherwise tery tree DP or bitmask DP

Concept

Dynamic programming(DP)

  • generic problem solving method
  • properties of DP problems:
    1. overlapping subproblems
    2. optimal substructure
  • avoid repeated calculations, solve sub-problems first
  • reduce exponential runtime recursive functions using memoization

Recursion tree

  • way of modeling recursive functions
  • recursive calls that depend on other recursive calls/the base case(s)
  • fib(5),
cpp
int fib(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return f(n - 1) + f(n - 2);
}
543210121032101

Recursion DAG

  • recursive function + memoization
  • fib(5) with memoization,
cpp
const int MAXN = 100;
bool found[MAXN];
int memo[MAXN];

int fib(int n) {
    if (found[n]) return memo[n];
    if (n == 0) return 0;
    if (n == 1) return 1;

    found[n] = true;
    return memo[n] = f(n - 1) + f(n - 2);
}
012345

Bottom-up DP

  • start with the basecase
  • uses tabulation instead of memoization
  • possibly reduce space complexity, but values need to be relaculated every function call
cpp
const int MAX_SAVE = 3; // only the previous, O(1) space
int fib[MAX_SAVE];

int f(int n) {
    fib[0] = 0;
    fib[1] = 1;
    for (int i = 2; i <= n; i++)
        fib[i % MAX_SAVE] = fib[(i - 1) % MAX_SAVE] + fib[(i - 2) % MAX_SAVE];

    return fib[n % MAX_SAVE];
}

Overlapping subproblems

  • when solutions to the same subproblems are needed repeatedly
  • store computed solutions to avoid re-computation
  • DP cannot help if the subproblems are not overlapping, use divide-and-conquer instead

Optimal substructure

  • the most optimal solution of the problem can be built using the opmital solutions of smaller subproblems
  • solve small parts optimally, then combine them to get the final optimal answer
  • eg. bellman-ford for SSSP

Application

Leetcode: Coin Change

  • knapsack
cpp
int dp[amount + 1];
dp[0] = 0;

sort(begin(coins), end(coins)); // sort first

for (int i = 1; i <= amount; i++) {
	dp[i] = INT_MAX;

	for (int c: coins) {
		if (c > i) break; // terminate if coin is larger than the current amount, possible since we sort first

		if (dp[i - c] != INT_MAX) dp[i] = min(dp[i], 1 + dp[i - c]); // check if the sub-problem has a solution and use it
		// this will result in the minimum coins to reach this current amount
	}
}
return dp[amount] == INT_MAX ? -1 : dp[amount];

Codeforces: Vacations

  • linear
  • tabulate the max number of active days if either resting, contest or sport
RCG1320000011112222222
cpp
vector<array<int, 3>> dp(n + 1, {0, 0, 0}); // number of active days after i days, if
                                            // on the ith day, rest, contest or sport
for (i = 1; i <= n; i++) {
	const auto &prev = dp[i - 1];
	// 1st col is max of previous row
	dp[i][0] = max(prev[0], max(prev[1], prev[2]));
	// 2nd col is max of previous row, or +1 if contest can be done and not consequtive
	dp[i][1] = a[i - 1] & 1 ? max(prev[0] + 1, prev[2] + 1) : dp[i][0];
	// 3rd col is max of previous row, or +1 if sport can be done and not consequtive
	dp[i][2] = a[i - 1] & 2 ? max(prev[0] + 1, prev[1] + 1) : dp[i][0];
}

cout << n - max(dp[n][0], max(dp[n][1], dp[n][2])) << "\n";

return 0;

Leetcode: Longest Common Subsequence

  • alignment
abcdeace000000000111111122122123
cpp
int n = text1.size(), m = text2.size();
vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

for (int i = 1; i <= n; i++) {
	for (int j = 1; j <= m; j++) {
		if (text1[i - 1] == text2[j - 1]) {
			// if same char increment from the diagonal
			dp[i][j] = dp[i - 1][j - 1] + 1;
		} else {
			// else choose the longest subsequence from 
			dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
		}
	}
}

return dp[n][m];
  • space saving - only two columns

Sources