动态规划 经典DP

 

 

动态规划

动态规划的基本思想

将⼀个问题分解为⼦问题递归求解,且将中间结果保存以避免重复计算。通常⽤来求最优

解,且最优解的局部也是最优的。求解过程产⽣多个决策序列,下⼀步总是依赖上⼀步的

结果,⾃底向上的求解。 动态规划算法可分解成从先到后的4个步骤:

1. 描述⼀个最优解的结构,寻找⼦问题,对问题进⾏划分。

2. 定义状态。往往将和⼦问题相关的各个变量的⼀组取值定义为⼀个状态。某个状态

的值就是这个⼦问题的解(若有k个变量,⼀般⽤K维的数组存储各个状态下的解,

并可根据这个数组记录打印求解过程)。

3. 找出状态转移⽅程。⼀般是从⼀个状态到另⼀个状态时变量值改变。

4. 以“⾃底向上”的⽅式计算最优解的值。

5. 从已计算的信息中构建出最优解的路径。(最优解是问题达到最优值的⼀组解)

其中步骤1~4是动态规划求解问题的基础,如果题⽬只要求最优解的值,则步骤5可以省

 

 

略。

 

最⼤连续⼦段和


N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续⼦段和

的最⼤值。当所给的整数均为负数时和为0。

1.定义状态:

sum[i]表⽰以第i位为结尾的最⼤⼦段和,ans为整个序列的最⼤字段和. 2.状态转移:

sum[i] = max{sum[i-1]+a[i],a[i]}

ans = max{sum[1],sum[2].....sum[n]}

sum[i]只与sum[i-1]有关,我们可以不⽤数组来记录,只⽤sum记录以当前元素结尾的最⼤⼦

段和,每次更新sum即可. 3.⾃底向上求解:
 

ans= sum = 0;

for (i = 1;i <= n;i++){

sum = max(sum,0)+a[i];

ans = max(sum,ans);

}

 

 

(模板,套用就行,大数据用long long)

 

最⻓递增⼦序列


⼦串&⼦序列:

⼦串:指给定字符串中选取的某⼀连续的段

⼦序列:可以不连续,但是要保证原字符串的顺序

⽅法⼀(时间复杂度O(n^2))

1.定义状态:

dp[i]表⽰以第i位为结尾的最⻓递增⼦序列. 2.状态转移:

我们可以在所有以a[j](a[j]<a[i])为结尾的⼦序列末尾加上a[i],构成以a[i]为结尾的

最⻓递增⼦序列,⻓度为以a[j]为结尾的最⻓递增⼦序列的⻓度+1. dp[i]=max{dp[j]+1}(a[j]<a[i])

3.⾃底向上求解

 

 

for (int i = 1;i <= n;i++) dp[i] = 1;

for (int i = 1;i <= n;i++){

    int j = i-1;

    while (j > 0){

        if (a[i] > a[j]){

            dp[i] = max(dp[i],dp[j]+1);

        }

        j--;

    }

}


⽅法⼆(时间复杂度O(nlogn))

1.定义状态:

f[j]表⽰⻓度为j的⼦序列的最后⼀项的最⼩值(规定当不存在⻓度为j的递增⼦序列时⻓度

2

f[j]表⽰⻓度为j的⼦序列的最后⼀项的最⼩值(规定当不存在⻓度为j的递增⼦序列时⻓度

f[j]=INF)

2.状态转移:

 

 

 

在处理到到第i位时, 如果a[i] > f[j],那么a[i]可以做⻓度为j+1位的组序列的的最后⼀项, 

 

 

f[j+1]=min{f[j+1],a[i]}


f[1]<f[2]<f[3]...<f[j]<a[i]<f[i+1]

j = max{j|f[j]>a[i]}(⼆分查找)

f[j+1] = a[i]

ans = max(ans,j+1)



⼆分的时间复杂度为O(logn),总的时间复杂度O(nlogn)

3.⾃底向上求解:

(略)

最⻓公共⼦序列问题(LCS)

1.定义状态:dp[i][j]表⽰a串的前i位,和b串的前j位的最⻓公共⼦序列⻓度。

2.状态转移:

(1)当a[i] == b[j]时,dp[i][j] = dp[i-1][j-1] + 1

(2)当a[i] != b[j]时,dp[i][j] = max{dp[i-1][j],dp[i][j-1]}

3.⾃底向上求解:

 

for (int i = 0;i <= lena;i++) dp[i][0] = 0;

for (int i = 0;i <= lenb;i++)dp[0][i] = 0;

for (int i= 1;i <= lena;i++){

    for (int j = 1;j <= lenb;j++){

        if (a[i] == b[j]){

            dpi][j] = dp[i-1][j-1] + 1;

        }

        else{

            dp[i][j] = max(dp[i-1][j],dp[i][j-1]);

        }

}

或者写成

 

 

for (int i = 0;i <= lena;i++) dp[i][0] = 0;

for (int i = 0;i <= lenb;i++) dp[0][i] = 0;

for (int i= 1;i <= lena;i++){

    for (int j = 1;j <= lenb;j++){

        dp[i][j]=Max(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]+(a[i]==b[j]?1:0));

    }   

}

 


