动态规划(Dynamic Programming, DP)是一种以求解最优化问题为目标的算法思想,它是将一个大问题划分成若干个子问题,并逐个求解子问题的过程。可以看做是递推式的扩展,其基础思想就是将问题拆分为更小的子问题来求解,然后将解组合起来得到原问题的解。
动态规划和递归求解都是分治法的一种变形,它们之间的区别在于动态规划保存了过程中的一些状态,避免了重复计算,提高了效率。
动态规划的特点
动态规划有三个重要的特点:
1. 最优子结构
(理解为氪分为几个子函数一样的分而治之)
最优子结构是指问题的最优解可以通过子问题的最优解推导出来。如果问题不具备最优子结构,则不能使用动态规划求解,需要使用其他算法思想。具有最优子结构的问题有很多,例如背包问题、图的最短路径问题等。
2. 重叠子问题
(理解为氪用一个最终,最简递推式来进行反复求最佳值)
重叠子问题是指在递归过程中,子问题被多次重复计算。针对这种情况,动态规划算法通过记忆化搜索或者 Bottom-up 的方式来避免重复计算,提高效率。
Bottom-up :是一种动态规划算法的实现方式,也称为自底向上法。它与 Top-down(自顶向下法,即记忆化搜索)是互补的思路,主要是针对具有重叠子问题和最优子结构特点的问题,通过递推的方式解决原问题。
3. 状态转移方程
状态转移方程是使用动态规划求解问题的核心。状态转移方程描述了子问题与原问题之间的关系,是从子问题的最优解推导出原问题的最优解的公式。通常需要分析问题的特点,设计出合适的状态表示方法,然后根据状态之间的转移关系得出状态转移方程,并用递推或者迭代的方式实现。
例如:
最长递增子序列问题的状态转移方程如下:
dp[i] = max{dp[j]+1},其中 0≤j<i 且 nums[j]<nums[i]
其中,nums
表示原始序列,dp[i]
表示以第 i
个元素为结尾的最长递增子序列的长度。这个方程的含义是,在前 i
个元素中,选择一个比第 i
个元素小的元素 j
,然后将第 i
个元素接在第 j
个元素后面,形成一个新的递增子序列。如果存在多个这样的 j
,则选择其中 dp[j]
最大的那一个。最终,dp[i]
的值为所有可行的 dp[j]+1
中的最大值。
动态规划的解题步骤
动态规划一般包括三个步骤:设计状态、找到状态转移方程、设置边界条件
例子1:最长递增子序列问题
最长递增子序列问题是一个经典的动态规划问题,其目标是在一个无序的序列中,求解最长的递增子序列。例如序列 [1, 3, 2, 4, 5, 7, 6] 的最长递增子序列是 [1, 2, 4, 5, 7],长度为 5。
要解决这个问题,需要分析其特点,确定状态和状态转移方程。
1. 设计状态
设 dp[i] 表示以第 i 个元素为结尾的最长递增子序列的长度。
2. 找到状态转移方程
对于第 i 个元素,如果前面存在比它小的元素 j,那么它可以接在第 j 个元素的后面形成一个新的递增子序列,此时 dp[i] 应该等于 dp[j]+1。如果不存在这样的 j,则 dp[i]=1,即以第 i 个元素结尾的最长递增子序列只包含第 i 个元素本身。
根据上述分析,可以得到如下状态转移方程:
int longest(int nums[], int n)
{
int dp[n];
for (int i = 0; i < n; i++) {
dp[i] = 1; // 初始时,所有元素的最长递增子序列长度均为 1
}
int max = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = (dp[i] > dp[j]+1) ? dp[i] : dp[j]+1;
}
}
max = (max > dp[i]) ? max : dp[i];
}
return max;
}
难度中等
6046
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]输出:6解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]输出:1
示例 3:
输入:nums = [5,4,-1,7,8]输出:23
提示:
- 1
5
- -10
4
4
今天看到这个题,只能想到用双重循环进行一个遍历,显然是时间超限的。题解中提到的dp一下就清晰了很多,另开数组记录最优然后一次遍历即可解决问题。
int maxSubArray(int* nums, int numsSize)
{
int max=0,temp=0;
for(int i = 1 ; i < numsSize ; i++)
{
temp = 0;
for(int j = i ; j > 0 ; j --)
{
temp += num[j];
max = max > temp ? max : temp ;
}
}
return max;
}
int maxSubArray(int* nums, int numsSize)
{
//用dp表示到每个位置时最大的子数组的值
int dp[numsSize];
//初始化
dp[0] = nums[0];
int num = dp[0];
for(int i = 1 ; i < numsSize ; i++)
{
//dp[i]要么与前一个数组结合,形成新的连续子数组。要么自己为一个最大和子数组
dp[i] = nums[i] > (dp[i-1] + nums[i]) ? num[i] : dp[i-1] + num[i];
num=num>dp[i]?num:dp[i];
}
return num;
}