0️⃣ Read This First
You only need these once the core nine (see the DP guide) aren’t enough.
These patterns are rarer in interviews — most DP questions are still 1D / knapsack / strings / grid. The value here is recognition: spotting “oh, the state is a bitmask” or “this is a game, so it’s minimax” and knowing the template. The thinking is identical — state, choices, combine, base case — only the state gets fancier.
Ordered most → least likely to show up in an interview.
1️⃣ Bitmask DP 🎭
💡 Signal:
nis tiny (≤ ~20) and the natural state is “which subset have I used/visited”. Encode that subset as the bits of an integermask.
State: dp[mask] (sometimes dp[mask][last]). Each bit i = “element i is used”. Transitions flip one bit on.
Partition to K Equal Sum Subsets — LC 698 🔥 💀
Split nums into k subsets of equal sum. dp[mask] = the amount already filled in the current (unfinished) bucket after using exactly the elements in mask.
// LC 698. Partition to K Equal Sum Subsets
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % k) return false; // can't divide evenly
int target = sum / k, n = nums.size();
int full = (1 << n) - 1;
vector<int> dp(1 << n, -1); // dp[mask] = fill of current bucket; -1 = unreachable
dp[0] = 0;
for (int mask = 0; mask <= full; mask++) {
if (dp[mask] == -1) continue;
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) continue; // element i already used
if (dp[mask] + nums[i] > target) continue;
int next = mask | (1 << i);
if (dp[next] == -1)
dp[next] = (dp[mask] + nums[i]) % target; // %target auto-closes a full bucket
}
}
return dp[full] == 0; // all used AND last bucket closed
}
};
// TC: O(2^n * n) SC: O(2^n)The bitmask tells
“Assign everyone to something”, “visit all nodes” (TSP), “cover all cells”, with
n ≤ 20.2^20 ≈ 10^6states is fine;2^25is not. Ifnis large, it’s not bitmask DP.
2️⃣ Tree DP 🌳
💡 Signal: the structure is a tree, and a node’s answer is built from its children’s answers. This is DP on a recursion that’s already there (the tree itself).
State: solve(node) returns everything the parent needs — often a small tuple of “best if I include this node” vs “best if I don’t”.
House Robber III — LC 337 🔥 📍
Rob a binary tree; you can’t rob a node and its direct child. Return {robThis, skipThis} from each node.
// LC 337. House Robber III (TreeNode is provided by LeetCode)
class Solution {
public:
// returns {rob this node, skip this node}
pair<int,int> solve(TreeNode* node) {
if (!node) return {0, 0};
auto [lRob, lSkip] = solve(node->left);
auto [rRob, rSkip] = solve(node->right);
int robThis = node->val + lSkip + rSkip; // rob node => skip both children
int skipThis = max(lRob, lSkip) + max(rRob, rSkip); // skip node => children choose freely
return {robThis, skipThis};
}
int rob(TreeNode* root) {
auto [r, s] = solve(root);
return max(r, s);
}
};
// TC: O(n) SC: O(h) recursionTree DP = post-order + return a tuple
Compute children first (post-order), then combine. The “memo” is implicit — each node is visited once. Same idea powers Binary Tree Maximum Path Sum, Diameter of Binary Tree, and Binary Tree Cameras.
3️⃣ Game / Minimax DP ♟️
💡 Signal: two players alternate, both play optimally. Model the score as “best the current player can achieve”, and subtract the opponent’s optimal reply.
Key trick: define solve(state) = best score difference (me − opponent). The opponent’s turn is just -solve(nextState).
Predict the Winner — LC 486 📍
Players pick from either end of nums. Can player 1 tie or win?
// LC 486. Predict the Winner
class Solution {
public:
vector<vector<int>> dp;
int solve(vector<int>& nums, int i, int j) {
if (i == j) return nums[i]; // only one choice left
if (dp[i][j] != INT_MIN) return dp[i][j];
int pickLeft = nums[i] - solve(nums, i + 1, j); // opponent plays optimally next
int pickRight = nums[j] - solve(nums, i, j - 1);
return dp[i][j] = max(pickLeft, pickRight);
}
bool predictTheWinner(vector<int>& nums) {
int n = nums.size();
dp.assign(n, vector<int>(n, INT_MIN)); // INT_MIN sentinel: score diff can be negative
return solve(nums, 0, n - 1) >= 0; // >= 0 means player 1 doesn't lose
}
};
// TC: O(n^2) SC: O(n^2)The
value - solve(rest)pattern is the whole gameBecause whatever the opponent gains is your loss, “my best diff = my pick minus their best diff from what’s left”. Stone Game and Nim-style problems all reduce to this. (Note the
INT_MINsentinel — a score diff of-1is a real value, so-1can’t mean “not computed”.)
4️⃣ Probability / Expected-Value DP 🎲
💡 Signal: “probability that …” or “expected number of …”. Same DP, but the combine step is a weighted sum (each branch times its probability), and
dpholdsdoubles.
Knight Probability in Chessboard — LC 688 📍
Probability a knight is still on an n×n board after k random moves.
// LC 688. Knight Probability in Chessboard
class Solution {
public:
int N;
vector<vector<vector<double>>> dp;
double solve(int k, int r, int c) {
if (r < 0 || c < 0 || r >= N || c >= N) return 0.0; // stepped off the board
if (k == 0) return 1.0; // survived all k moves
if (dp[k][r][c] >= 0) return dp[k][r][c];
int dr[] = {-2,-2,-1,-1,1,1,2,2};
int dc[] = {-1,1,-2,2,-2,2,-1,1};
double prob = 0.0;
for (int d = 0; d < 8; d++)
prob += solve(k - 1, r + dr[d], c + dc[d]) / 8.0; // each move equally likely
return dp[k][r][c] = prob;
}
double knightProbability(int n, int k, int row, int column) {
N = n;
dp.assign(k + 1, vector<vector<double>>(n, vector<double>(n, -1.0)));
return solve(k, row, column);
}
};
// TC: O(k * n^2 * 8) SC: O(k * n^2)Combine = expectation
Counting DP sums integers; probability DP sums
p(branch) * value(branch). Everything else — state, memo, base case — is unchanged. Use a sentinel like-1.0since real probabilities are in[0, 1].
5️⃣ Digit DP 🔢
💡 Signal: “count numbers in
[L, R]with some digit property” where the range is huge (up to10^18). You build the number digit by digit, tracking whether you’re still hugging the upper bound.
State: solve(pos, tight, ...problemState). tight = are the digits so far exactly the prefix of N? Only when tight == false are the remaining positions unconstrained → only those states are memoizable.
// Digit DP template — count integers in [0, N] with a digit property
class Solution {
public:
string num;
vector<vector<int>> dp; // dp[pos][state], valid only when tight == false
int solve(int pos, int state, bool tight) {
if (pos == (int)num.size()) return 1; // a full number was formed (adapt per problem)
if (!tight && dp[pos][state] != -1) return dp[pos][state];
int limit = tight ? (num[pos] - '0') : 9; // can't exceed N's digit while tight
int total = 0;
for (int d = 0; d <= limit; d++) {
// if (!allowed(state, d)) continue; // e.g. no two equal adjacent digits
int newState = state; // update per problem
total += solve(pos + 1, newState, tight && (d == limit));
}
if (!tight) dp[pos][state] = total; // memoize ONLY the unbounded states
return total;
}
int count(int N) {
num = to_string(N);
dp.assign(num.size(), vector<int>(/*num states*/ 10, -1));
return solve(0, 0, true);
}
};
// TC: O(digits * states * 10) SC: O(digits * states)
// For [L, R]: answer = count(R) - count(L - 1).Why memoize only when
!tightWhen you’re still tight, the allowed digits depend on
N’s exact prefix, so that state isn’t reusable. Once you’ve gone below the bound, the rest is a clean “any digits” subproblem — that’s the part worth caching. Flagship problems: Numbers At Most N Given Digit Set (LC 902), Non-negative Integers without Consecutive Ones (LC 600).
6️⃣ Broken-Profile / Bitmask-on-Grid DP 🧱
💡 Signal: tile / fill a grid (dominoes,
1×2tiles) wherecolumns ≤ ~14. Process column by column, and the state is a bitmask of which cells in the boundary “profile” are already filled by tiles poking out of the previous column.
Think of it as bitmask DP where the mask describes the jagged frontier between the processed and unprocessed part of the grid. Transitions enumerate how the next column’s cells get covered given the incoming profile.
- State:
dp[col][mask]— ways/cost to fill everything left ofcolwithmaskmarking cells that stick intocol. - Flagship: counting domino tilings of an
m × nboard; Painting a Grid With Three Different Colors (LC 1931) uses the same column-profile idea with a color mask instead of a fill mask.
Niche — recognize, don't memorize
Broken-profile is rare in interviews and heavy to code. Know the shape (column sweep + frontier bitmask,
cols ≤ 14) so you can recognize it; reach for a reference implementation when you actually need it.
7️⃣ DP Optimizations ⚡
Sometimes the right DP is obvious but too slow. These turn an O(n²) or O(n·W) DP into something faster by speeding up the transition, not by changing the state.
| Technique | Use when the transition is… | Speed-up |
|---|---|---|
| Prefix / suffix sums | a sum over a contiguous range of previous states | O(range) → O(1) |
| Monotonic deque (sliding max) | dp[i] = best of dp[i-k..i-1] + cost over a window | O(k) → O(1) amortized |
| Binary lifting / matrix expo | a fixed linear recurrence iterated a huge number of times | O(n) → O(log n) |
| Knuth’s optimization | interval DP where the optimal split is monotonic | O(n³) → O(n²) |
| Divide & Conquer optimization | dp[i][j] with monotonic optimal split point | O(kn²) → O(kn log n) |
| Convex Hull Trick / Li Chao | transition is min/max of linear functions of the state | O(n²) → O(n log n) |
| Bitset | boolean subset-sum / reachability transitions | ~64× constant factor |
| SOS DP (sum over subsets) | aggregate over all submasks of each mask | O(3^n) → O(2^n · n) |
Optimize last
Get the correct DP first (state + recurrence), confirm it’s too slow, then reach for one of these. The classic interview-friendly ones are prefix sums and the monotonic deque (e.g. Jump Game VI, LC 1696); the rest are competitive-programming territory.
🔗 Back to the Core
- The DP Guide — the 4-rung ladder, the 5 universal patterns, and the nine core interview patterns with full C++.
- DP Problem Tracker — the curated problem set to drill each pattern.
The one idea that never changes
Bitmask, tree, game, probability, digit — the state gets creative, but you always answer the same four questions: state? choices? combine? base case? Then climb the ladder. Master that and “advanced” is just “a new state”.