最⻓公共⼦串

1.定义状态:dp[i][j]表⽰以a串第i位和b串的第j位结尾的最⻓公共⼦串⻓度。

2.状态转移:

(1)当a[i] == b[j]时,dp[i][j] = dp[i-1][j-1] + 1

(2)当a[i] != b[j]时,dp[i][j] = 0

3.⾃底向上求解:
 

for (int i = 0;i <= lena;i++) dp[i][0] = 0;

for (int i = 0;i <= lenb;i++) dp[0][i] = 0;

for(int i= 1;i <= lena;i++){

    for (int j = 1;j <= lenb;j++){

        if (a[i] == b[j]){

            dp[i][j] = dp + 1;

        }

        else{

            dp[i][j] = 0;

        }

    }

}


<a[i])为结尾的⼦序列末尾加上a[i],构成以a[i]为结尾的 最⻓递增⼦序列,⻓度为以a[j]为结尾的最⻓递增⼦序列的⻓度+1.="" dp[i]="max{dp[j]+1}(a[j]<f[2]<f[3]...<f[j]<a[i]</f[2]<f[3]...<f[j]<a[i]</a[i])为结尾的⼦序列末尾加上a[i],构成以a[i]为结尾的>

 

### 动态规划算法及其 dp 数组的使用方法 动态规划(Dynamic Programming, DP)是一种通过将复杂问题分解为更小的子问题来解决优化问题的算法技术。它通常用于求解具有重叠子问题和最优子结构性质的问题。在动态规划中,`dp` 数组是一个关键的数据结构,用于存储子问题的解,避免重复计算。 #### 1. 动态规划的核心思想 动态规划的核心思想是将一个问题拆分为多个子问题,并通过递推关系从子问题的解逐步构建出原问题的解。这种思想可以显著减少计算量,提高算法效率[^1]。 #### 2. `dp` 数组的作用 `dp` 数组是用来存储子问题解的数组或矩阵。它的每个元素代表某个子问题的解。例如,在划分数组问题中,`dp[i][rest]` 表示在前 `i` 个元素中选择若干个数使得它们的累加和最接近但不超过 `rest` 的最大值[^1]。 #### 3. 动态规划的基本步骤 - **定义状态**:确定 `dp` 数组的含义,明确每个元素表示什么。 - **状态转移方程**:根据问题的性质,设计从一个状态转移到另一个状态的规则。 - **初始化**:设置 `dp` 数组的初始值,通常是边界条件。 - **遍历顺序**:按照正确的顺序填充 `dp` 数阵列,确保每个状态都可以从已经计算过的状态中得出。 - **返回结果**:从 `dp` 数组中提取最终答案。 #### 4. 示例代码解析 以下是一个经典动态规划问题——计算不同路径的数量的代码解析: ```go func uniquePaths(m int, n int) int { dp := make([][]int, m) for k := range dp { dp[k] = make([]int, n) } for i := 0; i < m; i++ { dp[i][0] = 1 // 初始化第一列 } for j := 0; j < n; j++ { dp[0][j] = 1 // 初始化第一行 } for i := 1; i < m; i++ { for j := 1; j < n; j++ { dp[i][j] = dp[i-1][j] + dp[i][j-1] // 状态转移方程 } } return dp[m-1][n-1] // 返回最终结果 } ``` 在这个例子中,`dp[i][j]` 表示从左上角到 `(i, j)` 的不同路径数量。通过状态转移方程 `dp[i][j] = dp[i-1][j] + dp[i][j-1]`,我们可以从已知的状态推导出未知的状态[^3]。 #### 5. `dp` 函数与 `memo` 数组的关系 除了使用 `dp` 数组外,动态规划还可以通过递归结合记忆化搜索实现。在这种情况下,`memo` 数组用于存储递归过程中计算出的子问题解,避免重复计算。`memo` 数组的更新通常发生在递归函数的最后一步,确保每个子问题的解都是准确且完整的[^2]。 #### 6. 滚动数组优化 在一些动态规划问题中,`dp` 数组的空间复杂度可以通过滚动数组技术进行优化。例如,在二维 `dp` 数组中,如果状态转移只依赖于前一行的数据,则可以将二维数组压缩为一维数组,从而节省空间[^3]。 ### 总结 动态规划是一种强大的算法技术,其核心在于利用 `dp` 数组或 `memo` 数组存储子问题的解,避免重复计算。通过合理定义状态、设计状态转移方程以及初始化 `dp` 数组,可以高效地解决许多复杂的优化问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值