一. 什么是动态规划?
- 通过使用递归、记忆化搜索、动态规划 三种方法 解 斐波那契数列问题, 来说明
递归
- 从上而下
- 但存在大量重复计算
- 举例: f(10)=f(9)+f(8) 与 f(9)=f(8)+f(7) 这里f(8)就被重复计算了
#include <iostream>
#include <ctime>
using namespace std;
int num = 0;
// 递归求斐波那契数列
int fib( int n ){
num ++;
if( n == 0 )
return 0;
if( n == 1 )
return 1;
return fib(n-1) + fib(n-2);
}
记忆化搜索
- 在递归的基础上优化
- 将计算过的f(n)都保存起来, 避免重复计算
#include <iostream>
#include <ctime>
#include <vector>
using namespace std;
vector<int> memo; // 用来保存 计算过的数值
int num = 0;
// 记忆化搜索
int fib(int n){
num ++;
if(n == 0)
return 0;
if(n == 1)
return 1;
if(memo[n] == -1)
memo[n] = fib(n - 1) + fib(n - 2);
return memo[n];
}
动态规划
#include <iostream>
#include <ctime>
#include <vector>
using namespace std;
// 动态规划
int fib( int n ){
vector<int> memo(n + 1, -1);
memo[0] = 0;
memo[1] = 1;
for(int i = 2 ; i <= n ; i ++)
memo[i] = memo[i - 1] + memo[i - 2];
return memo[n];
}
二.第一个动态规划问题 Climbing Stairs
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
递归解决
- 直接使用 依然会存在 重复计算的问题
- 配合记忆化搜索 来解决问题
#include <iostream>
#include <vector>
using namespace std;
/// 70. Climbing Stairs
/// https://leetcode.com/problems/climbing-stairs/description/
/// 记忆化搜索
/// 时间复杂度: O(n)
/// 空间复杂度: O(n)
class Solution {
private:
vector<int> memo; // 存储计算过的内容
int calcWays(int n){
if(n == 0 || n == 1)
return 1;
if(memo[n] == -1) // memo[n] 如果没有计算过
memo[n] = calcWays(n - 1) + calcWays(n - 2);
return memo[n];
}
动态规划
#include <iostream>
#include <vector>
using namespace std;
/// 70. Climbing Stairs
/// https://leetcode.com/problems/climbing-stairs/description/
/// 动态规划
/// 时间复杂度: O(n)
/// 空间复杂度: O(n)
class Solution {
public:
int climbStairs(int n) {
vector<int> memo(n + 1, -1);
memo[0] = 1;
memo[1] = 1;
for(int i = 2 ; i <= n ; i ++)
memo[i] = memo[i - 1] + memo[i - 2];
return memo[n];
}
};
三. 发现重叠子问题 Integer Break
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
解题思路
- 重叠子问题: 在寻找子问题的最大乘积中得到答案
- 以4为例: 寻找3, 2, 1的最大乘积
代码
#include <iostream>
#include <cassert>
using namespace std;
/// 343. Integer Break
/// https://leetcode.com/problems/integer-break/description/
/// 暴力搜索
/// 在Leetcode中提交这个版本的代码会超时! (Time Limit Exceeded)
/// 时间复杂度: O(n^n)
/// 空间复杂度: O(n)
class Solution {
private:
int max3(int a, int b, int c){
return max(a, max(b, c));
}
// 将n进行分割(至少分割两部分), 可以获得的最大乘积
int breakInteger(int n){
if(n == 1)
return 1;
int res = -1;
for(int i = 1 ; i <= n - 1 ; i ++)
res = max3(res, i * (n - i), i * breakInteger(n - i));
// n-i 与n-i的分割的最大乘积, 需要比较
return res;
}
public:
int integerBreak(int n) {
assert(n >= 1);
return breakInteger(n);
}
};
#include <iostream>
#include <vector>
#include <cassert>
using namespace std;
/// 343. Integer Break
/// https://leetcode.com/problems/integer-break/description/
/// 记忆化搜索
/// 时间复杂度: O(n^2)
/// 空间复杂度: O(n)
class Solution {
private:
vector<int> memo;
int max3(int a, int b, int c){
return max(a, max(b, c));
}
// 将n进行分割(至少分割两部分), 可以获得的最大乘积
int breakInteger(int n){
if(n == 1)
return 1;
if(memo[n] != -1)
return memo[n];
int res = -1;
for(int i = 1 ; i <= n - 1 ; i ++)
res = max3(res, i * (n - i) , i * breakInteger(n - i));
memo[n] = res;
return res;
}
public:
int integerBreak(int n) {
assert(n >= 1);
memo.clear();
for(int i = 0 ; i < n + 1 ; i ++)
memo.push_back(-1);
return breakInteger(n);
}
};
#include <iostream>
#include <vector>
using nam