LeetCode [learning plan]: [algorithm]
preface
Dynamic programming (DP) is a big head in the algorithm. It aims to turn the problem to be solved into several subproblems. First solve the subproblems, and then get the solution of the original problem from the solutions of these subproblems. Sub problems in dynamic programming are often related.
70. Climb stairs
LeetCode: 70. Climb stairs
simple single \color{#00AF9B} {simple} simple
Suppose you are climbing stairs. You need n steps to reach the roof.
You can climb one or two steps at a time. How many different ways can you climb to the roof?
Note: given n is a positive integer.
Example 1:
Input: 2 Output: 2 Explanation: there are two ways to climb to the roof. 1. 1 rank + 1 rank 2. 2 rank
Example 2:
Input: 3 Output: 3 Explanation: there are three ways to climb to the roof. 1. 1 rank + 1 rank + 1 rank 2. 1 rank + 2 rank 3. 2 rank + 1 rank
Method 1: dynamic programming + sliding array
We use f ( x ) f(x) f(x) represents the number of schemes when climbing to the x-th step. So we can get an array dp to store x ∈ [ 1 , n ] x \in [1, n] x ∈ [1,n] f ( x ) f(x) f(x), where n is the number of steps.
The title says that we can climb 1 or 2 steps at a time. This sentence is from the perspective of climbing back from the current step; Conversely, when we consider how to get to the current step, we can find that we have either taken two steps from the penultimate grid or one step from the previous grid. This sentence is translated into a mathematical formula as follows:
f ( x ) = f ( x − 2 ) + f ( x − 1 ) f(x)=f(x-2)+f(x-1) f(x)=f(x−2)+f(x−1)
The number of schemes reaching order x is equal to the number of schemes of order x-2 plus the number of schemes of order x-1.
At the same time, the above formula is also a recursive formula. Let's consider f(1) and f(2).
- f(1): there is only one scheme for climbing the first step.
- f(2): either step up to the first step and then step up again, or step up to the second step directly across two steps. There are two options.
Therefore, the recurrence formula of this problem is:
f ( x ) = { 1 x = 1 2 x = 2 f ( x − 2 ) + f ( x − 1 ) x > 2 f(x) = \begin{cases} {1} & {x=1} \\ {2} & {x=2} \\ {f(x-2)+f(x-1)} & {x>2} \end{cases} f(x)=⎩⎪⎨⎪⎧12f(x−2)+f(x−1)x=1x=2x>2
List the values of the first several items: 1,2,3,5,8,13
It can be found that the answer to this question is exactly Fibonacci sequence.
Finally, the answer to this question is the last item in the answer array dp. We can also do some optimization. We can see from the recursive formula that the value of an item is only related to its penultimate first and second terms. Then we only need three variables to complete the derivation, and there is no need for the answer array. This is also the concept of sliding array:
[1,2,3] 1 <- [2,3,5] <- 5 2 <- [3,5,8] <- 8
class Solution { public: int climbStairs(int n) { if (n == 1) return 1; else if (n == 2) return 2; int pre2 = 1, pre = 2; int ans = 0; for (int _ = 3; _ <= n; _++) { ans = pre2 + pre; pre2 = pre; pre = ans; } return ans; } };
Complexity analysis
-
Time complexity: O ( n ) O(n) O(n)
-
Space complexity: O ( 1 ) O(1) O(1). We only need constant space to store several variables.
Reference results
Accepted 45/45 cases passed (0 ms) Your runtime beats 100 % of cpp submissions Your memory usage beats 79.65 % of cpp submissions (5.8 MB)
Method 2: general term formula
From the above, we know that the answer to this question exactly conforms to the Fibonacci sequence, so we can also calculate it directly from the general term formula of the Fibonacci sequence:
f
(
x
)
=
1
5
[
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
]
f(x)= \frac{1}{\sqrt{5}} \left[ {\left( \frac{1+ \sqrt{5}}{2} \right)}^n - {\left( \frac{1- \sqrt{5}}{2} \right)}^n \right]
f(x)=5
1[(21+5
)n−(21−5
)n]
#include <cmath> class Solution { public: int climbStairs(int n) { const double sqrt5 = sqrt(5); const double numerator = pow((1 + sqrt5) / 2, n + 1) - pow((1 - sqrt5) / 2, n + 1); return (int)round(numerator / sqrt5); } };
Complexity analysis
The space-time complexity of the pow function used in the code is related to the instruction set of the CPU, which is not analyzed here.
Reference results
Accepted 45/45 cases passed (0 ms) Your runtime beats 100 % of cpp submissions Your memory usage beats 25.47 % of cpp submissions (6 MB)
198. House raiding
LeetCode: 198. House raiding
in etc. \color{#FFB800} {medium} secondary
You are a professional thief who plans to steal houses along the street. There is a certain amount of cash hidden in each room. The only restrictive factor affecting your theft is that the adjacent houses are equipped with interconnected anti-theft systems. If two adjacent houses are intruded by thieves on the same night, the system will automatically alarm.
Given a non negative integer array representing the amount stored in each house, calculate the maximum amount you can steal overnight without touching the alarm device.
Example 1:
Input:[1,2,3,1] Output: 4 Explanation: stealing house 1 (amount of money = 1) ,Then steal house 3 (amount of money = 3). Maximum amount stolen = 1 + 3 = 4 .
Example 2:
Input:[2,7,9,3,1] Output: 12 Explanation: stealing house 1 (amount of money = 2), Stealing house 3 (amount of money = 9),Then steal house 5 (amount of money = 1). Maximum amount stolen = 2 + 9 + 1 = 12 .
Tips:
- 1 <= nums.length <= 100
- 0 <= nums[i] <= 400
Method 1: dynamic programming + sliding array
Let's set the array dp to store the maximum amount we can steal when we steal the i-th house. Next, let's think about how to calculate the maximum amount of each house? We consider what happened when we came to the k-th house:
- Steal the k-th house. Then, according to the meaning of the question, you can't steal house k-1, so the total amount of house K is the total amount dp[k-2] of the former House k-2 plus the amount nums[k].
- Don't steal the K house. Then the total amount at the k-th house is the total amount of the first k-1 house dp[k-1].
So we can write the following state transition equation (which can be understood as a recursive formula):
d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i]=max(dp[i-2]+nums[i], dp[i-1]) dp[i]=max(dp[i−2]+nums[i],dp[i−1])
Among them, dp[0] is the amount of the first house, nums[0], and dp[1] is the maximum value of the amount of the first house and the second house, max(nums[0], nums[1]). The final answer is the last item of the dp array.
Like the above question, the state transition equation of this question also involves three quantities: dp[i], dp[i-1], dp[i-2]. Therefore, this problem can also use the idea of sliding array to reduce the space complexity to O ( n ) O(n) The dp array of O(n) is optimized as O ( 1 ) O(1) Three variables of O(1).
#include <vector> using namespace std; class Solution { public: int rob(vector<int> &nums) { const int n = nums.size(); if (n == 1) { return nums[0]; } int pre = nums[0]; int ans = max(nums[0], nums[1]); for (int i = 2; i < n; i++) { int temp = ans; ans = max(pre + nums[i], ans); pre = temp; } return ans; } };
Complexity analysis
-
Time complexity: O ( n ) O(n) O(n)
-
Space complexity: O ( 1 ) O(1) O(1). We only need constant space to store several variables.
Reference results
Accepted 68/68 cases passed (0 ms) Your runtime beats 100 % of cpp submissions Your memory usage beats 47.78 % of cpp submissions (7.5 MB)
120. Triangle minimum path and
LeetCode: 120. Triangle minimum path and
in etc. \color{#FFB800} {medium} secondary
Given a triangle, find the minimum path sum from top to bottom.
Each step can only move to the adjacent nodes in the next row. Adjacent nodes here refer to two nodes whose subscript is the same as or equal to the subscript + 1 of the previous node. That is, if it is in the subscript i of the current row, the next step can be moved to the subscript i or i + 1 of the next row.
Example 1:
Input: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]] Output: 11 Explanation: as shown in the following diagram: 2 3 4 6 5 7 4 1 8 3 The minimum path sum from top to bottom is 11 (i.e., 2) + 3 + 5 + 1 = 11).
Example 2:
Input: triangle = [[-10]] Output:-10
Tips:
- 1 <= triangle.length <= 200
- triangle[0].length == 1
- triangle[i].length == triangle[i - 1].length + 1
- -104 <= triangle[i][j] <= 104
Advanced:
- Can you only use the extra space of O(n) (n is the total number of triangles) to solve this problem?
Method 1: dynamic programming
Set dp[level][i] as the minimum path sum when reaching the i-th position of the level
Let's first consider how to calculate the value of dp[i][j]. From the question: it can only come to (i, j) from (i-1, j-1) and (i-1, j). Therefore, dp[i][j] must be the minimum value of the first two terms plus its own value. The state transition equation is:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
+
t
r
i
a
n
g
l
e
[
i
]
[
j
]
dp[i][j]=min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
dp[i][j]=min(dp[i−1][j−1],dp[i−1][j])+triangle[i][j]
Since there are i elements in layer i and only i-1 elements in layer i-1, we should also pay attention to the boundary.
- When j=0, there is no (i-1, 0-1), so the leftmost node of each layer can only be calculated by dp[i-1][j].
- When j=i, there is no (i-1, i), so the rightmost node of each layer can only be calculated by dp[i-1][j-1].
#include <vector> #include <algorithm> using namespace std; class Solution { public: int minimumTotal(vector<vector<int>> &triangle) { const int n = triangle.size(); vector<vector<int>> dp(n, vector<int>(n)); dp[0][0] = triangle[0][0]; for (int i = 1; i < n; i++) { dp[i][0] = dp[i - 1][0] + triangle[i][0]; for (int j = 1; j < i; j++) { dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]; } dp[i][i] = dp[i - 1][i - 1] + triangle[i][i]; } return *min_element(dp[n - 1].begin(), dp[n - 1].end()); } };
Complexity analysis
-
Time complexity: O ( n 2 ) O(n^2) O(n2)
-
Space complexity: O ( n 2 ) O(n^2) O(n2). We need one n × n n \times n n × A two-dimensional array of n.
Reference results
Accepted 44/44 cases passed (4 ms) Your runtime beats 92.18 % of cpp submissions Your memory usage beats 13.81 % of cpp submissions (8.8 MB)
Method 2: dynamic programming + rolling array
d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j − 1 ] , d p [ i − 1 ] [ j ] ) + t r i a n g l e [ i ] [ j ] dp[i][j]=min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j] dp[i][j]=min(dp[i−1][j−1],dp[i−1][j])+triangle[i][j]
We can see from the previous state transition equation that dp[i] is only related to its previous line dp[i-1], that is, we can at least take the spatial complexity as O ( n 2 ) O(n^2) The dp array of O(n2) is reduced to O ( 2 × n ) = O ( n ) O(2 \times n)=O(n) O(2×n)=O(n).
Define an array predp, which is equivalent to dp[i-1]. Each time you go down one line, predp records the previous dp, and then stores the new results in dp. In this way, the optimization based on the idea of rolling array is completed.
#include <vector> #include <algorithm> using namespace std; class Solution { public: int minimumTotal(vector<vector<int>> &triangle) { const int n = triangle.size(); vector<int> *predp = new vector<int>(1, triangle[0][0]), *dp = predp; for (int level = 1; level < n; level++) { dp = new vector<int>(); dp->reserve(level + 1); dp->emplace_back(predp->at(0) + triangle[level][0]); for (int j = 1; j < level; j++) { dp->emplace_back(min(predp->at(j - 1), predp->at(j)) + triangle[level][j]); } dp->emplace_back(predp->at(level - 1) + triangle[level][level]); delete predp; predp = dp; } return *min_element(dp->begin(), dp->end()); } };
Complexity analysis
-
Time complexity: O ( n 2 ) O(n^2) O(n2)
-
Space complexity: O ( n ) O(n) O(n)
Reference results
Accepted 44/44 cases passed (4 ms) Your runtime beats 92.18 % of cpp submissions Your memory usage beats 37.89 % of cpp submissions (8.5 MB)