1, Linear state dp
Linear state dp is also a kind of linear dp, but it is a little more complex. Their essential difference is that linear dp only needs to consider the recurrence of one state, and state dp needs to consider the dynamic recurrence between multiple variables.
But also because the state is more, in fact, the difficulty is reduced
In the example, we will talk about the invincible template of state dp
1. Status dp examples and problem solving templates
1) Example 1: LeetCode 198 house raiding
Different from linear dp, there are multiple dimensions in state dp, that is, each house may or may not be stolen. Both cases should be considered. In other words, regardless of the previous recursive result, the latter two cases should be considered
Invincible template in status dp: add dimension
Change DP array from one dimension to two dimensions dp[i][j]
The second dimension of j can only take 0 or 1 to indicate whether the current house has been stolen
dp[i][0] means that the ith house was not stolen, dp[i][1] means that the ith house was stolen
So we have to maintain two variables of the same house every time, and there are also two corresponding dynamic transfer equations
- If you choose not to steal the current house i, then the house i - 1 may or may not be stolen
- dp[i][0] = max(dp[i - 1][1], dp[i - 1][0])
- If you choose to steal the current house i, then the house i - 1 must not be stolen
- dp[i][1] = dp[i - 1][0] + price[i]
Next, just pay attention to initialization
- dp[0][0] that is, don't steal the first house dp[0][0] = 0
- dp[0][1] that is, stealing the first house dp[0][1] = price[0]
public int rob(int[] nums) { int[][] dp = new int[nums.length][2]; dp[0][1] = nums[0]; dp[0][0] = 0; for (int i = 1; i < nums.length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = dp[i - 1][0] + nums[i]; } return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]); }
Of course, it can be optimized. The state of each house can be represented by two int s
public int rob(int[] nums) { int rob = nums[0]; // steal int pass = 0; // Don't steal for (int i = 1; i < nums.length; i++) { int pre_pass = pass; // The last pass is used to calculate rob pass = Math.max(pass, rob); rob = pre_pass + nums[i]; } return Math.max(pass, rob); }
2) Example 2: LeetCode 188 the best time to buy and sell stocks Ⅳ
At first glance, it seems difficult. In fact, it is also an idea of increasing dimensions
Invincible template in status dp: add dimension
Turn dp array into 3D dp[i][j][k]
In the second dimension, j can only take 0 or 1, indicating whether he holds stocks or not
dp[i][0][k] means no stocks on hand on day I, and dp[i][1][k] means stocks on hand on day I
K in the third dimension can take 0 ~ k, dp[i][j][k] indicates that K transactions have been completed on day I
Then there is the dynamic transfer equation
- If there is no stock in hand at the end of day i
- Either they didn't hold it at the end of the previous day, or they sold shares and completed a transaction today
- dp[i][0][k] = max(dp[i - 1][0][k], dp[i - 1][1][k - 1] + price[i])
- If you hold shares at the end of day i
- You either held shares at the end of the previous day or bought shares today
- dp[i][1][k] = max(dp[i - 1][1][k], dp[i - 1][0][k] - price[i])
Finally, initialization
- dp[0][0][k] = 0, dp[0][1][k] = -price[0], where k is 0 ~ k
for (int i = 0; i <= k; i++) { dp[0][1][i] = -prices[0]; dp[0][0][i] = 0; }
Why do you need to assign values to all dp[0][0] and dp[0][1] in the third dimension?
We know up to k transactions
However, it is not necessarily the best answer to complete all k transactions. It is possible that the number of days is not enough to support so many transactions
Therefore, the third dimension is used to limit the number of transactions, up to k times and at least 0 times
The answer will be generated in all the limited number of transactions on the last day
int ans = 0; for (int i = 0; i <= k; i++) ans = Math.max(ans, dp[len - 1][0][i]); return ans;
Complete code
public int maxProfit(int k, int[] prices) { int len = prices.length; if (len == 0) return 0; int[][][] dp = new int[len][2][k + 1]; for (int i = 0; i <= k; i++) { dp[0][1][i] = -prices[0]; dp[0][0][i] = 0; } for (int i = 1; i < len; i++) { dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][0][0] - prices[i]); for (int j = 1; j <= k; j++) { dp[i][0][j] = Math.max(dp[i - 1][0][j], dp[i - 1][1][j - 1] + prices[i]); dp[i][1][j] = Math.max(dp[i - 1][1][j], dp[i - 1][0][j] - prices[i]); } } int ans = 0; for (int i = 0; i <= k; i++) ans = Math.max(ans, dp[len - 1][0][i]); return ans; }
Of course, it can be optimized
public int maxProfit(int k, int[] prices) { if (k == 0 || prices.length == 0) return 0; int[] buy = new int[k + 1]; int[] sell = new int[k + 1]; // initialization for (int i = 0; i <= k; i++) buy[i] = -prices[0]; sell[0] = 0; for (int i = 1; i < prices.length; i++) for (int j = 0; j <= k; j++) { // Not held, not held the day before or sold today to complete a transaction if (j > 0) sell[j] = Math.max(sell[j], buy[j - 1] + prices[i]); // Hold, hold the day before or buy today buy[j] = Math.max(buy[j], sell[j] - prices[i]); } int ans = 0; for (int i = 0; i <= k; i++) ans = Math.max(ans, sell[i]); return ans; }
2. Practical topic
LeetCode 123 the best time to buy and sell stocks Ⅲ
LeetCode 309 best time to buy and sell stocks, including freezing period
LeetCode 714 the best time to buy and sell stocks, including handling charges
Finally, think about it: not only limit k transactions, but also have a freezing period, as well as handling charges, and require optimization. How do you solve it?
2, Ring state dp
Example and practical questions: LeetCode 213 house raiding II
Because the house is a ring, giving up the first one and giving up the last one will have an impact on the recursion.
So the ring problem can be transformed into a quadratic linear dp
- First linear dp: the last one is not considered.
- Second linear dp: the first one is not considered.
The recurrence equation of this problem has been explained in the first problem of this article
- dp[i][0] = max(dp[i - 1][1], dp[i - 1][0])
- dp[i][1] = dp[i - 1][0] + price[i]
But the most important thing to note is that the minimum number of houses is 1, so in order not to lose generality, make a slight change in the recursion
- First linear dp: the last one is not considered. For the sake of generality, we push it to the last one, but only take dp[i][0]
- Second linear dp: the first one is not considered. For generality, we initialize dp[0][1] to infinity, so that even if we steal the first one, we will abandon this choice because of infinity
public int rob(int[] nums) { int len = nums.length; int[][] dp = new int[len][2]; // You can steal the first one and ignore the last one int first= dp[0][1] = nums[0]; for (int i = 1; i < len; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = dp[i - 1][0] + nums[i]; } first = Math.max(ans, dp[len - 1][0]); // You can steal the last one, not the first dp[0][0] = 0; dp[0][1] = -(int)1e9; for (int i = 1; i < len; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]); dp[i][1] = dp[i - 1][0] + nums[i]; } return Math.max(first, Math.max(dp[len - 1][0], dp[len - 1][1])); }
3, Tree state dp
Example and practical questions: LeetCode 337 house raiding III
The idea is exactly the same as that of linear, but the array cannot record the previous result at this time, so we use HashMap
- HashMap < treenode, int [] > DP stores two cases of a node, stealing and not stealing
- If cur[j] is used to represent the stolen status of the current node
- cur[0] indicates that the current house has not been stolen: it is composed of the stolen and not stolen situation of its left and right sons, and the corresponding pseudo code is as follows:
- cur[0] = max(leftNode.cur[0], leftNode.cur[1]) + max(rightNode.cur[0], rightNode.cur[1])
- cur[1] indicates that the current house has been stolen: then neither his left nor right sons have been stolen, and the corresponding pseudo code is as follows:
- cur[1] = leftNode.cur[0] + rightNode.cur[0] + curNode.val
HashMap<TreeNode, int[]> dp = new HashMap<>(); public int rob(TreeNode root) { int[] cur = new int[2]; if (root.left != null) { cur[0] += rob(root.left); // Steal left node cur[1] += dp.get(root.left)[0]; // Do not steal left node } if (root.right != null) { cur[0] += rob(root.right); // Steal right node cur[1] += dp.get(root.right)[0]; // Do not steal right nodes } cur[1] += root.val; dp.put(root, cur); return Math.max(cur[0], cur[1]); }