线性dp
斐波那契数列
[
1110
]
[
F
(
n
)
F
(
n
−
1
)
]
=
[
F
(
n
)
+
F
(
n
−
1
)
F
(
n
)
]
=
[
F
(
n
+
1
)
F
(
n
)
]
[ 1 110 ][ F(n)F(n−1) ]=[ F(n)+F(n−1)F(n)]=[ F(n+1)F(n)]
[1110][F(n)F(n−1)]=[F(n)+F(n−1)F(n)]=[F(n+1)F(n)]
因此:
[
F
(
n
+
1
)
F
(
n
)
]
=
[
1
1
1
0
]
n
[
F
(
1
)
F
(
0
)
]
\left[ \begin{matrix} F(n + 1)\\ F(n) \end{matrix} \right] = \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] ^n \left[ \begin{matrix} F(1)\\ F(0) \end{matrix} \right]
[F(n+1)F(n)]=[1110]n[F(1)F(0)]
上述等式为O(log(n))解法的关键
DP3跳台阶拓展问题
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
scanf("%d",&n);
--n;
printf("%d",1<<n);
return 0;
}
/*
dp(0) = 1;
dp(1) = 1;
dp(2) = dp(1) + dp(0) = 2
dp(3) = dp(2) + dp(1) + dp(0) = dp(2) + dp(2) = 2*dp(2) = 4
dp(4) = dp(3) + dp(2) + dp(1) + dp(0) = 2*dp(3) = 8
...
dp(n) = dp(n-1) + dp(n-2) + dp(n-3) + ... + dp(1) + dp(0) = 2*dp(n-1)
dp(n) = 2^(n-1) n >= 1
dp(0) = 0
*/
dp4 最小花费爬楼梯
/*
定义dp(i) 为到达第i个台阶的最小花费
因为一次可以跳1个或2个
那么类似于斐波那契数列
第i个只能从i-1, i-2个来
其实这也是为什么题目要规定能从第0个或者第1个开始跳
方便你一块进行处理
那么递推公式就很明显了
dp(i) =
min(dp(i-1) + cost(i-1), dp(i-2) + cost(i-2));
从最小花费跳过来的才最小
我们不需要管dp(i-1)是最小的
只需要它是 到达i-1最小的那个就行了
*/
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
scanf("%d",&n);
vector<int> cost(n), dp(n+1);
for(int i = 0; i < n; ++i) {
scanf("%d",&cost[i]);
}
for(int i = 2; i <= n; ++i) {
dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
printf("%d",dp[n]);
return 0;
}
打家劫舍
上面这题和打家劫舍从本质上可归属于一类
就放在一块写了
下面的这份是官方代码 稍微加了点注释便于理解
原题链接
class Solution {
public:
int rob(vector<int>& nums) {
//边界条件
if (nums.empty()) {
return 0;
}
int size = nums.size();
if (size == 1) {
return nums[0];
}
vector<int> dp = vector<int>(size, 0);//前i个房屋可以抢劫到的最大金额 i = {1,2,...,n}
dp[0] = nums[0];//包含第i个 前1个只能抢劫它了
dp[1] = max(nums[0], nums[1]);//抢劫第0个 或者第1个
for (int i = 2; i < size; i++) {
//num(i) + dp(i-2) 选择偷第i个 那么只能从dp(i-2)累加
//dp(i-1) 不偷第i个 从dp(i-1)累加
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp.back();
}
};
当然还可以用一种空间上更优的解法。
观察到数组中有的元素用了一遍就没有使用了,这无疑是可以优化的。
优化后空间复杂度为O(1)
class Solution {
public:
int rob(vector<int>& nums) {
//边界处理
if (nums.empty()) {
return 0;
}
int size = nums.size();
if (size == 1) {
return nums[0];
}
//可以优化的原因在于 实际上我们只使用了 3个数据 dp(i),dp(i-1),dp(i-2) 所有的操作都是在这三个数据上进行的 而像nums可以直接访问 故对上面那个程序稍微进行修改 即得到下面这种形式
int first = nums[0], second = max(nums[0], nums[1]);
for (int i = 2; i < size; i++) {
int temp = second;
second = max(first + nums[i], second);
first = temp;
}
return second;
}
};
以上两份代码均来自leetcode官方题解
详细解释可以看看官方的题解,这里只点明关键点。