mirror of
https://github.com/Estom/notes.git
synced 2026-04-03 02:49:25 +08:00
123
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
|
||||
* 迭代思想,减治思想。每次选择一个元素,参与构建子集。
|
||||
* 首先对元素去重。记录重复元素的个数。如果多个重复元素分散在其他数组中没有意义。所有多个重复元素在一起的情况下组合到之前的解集中。
|
||||
* 这也算是一种动态规划的思想?每次都利用之前构建好的集合。构成新的解集集合。
|
||||
* 这也算是一种**动态规划**的思想?每次都利用之前构建好的集合。构成新的解集集合。
|
||||
|
||||
### 算法分析
|
||||
* 时间复杂度:$O(n×2^n)$
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
# 动态规划
|
||||
|
||||
> 参考文献
|
||||
> * [https://zhuanlan.zhihu.com/p/144655170](https://zhuanlan.zhihu.com/p/144655170)
|
||||
> * [https://www.cxyxiaowu.com/6781.html](https://www.cxyxiaowu.com/6781.html)
|
||||
> * [https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247486923&idx=2&sn=6c1c8aeb4db68522e67ddf8c1e933660&chksm=fa0e624acd79eb5cdb410808921609a830b9b9221e813e4eb89cf551ca48f317668d44b095d2&scene=21#wechat_redirect](https://mp.weixin.qq.com/s?__biz=MzUyNjQxNjYyMg==&mid=2247486923&idx=2&sn=6c1c8aeb4db68522e67ddf8c1e933660&chksm=fa0e624acd79eb5cdb410808921609a830b9b9221e813e4eb89cf551ca48f317668d44b095d2&scene=21#wechat_redirect)
|
||||
> * [https://www.cxyxiaowu.com/7012.html](https://www.cxyxiaowu.com/7012.html)
|
||||
|
||||
|
||||
## 1 概述
|
||||
### 基本思想
|
||||
|
||||

|
||||
* 多阶段决策问题。比如最短路线问题,机器负荷问题等,把解决这一类问题的的方法称为动态规划方法
|
||||
* 动态规划算法与分治法类似,其思想把求解的问题分成许多阶段或多个子问题,然后按顺序求解各子问题。最后一个阶段或子问题的解就是初始问题的解。
|
||||
|
||||
* 动态规划基本思想是保留已解决的子问题的解,在需要时再查找已求得的解,就可以避免大量重复计算,进而提升算法效率。
|
||||
|
||||
|
||||
### 对比分治法
|
||||
* 动态规划中分解得到的**子问题不是互相独立的**。不同子问题的数目常常只有多项式级,用分治法求解时,有些子问题被重复计算了多次,从而导致分治法求解问题时间复杂度极高。
|
||||
|
||||
@@ -19,28 +28,69 @@
|
||||
|
||||
|
||||
|
||||
## 2 构建动态规划模型
|
||||
## 2 动态规划模型的基本概念
|
||||
|
||||
### 基本概念
|
||||

|
||||
以上是求A到G的最短路线问题。
|
||||
### 阶段
|
||||
|
||||
* 阶段;明确问题的各个阶段:将问题划分为相互联系的各个阶段。
|
||||
* 状态:明确问题的各个状态,表达每个阶段的状态,定义状态变量。描述过程状态的变量,称为状态变量。常用xk表示在第k段的某一状态。
|
||||
* 决策:明确状态从一个阶段到另一个阶段的变化。实现状态转移方程。
|
||||
* 最优指标函数:用来判断决策过程中的优劣问题,淘汰不好的解,留下最优子结构。决策就是某阶段状态给定以后,从该状态演变到下一阶段某状态的选择。描述决策的变量,称为决策变量。
|
||||
明确问题的各个**阶段**:将问题划分为相互联系的各个阶段。以便能按一定的次序求解问题。阶段的划分一般是根据时间和空间的特征进行的,但是要能够把问题的过程转化为多阶段决策问题。
|
||||
|
||||
**最短路线问题中**:ABCDEFG是七个不同的阶段。
|
||||
|
||||
### 状态
|
||||
**状态**表示每个阶段开始所处的自然状况或者客观条件。
|
||||
|
||||
**最短路线问题中**:第一阶段的状态就是A,第二阶段的状态就是$\{B_1,B_2\}$ ,即第k阶段所有出发点的集合。描述过程状态的变量称为状态变量,用$\{S_k\}$表示第 k 阶段的**状态变量**。如$S_3=\{C_1,C_2,C_3,C_4\}$
|
||||
|
||||
动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
|
||||
* 要能够用来描述受控过程的演变特征。
|
||||
* 要满足无后效性。 所谓无后效性是指:如果某段状态给定,则在这段以后过程的发展不受前面各阶段状态的影响。
|
||||
* 可知性。即是规定的各段状态变量的值,由直接或间接都是可以知道的。
|
||||
|
||||
### 决策
|
||||
|
||||
决策表示当过程处于某一阶段某一状态时,可以做出不同的决定,从而确定下一阶段的状态,这个决定就叫做**决策**。
|
||||
|
||||
描述决策的变量,称为**决策变量**。可以是一个数一组数,也可以是一个向量。常用$u_k(s_k)$表示第 k 个阶段处于$s_k$时的决策变量,可见决策变量是状态的函数,也就是处于不同的状态时所能的决策与当前状态有关。
|
||||
|
||||
决策变量的取值常常限制在某一范围内,此范围称为**允许决策集合**。常用$D_k(s_k)$表示第 k 阶段从状态 $s_k$出发的允许决策集合,显然有 $u_k(s_k)$ 属于$D_k$
|
||||
|
||||
最短路线问题中:若从$B_1$出发,允许决策集合$D_2(B_1)=\{C_1,C_2,C_3\}=\{u_2(B_1)\}$
|
||||
|
||||
|
||||
### 策略
|
||||
|
||||
**策略**是按顺序排列的策略组成的集合。由过程的第k阶段开始到终止状态为止的过程,称为问题的后部子过程(或称为k子过程)。由每段的决策按照顺序排列组成的决策函数序列$\{u_k(s_k),u_{k+1}s_{k+1},\dots,u_n(s_n)\}$称为k子过程策略,简称为子策略.
|
||||
|
||||
### 状态转移方程
|
||||
|
||||
状态转移方程是确定过程由一个状态到另一个状态的演变过程。如果给定第 k 个阶段的状态变量$s_k$的取值,那么该阶段的决策变量$u_k(s_k)$一经确定,第 k+1 阶段的状态变量$s_{k+1}$的取值也就决定了。
|
||||
|
||||
$$
|
||||
s_{k+1}=T_k(s_k,u_k)
|
||||
$$
|
||||
|
||||
|
||||
### 最优指标函数
|
||||
用来判断决策过程中的优劣问题,淘汰不好的解,留下最优子结构。
|
||||
$$
|
||||
v_k=v_k(s_k,u_k,s_{k+1},u_{k+1},\dots,s_{n})
|
||||
$$
|
||||
|
||||
## 3 构建动态规划模型
|
||||
|
||||

|
||||
|
||||
### 方法
|
||||
|
||||
* 动态规划问题的关键在于正确地写出基本的递推关系式和恰当的边界条件
|
||||
* 备忘录方法(矩阵表格)
|
||||
|
||||
### 动态规划算法的步骤
|
||||
1. **确定状态变量$x_k$**:描述过程的状态,又要满足无后效性。动态规划中的状态与一般所说的状态概念是不同的,它必须具有三个特性:
|
||||
* 要能够用来描述受控过程的演变特征。
|
||||
* 要满足无后效性。 所谓无后效性是指:如果某段状态给定,则在这段以后过程的发展不受前面各阶段状态的影响。
|
||||
* 可知性。即是规定的各段状态变量的值,由直接或间接都是可以知道的。
|
||||
2. **确定决策变量$u_k$** :及每段的允许决策集合$D_k(x_k)=\{u_k\}$
|
||||
3. **确定状态转移方程** :如果给定第k段状态变量$x_k$的值,则该段的决策变量uk一经确定,第k+1段状态变量$x_{k+1}$的值也就完全确定。
|
||||
4. **确定指标函数$v_k$**:写出$v_k$和$n$关系,并要满足递推性。
|
||||
1. **问题分解划分阶段i**:找到规模增长的方向。规模增长如果只有一个方向,称为**线性规模增长**。如果有两个方向称为**非线性规模增长**。例如两个字符串最长公共子序列,典型的两个方向的非线性规模增长。给出增长方向的阶段序列。
|
||||
2. **确定状态变量$x_k$**:描述阶段的状态。某一个阶段可以只有一个状态,表示**线性决策过程**复杂度通常为O(n)。某一阶段也可以有多个状态,如图中所展示,称为**非线性决策过程**,复杂度通常为O(n*m)。n表示阶段的数量,m表示每个阶段状态的最大数量。
|
||||
3. **确定状态转移方程** :状态变量和决策变量构成的状态转移方程$x_{k+1}=T_k(u_k,x_k)$。如果给定第k段状态变量$x_k$的值,则该段的决策变量$u_k$一经确定,第k+1段状态变量$x_{k+1}$的值也就完全确定。
|
||||
4. **确定边界实现过程**。开始和结束的条件。往往添加$x_0$维,方便计算状态转移。
|
||||
|
||||
|
||||
### 可逆过程
|
||||
@@ -50,22 +100,32 @@
|
||||
* 顺序解法和逆序解法只表示行进方向的不同或始端的颠倒。但用动态规划方法求最优解时,都是在行进方向规定后,均要逆着这个规定的行进方向,从最后一段向前逆推计算,逐段找出最优途径
|
||||
|
||||
|
||||
## 3 动态规划分类
|
||||
## 4 动态规划分类
|
||||
### 规模增长
|
||||
* 确定规模的增长方向。一般在动态规划问题中。规模可变的并不只有一个。比如在正则表达式与字符串的匹配问题中。字符串的规模可以变化,正则表达式的规模也可以变化。
|
||||
* 根据规模增长的可以将动态规划分为三类:线性动态规划、矩阵动态规划、序列动态规划。
|
||||
* 根据规模增长的可以将动态规划分为四类:
|
||||
* 线性动态规划。线性规模增长。线性决策过程。例如:斐波那契数列、青蛙跳台阶、最大子段和。时间复杂度为O(n)
|
||||
* 树形动态规划。非线性规模增长。非线性决策过程。第二个规模增长方向构成该阶段的状态集合(每个阶段的状态并非一个),但是每个阶段的状态数量不确定。例如:三角形数组最小路径和、单向最短路径问题。时间复杂度为O(n*m)//m表示某一阶段最大的状态数量。
|
||||
* 矩阵动态规划。非线性规模增长。非线性决策。第二个规模增长方向与第一个规模增长方向独立,不受第一个规模增长影响,每个阶段的状态数量是固定的。例如背包问题、n个骰子的点数问题。O(n*m)。由于矩阵可以用来表示图。也可以定义为图型动态规划
|
||||
* 序列动态规划。非线性规模增长。非线性决策。第二个规模增长的方向不受第一个规模增长的影响,矩阵动态规划的特殊形式,针对序列的动态规划。例如:最长公共子序列、正则表达式匹配。O(n*m)
|
||||
|
||||
## 4.1 线性动态规划
|
||||
|
||||
## 4.2 树型动态规划
|
||||
|
||||
## 4.3 矩阵动态规划(图型动态规划)
|
||||
|
||||
|
||||
## 4.4 序列动态规划
|
||||
|
||||
|
||||
## 4 常见问题
|
||||
## 5 常见问题
|
||||
### 1矩阵连乘问题
|
||||
|
||||
### 2凸多边形最优三角剖分
|
||||
|
||||
### 3 最长公共子序列
|
||||
|
||||
### 4 斐波那契数列
|
||||
|
||||
### 5图像压缩问题
|
||||
|
||||
|
||||
82
算法/A类:基本算法/5 动态规划——序列动态规划.md
Normal file
82
算法/A类:基本算法/5 动态规划——序列动态规划.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# 动态规划——序列动态规划
|
||||
|
||||
|
||||
> 本质上也是矩阵动态规划,是特殊的矩阵动态规划,出现的频率比较高。包含两个规模增长方向,并且相互独立增长。不懂点在于题目是以序列的形式给出的。而不是矩阵形式给出的。
|
||||
|
||||
|
||||
1. 子序列(subsequence): 一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。例如序列<A,B,C,B,D,A,B>的子序列有:<A,B>、<B,C,A>、<A,B,C,D,A>等。
|
||||
2. 公共子序列(common subsequence): 给定序列X和Y,序列Z是X的子序列,也是Y的子序列,则Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,那么序列Z=[B,C,A]为X和Y的公共子序列,其长度为3。但Z不是X和Y的最长公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均为X和Y的最长公共子序列,长度为4,而X和Y不存在长度大于等于5的公共子序列。对于序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。
|
||||
3. 最长公共子序列:给定序列X和Y,从它们的所有公共子序列中选出长度最长的那一个或几个。
|
||||
4. 子串: 将一个序列从最前或最后或同时删掉零个或几个字符构成的新系列。区别与子序列,子序列是可以从中间抠掉字符的。cnblogs这个字符串中子序列有多少个呢?很显然有27个,比如其中的cb,cgs等等都是其子序列
|
||||
|
||||

|
||||
|
||||
|
||||
## 1 最长上升序列
|
||||
### 问题描述
|
||||
给定一个无序的整数数组,找到其中最长上升子序列的长度。
|
||||
|
||||
示例:
|
||||
```
|
||||
输入: [10,9,2,5,3,7,101,18]输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
|
||||
```
|
||||
说明:
|
||||
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
|
||||
你算法的时间复杂度应该为 O(n2) 。
|
||||
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
|
||||
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 问题分解划分阶段:如果我们确定终点位置,然后去 看前面 i – 1 个位置中,哪一个位置可以和当前位置拼接在一起,这样就可以把第 i 个问题拆解成思考之前 i – 1 个问题,注意这里我们并不是不考虑起始位置,在遍历的过程中我们其实已经考虑过了。
|
||||
* 确定状态变量:dp[i]表示i阶段为终点的最长上升序列
|
||||
* 确定状态转移方程:
|
||||
$$
|
||||
dp[i] = Math.max(dp[j],...,dp[k]) + 1, \\
|
||||
inputArray[j] < inputArray[i], inputArray[k] < inputArray[i]
|
||||
$$
|
||||
* 确定边界。添加初始的0.然后递增。
|
||||
|
||||
|
||||
### 算法分析
|
||||
|
||||
|
||||
### 算法实现
|
||||
|
||||
```
|
||||
//@五分钟学算法//www.cxyxiaowu.compublic int lengthOfLIS(int[] nums) {
|
||||
if (nums == null || nums.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dp[i] -> the longest length sequence from 0 - i, and must include nums[i]
|
||||
int[] dp = new int[nums.length];
|
||||
|
||||
Arrays.fill(dp, 1);
|
||||
|
||||
int max = 0;
|
||||
|
||||
for (int i = 0; i < nums.length; ++i) {
|
||||
for (int j = 0; j < i; ++j) {
|
||||
if (nums[i] > nums[j]) {
|
||||
dp[i] = Math.max(dp[j] + 1, dp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
max = Math.max(max, dp[i]);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
```
|
||||
|
||||
## 2 最长公共子序列
|
||||
> 5.3
|
||||
|
||||
## 3 正则表达式匹配
|
||||
|
||||
> 5.13
|
||||
|
||||
55
算法/A类:基本算法/5 动态规划——树形动态规划.md
Normal file
55
算法/A类:基本算法/5 动态规划——树形动态规划.md
Normal file
@@ -0,0 +1,55 @@
|
||||
## 1 三角形数组最小路径和
|
||||
|
||||
### 问题描述
|
||||
|
||||
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
|
||||
|
||||
例如,给定三角形:
|
||||
```
|
||||
[
|
||||
[2],
|
||||
[3,4],
|
||||
[6,5,7],
|
||||
[4,1,8,3]
|
||||
]
|
||||
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
|
||||
```
|
||||
说明:如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 策略选择
|
||||
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 问题分解划分阶段:规模增长方向,从下到上(不用判断边界)。阶段n=n,n-1,...,1
|
||||
* 确定状态变量:k阶段的状态变量是数组X[n]内的前k项,组成的状态变量集合。
|
||||
* 状态转移方程。k阶段的状态变量X_k[n],分两种情况讨论。X_k[j]可能有两种情况,利用了上一阶段X_k-1[j]的长度。或者领了X_k-1[j+1]的长度。
|
||||
$$
|
||||
X_{k}[j]=min\{X_{k-1}[j-1]+triangle[j],X_{k-1}[j+1]+triangle[j]\}
|
||||
$$
|
||||
* 确定边界实现过程。初始值为0。从最后一层开始。
|
||||
|
||||
|
||||
### 算法实现
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
int minimumTotal(vector<vector<int>>& triangle) {
|
||||
int n = triangle.size();
|
||||
vector<int> result(n+1,0);
|
||||
// cout<<result.size()<<endl;
|
||||
for(int i=n-1;i>=0;i--){
|
||||
for(int j=0;j<triangle[i].size();j++){
|
||||
result[j]=triangle[i][j]+min(result[j],result[j+1]);
|
||||
}
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
};//拷贝初始化会拖慢速度
|
||||
```
|
||||
|
||||
|
||||
193
算法/A类:基本算法/5 动态规划——矩阵动态规划.md
Normal file
193
算法/A类:基本算法/5 动态规划——矩阵动态规划.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 动态规划——矩阵动态规划
|
||||
|
||||
> 矩阵可以看作是图的一种,怎么说?你可以把整个矩阵当成一个图,矩阵里面的每个位置上的元素当成是图上的节点,然后每个节点的邻居就是其相邻的上下左右的位置
|
||||
>
|
||||
> 矩阵类动态规划的两个规模增长方向可能存在两种情况:等价独立和非等价独立。例如不同路径和最大矩形中的规模增长方向,是等价独立的,两个规模增长的含义是一样的,比较好判断。但是在背包问题等其他子问题中。规模增长的方向是不等价,一个是背包容量的增长,另一个是物品数量的增长,通常比较难发现。
|
||||
> * 不同路径
|
||||
> * 最大矩形
|
||||
|
||||
## 1 不同路径
|
||||
|
||||
### 问题描述
|
||||
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
|
||||
|
||||
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
|
||||
|
||||
问总共有多少条不同的路径?
|
||||

|
||||
例如,上图是一个7 x 3 的网格。有多少可能的路径?说明: m 和 n 的值均不超过 100。
|
||||
|
||||
示例 1:
|
||||
```
|
||||
输入: m = 3, n = 2
|
||||
输出: 3
|
||||
解释:
|
||||
从左上角开始,总共有 3 条路径可以到达右下角。
|
||||
|
||||
1. 向右 -> 向右 -> 向下
|
||||
2. 向右 -> 向下 -> 向右
|
||||
3. 向下 -> 向右 -> 向右
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
|
||||
### 策略选择
|
||||
|
||||
|
||||
### 算法设计
|
||||
* 问题分解划分阶段:规模增长的方向有两个m和n,两者相互独立。第一个阶段是m。第二个阶段是n
|
||||
* 确定状态变量。对于第i,j阶段的状态变量,x(i,j)。表示到达改点的路径的总的数量。构成状态矩阵。
|
||||
* 确定状态转移方程。对于i,j阶段的状态,有两种构成情况。从上向下,从左向右。所以:
|
||||
|
||||
$$
|
||||
x(i,j)=x(i-1,j)+x(i,j-1)
|
||||
$$
|
||||
* 确定边界。添加额外列,额外行,表示可能性为0.
|
||||
|
||||
|
||||
### 算法分析
|
||||
|
||||
* 时间复杂度O(m+n)
|
||||
* 空间复杂度O(m*n)
|
||||
|
||||
### 算法实现
|
||||
```C++
|
||||
class Solution {
|
||||
public:
|
||||
int uniquePaths(int m, int n) {
|
||||
vector<int> vec(n,1);
|
||||
vector<vector<int>> result(m,vec);
|
||||
for(int i=1;i<m;i++){
|
||||
for(int j=1;j<n;j++){
|
||||
result[i][j]=result[i-1][j]+result[i][j-1];
|
||||
}
|
||||
}
|
||||
return result[m-1][n-1];
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 2 最大矩形
|
||||
### 问题描述
|
||||
|
||||
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
|
||||
|
||||
示例:
|
||||
|
||||
输入:
|
||||
```
|
||||
1 0 1 0 0
|
||||
1 0 1 1 1
|
||||
1 1 1 1 1
|
||||
1 0 0 1 0
|
||||
输出: 4
|
||||
```
|
||||
### 问题分析
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 问题分解划分阶段:规模增长方向横向和纵向两个。选择阶段i,j
|
||||
* 确定状态变量:x(i,j)表示i,j能构成的最大矩形。
|
||||
* 确定状态转移方程。x(i,j)构成的最大矩形与(i-1,j),(i,j-1),(i-1,j-1)有关。是三个中最小的那个决定的。
|
||||
$$
|
||||
x(i,j)=min(x(i-1,j),x(i,j-1),x(i-1,j-1))
|
||||
$$
|
||||
|
||||
### 算法分析
|
||||
|
||||
### 算法实现
|
||||
|
||||
```java
|
||||
public int maximalSquare(char[][] matrix) {
|
||||
if (matrix.length == 0 || matrix[0].length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int m = matrix.length, n = matrix[0].length;
|
||||
|
||||
int[][] dp = new int[m][n];
|
||||
|
||||
int maxLength = 0;
|
||||
|
||||
for (int i = 0; i < m; ++i) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
if (matrix[i][j] == '1') {
|
||||
if (i == 0 || j == 0) {
|
||||
dp[i][j] = matrix[i][j] == '1' ? 1 : 0;
|
||||
} else {
|
||||
dp[i][j] = Math.min(dp[i - 1][j],
|
||||
Math.min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
|
||||
}
|
||||
|
||||
maxLength = Math.max(dp[i][j], maxLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxLength * maxLength;
|
||||
}
|
||||
```
|
||||
|
||||
### 3 粉刷房子
|
||||
|
||||
### 问题描述
|
||||
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
|
||||
|
||||
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的矩阵来表示的。
|
||||
|
||||
例如,costs[0][0]表示第 0 号房子粉刷成红色的成本花费;costs[1][2]表示第 1 号房子粉刷成绿色的花费,以此类推。请你计算出粉刷完所有房子最少的花费成本。
|
||||
|
||||
注意:
|
||||
|
||||
所有花费均为正整数。
|
||||
|
||||
示例:
|
||||
```
|
||||
输入: [[17,2,17],[16,16,5],[14,3,19]]
|
||||
输出: 10
|
||||
解释: 将 0 号房子粉刷成蓝色,1 号房子粉刷成绿色,2 号房子粉刷成蓝色。
|
||||
最少花费: 2 + 5 + 3 = 10。
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 策略选择
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 问题分解划分阶段:规模增长的方向为房子的个数n,颜色的数量m。划分阶段为n=1,2,3,...,k。m=0,1,2
|
||||
* 确定状态变量。dp(i,j)表示第i个房子被染成j颜色的最低费用。
|
||||
* 确定状态转移方程
|
||||
|
||||
$$
|
||||
dp(i,j)=min(dp(i-1,(j+1)\%3),dp(i-1,(j+2)\%3))+cost(i,j)
|
||||
$$
|
||||
|
||||
* 确定边界实现过程。添加0房子边界。
|
||||
|
||||
### 算法分析
|
||||
|
||||
### 算法实现
|
||||
```
|
||||
//@五分钟学算法//www.cxyxiaowu.compublic int minCost(int[][] costs) {
|
||||
if (costs == null || costs.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int n = costs.length;
|
||||
|
||||
int[][] dp = new int[n][3];
|
||||
|
||||
for (int i = 0; i < costs[0].length; ++i) {
|
||||
dp[0][i] = costs[0][i];
|
||||
}
|
||||
|
||||
for (int i = 1; i < n; ++i) {
|
||||
dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + costs[i][0];
|
||||
dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + costs[i][1];
|
||||
dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + costs[i][2];
|
||||
}
|
||||
|
||||
return Math.min(dp[n - 1][0], Math.min(dp[n - 1][1], dp[n - 1][2]));
|
||||
}
|
||||
```
|
||||
@@ -1,4 +1,119 @@
|
||||
## 1 连续子数组最大和
|
||||
# 动态规划——线性动态规划
|
||||
|
||||
> 主要用来熟练动态规划的步骤
|
||||
> * 斐波那契数列
|
||||
> * 青蛙跳台阶
|
||||
> * 最大子段和
|
||||
|
||||
## 1 斐波那契数列
|
||||
|
||||
### 问题描述
|
||||
|
||||
* 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
|
||||
```
|
||||
F(0) = 0, F(1) = 1
|
||||
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
|
||||
```
|
||||
* 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
|
||||
* [链接](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof)
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 问题分类
|
||||
|
||||
* 数组
|
||||
* 动态规划+迭代
|
||||
|
||||
### 选择策略
|
||||
* 数组
|
||||
* 动态规划
|
||||
* 原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)f(n+1)=f(n)+f(n−1) 为转移方程。
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 状态定义: 设$dp$为一维数组,其中 $dp[i]$ 的值代表 斐波那契数列第 $i$个数字 。
|
||||
* 转移方程: $dp[i + 1] = dp[i] + dp[i - 1]$,即对应数列定义 $f(n + 1) = f(n) + f(n - 1)$;
|
||||
* 初始状态: $dp[0] = 0 dp[1] = 1$,即初始化前两个数字;
|
||||
* 返回值:$dp[n]$ ,即斐波那契数列的第 n 个数字。
|
||||
|
||||
|
||||
### 算法分析
|
||||
* 时间复杂度O(n)
|
||||
* 空间复杂度O(n)
|
||||
|
||||
|
||||
### 算法实现
|
||||
|
||||
```
|
||||
int fib(int n) {
|
||||
// 递归法,时间复杂度太高。
|
||||
if(n==0){
|
||||
return 0;
|
||||
}
|
||||
if(n==1){
|
||||
return 1;
|
||||
}
|
||||
// return fib(n-1)+fib(n-2);
|
||||
long long a[n+1];
|
||||
a[0]=0;
|
||||
a[1]=1;
|
||||
for(int i=2;i<n+1;i++){
|
||||
a[i]=(a[i-1]+a[i-2])%(1000000007);
|
||||
}
|
||||
return a[n];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 2 爬楼梯
|
||||
|
||||
### 问题描述
|
||||
|
||||
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。
|
||||
|
||||
示例 1:
|
||||
```
|
||||
输入:2
|
||||
输出:2
|
||||
解释: 有两种方法可以爬到楼顶。
|
||||
1. 1 阶 + 1 阶
|
||||
2. 2 阶
|
||||
```
|
||||
### 问题分析
|
||||
|
||||
### 策略选择
|
||||
|
||||
### 算法设计
|
||||
|
||||
1. 问题分解划分阶段:规模增长的方向n。线性规模增长。阶段n=1,2,...,n各个阶段。
|
||||
2. 确定状态变量:xk,表示k阶段公有xk中爬楼梯的方法
|
||||
3. 确定状态转移方程:分为两种情况讨论,如果最后是1步,则x_k =x_k-1。如果最后一步是两步。则x_k=x_k-2。
|
||||
|
||||
$$
|
||||
x_k = x_{k-1}+x_{k-2}
|
||||
$$
|
||||
4. 确定边界。x_0=0表示开始的情况。x_k表示当前的结果。x_n表示终止的情况。
|
||||
|
||||
### 算法实现
|
||||
```java
|
||||
public int climbStairs(int n) {
|
||||
if (n == 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int[] dp = new int[n + 1]; // 多开一位,考虑起始位置
|
||||
|
||||
dp[0] = 0; dp[1] = 1; dp[2] = 2;
|
||||
for (int i = 3; i <= n; ++i) {
|
||||
dp[i] = dp[i - 1] + dp[i - 2];
|
||||
}
|
||||
|
||||
return dp[n];
|
||||
}
|
||||
```
|
||||
|
||||
## 3 连续子数组最大和
|
||||
|
||||
### 问题描述
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
2. 确定状态转移方程
|
||||
3. 边界处理
|
||||
|
||||
* 下面我们一步一步分析,相信你一定会有所收获!
|
||||
|
||||
* 表示状态
|
||||
* 分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。
|
||||
@@ -43,7 +42,7 @@
|
||||
* 所以状态表示就是这样的:dp[i][j]dp[i][j] ,表示投掷完 ii 枚骰子后,点数 jj 的出现次数。
|
||||
* 找出状态转移方程
|
||||
* 找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何得到的。
|
||||
* 最后一个阶段也就是投掷完 nn 枚骰子后的这个阶段,我们用 dp[n][j]来表示最后一个阶段点数 j出现的次数。
|
||||
* 最后一个阶段也就是投掷完 n 枚骰子后的这个阶段,我们用 dp[n][j]来表示最后一个阶段点数 j出现的次数。
|
||||
* 单单看第 n 枚骰子,它的点数可能为 1 , 2, 3, ... , 6因此投掷完 n 枚骰子后点数 j 出现的次数,可以由投掷完n−1 枚骰子后,对应点数 j-1, j-2, j-3, ... , j-6出现的次数之和转化过来。
|
||||
|
||||
```
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# 斐波那契数列
|
||||
|
||||
## 1 斐波那契数列
|
||||
|
||||
### 问题描述
|
||||
|
||||
* 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
|
||||
```
|
||||
F(0) = 0, F(1) = 1
|
||||
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
|
||||
```
|
||||
* 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
|
||||
* [链接](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof)
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 问题分类
|
||||
|
||||
* 数组
|
||||
* 动态规划+迭代
|
||||
|
||||
### 选择策略
|
||||
* 数组
|
||||
* 动态规划
|
||||
* 原理: 以斐波那契数列性质 f(n + 1) = f(n) + f(n - 1)f(n+1)=f(n)+f(n−1) 为转移方程。
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 状态定义: 设$dp$为一维数组,其中 $dp[i]$ 的值代表 斐波那契数列第 $i$个数字 。
|
||||
* 转移方程: $dp[i + 1] = dp[i] + dp[i - 1]$,即对应数列定义 $f(n + 1) = f(n) + f(n - 1)$;
|
||||
* 初始状态: $dp[0] = 0 dp[1] = 1$,即初始化前两个数字;
|
||||
* 返回值:$dp[n]$ ,即斐波那契数列的第 n 个数字。
|
||||
|
||||
|
||||
### 算法分析
|
||||
* 时间复杂度O(n)
|
||||
* 空间复杂度O(n)
|
||||
|
||||
|
||||
### 算法实现
|
||||
|
||||
```
|
||||
int fib(int n) {
|
||||
// 递归法,时间复杂度太高。
|
||||
if(n==0){
|
||||
return 0;
|
||||
}
|
||||
if(n==1){
|
||||
return 1;
|
||||
}
|
||||
// return fib(n-1)+fib(n-2);
|
||||
long long a[n+1];
|
||||
a[0]=0;
|
||||
a[1]=1;
|
||||
for(int i=2;i<n+1;i++){
|
||||
a[i]=(a[i-1]+a[i-2])%(1000000007);
|
||||
}
|
||||
return a[n];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
## 01 背包问题
|
||||
# 背包问题
|
||||
## 1 01 背包问题
|
||||
|
||||
### 问题描述
|
||||
有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?
|
||||
有m个物品,它们有各自的体积和价值,现有给定容量的背包c,如何让背包里装入的物品具有最大的价值总和?
|
||||
|
||||
### 问题分析
|
||||
|
||||
@@ -9,9 +10,62 @@
|
||||
|
||||
|
||||
### 算法设计
|
||||
* 问题分解划分阶段:2个规模增长方向。背包的容量和物品。背包的容量表示为c=1,2,3,n。物品的重量表示为w1,w2,w3。物品的机制v1,v2,v3。第一个阶段表示是否添加物品。第二个阶段是背包容量的增长。
|
||||
* 确定状态dp[c,m]
|
||||
* 确定状态转移方程
|
||||
|
||||
$$
|
||||
dp[c,m]=max(dp[c-1],v[m]+dp[c-w[m]])
|
||||
$$
|
||||
|
||||
### 算法分析
|
||||
|
||||
### 算法实现
|
||||
|
||||
```
|
||||
for(int i=1;i<=n;i++)
|
||||
{
|
||||
for(int j=v;j>=weight[i];j--)
|
||||
{
|
||||
dp[j]=max(dp[j],dp[i-weight[i]]+value[i]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2 完全背包问题
|
||||
|
||||
### 问题描述
|
||||
|
||||
|
||||
### 问题分析
|
||||
|
||||
|
||||
### 策略选择
|
||||
|
||||
|
||||
### 算法设计
|
||||
|
||||
* 问题分解划分阶段:2个规模增长方向。背包的容量和物品。背包的容量表示为c=1,2,3,n。物品的重量表示为w1,w2,w3。物品的机制v1,v2,v3。第一个阶段表示是否添加物品。第二个阶段是背包容量的增长。
|
||||
* 确定状态dp[c,m]
|
||||
* 确定状态转移方程
|
||||
|
||||
$$
|
||||
dp[i][j]=max(dp[i][j],dp[i-1][j-k*weight[i]]+k*value[i]);
|
||||
$$
|
||||
* 确定边界
|
||||
### 算法分析
|
||||
|
||||
|
||||
### 算法实现
|
||||
```
|
||||
|
||||
for(i=1;i<=n;i++)
|
||||
{
|
||||
for(j=weight[i];j<=v;j++)
|
||||
{
|
||||
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3 多重背包
|
||||
BIN
算法/A类:基本算法/image/2021-04-01-09-44-35.png
Normal file
BIN
算法/A类:基本算法/image/2021-04-01-09-44-35.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
算法/A类:基本算法/image/2021-04-01-09-46-42.png
Normal file
BIN
算法/A类:基本算法/image/2021-04-01-09-46-42.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
算法/A类:基本算法/image/2021-04-01-11-39-32.png
Normal file
BIN
算法/A类:基本算法/image/2021-04-01-11-39-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Reference in New Issue
Block a user