初探动态规划
1、定义
一种数学优化的方法,同时也是编程的方法。
重要属性:
- 最优子结构 Optimal Substructure
-
- 状态转移方程f{n}
-
- 重叠子问题(Overlapping Sub-problems)
必须满足以上两个属性才可以归为动态规划问题。
2、例题讲解
2.1、例题1 引入
要求从A到C的最长路径,要求,每个顶点只能出现一次。
A到C的最长距离
- A->B->C
- A 到 B的最长距离
- B 到 C的最长距离
最后有一条路径出来了
A->D->C->B->A->D->C
对于这道题而言,没有最佳子结构,所以无法利用动态规划来解题。
2.2、例题2 LeetCode第300题
2.2.1、动态规划方法
题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
思路:将问题规模减少,推导出状态转移方程式
来几个直观的图片:
那么这一题,状态转移方程怎么推导呢?
到这里,已经可以证明这个问题是存在最优子结构的,我们继续分析。(emmm这里还有点不懂)
2.2.2、公式法
通过观察得到:
复杂度太高,放弃。
2.2.3、记忆化
时间复杂度分析:
对于这种将问题规模不断减少的做法,我们把它称为自顶向下的方法。
换一种自底向上的方法吧,
老规矩,我们来分析一下时间复杂度吧!
3、动态规划解题难点
- 应该采用什么样的数据结构来保存什么样的计算结果
- 如何利用保存下来的计算结果推导出状态转移方程
3.1、线性规划
- 线性规划
- 各个子问题的规模以线性的方式分布。
- 子问题的最佳状态或结果可以存储在一维线性的数据结构中,例如:一维数组,哈希表等。
- 通常我们会用dp[i]表示第i个位置的结果,或者从0开始到第i个位置为止的最佳状态或结果。
题目:
求出递归公式:
代码实现:
public int rob(int[] nums) {
int n = nums.length;
// 处理当数组为空或者数组只有一个元素的情况
if(n == 0) return 0;
if(n == 1) return nums[0];
// 定义一个 dp 数组,dp[i] 表示到第 i 个元素为止我们所能收获到的最大总数
int[] dp = new int[n];
// 初始化 dp[0],dp[1]
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
// 对于每个 nums[i],考虑两种情况,选还是不选,然后取最大值
for (int i = 2; i < n; i++) {
dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]);
}
return dp[n - 1];
}
LeetCode第62题
3.2、区间规划
各个子问题的规模由不同区间来定义
子问题的最佳状态或结果可以存储在为二维数组中
这类问题的时间复杂度一般为多项式时间,即对于一个大小为n的问题,时间复杂度不会超过n的多项式倍数。
最长回文子序列
给定一个字符串s,找到其中最长的回文子序列。
可以假设s的最大长度为1000。
递归公式,
3.3、约束规划
在普通的线性规划里,一般题目有两种需求:
- 统计
- 最优解
例题:0-1背包问题
给定n个物品,每个物品有各自的价值v和重量w,在限定的最大重量内,我们如何选择,才能使被带走的物品的价值总和最大?
这题自己做一遍。
学习动态规划没有捷径,更重要的是多练!