简介
动态规划(Dynamic Programming)也叫DP算法,通常用于求解具有某种最优性质
的问题。其基本思想就是将一个复杂的问题分解若干个简单的子问题,而这些子问题相互之间不是独立
的,每个子问题仅仅只解决一次。我们可以使用一块额外的空间(例如数组)来记录所有已解的子问题的答案,不管这个子问题以后是否会被用到,都会记录到这个空间中。
基本概念
首先我们来说明以下几个概念,
状态:状态可以理解为原问题和简单的子问题的解。
这时,我们需要根据子问题的状态的推导出原问题的状态,也就是推导出状态转移方程
。
状态转移方程:从i-1状态转移到i状态的状态转移规律,用一个公式展示出来。
例如DP[i] = DP[i-1] +1
;
解题步骤
动态规划的问题的基本步骤如下:
1,动态规划顾名思义,就是变化的状态的规划。所以我们得首先定义一个状态,即定义最优解的状态。
2,确定状态转移方程
3,设置初始状态(设置边界值)
适用条件
任何思想和算法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。
最优化原理
- 最优化原理:通过求解子问题的最优解,可以获得原问题的最优解
- 无后效性:某阶段的状态一旦确定,此后的过程的演变不在受此前各状态及决策的影响(未来与过去无关)。在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的。
例题分析
我们选择leetcode上的一道题来分析,来帮助大家更加深入的了解动态规划算法的原理以及使用。
题目概述
最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
题目分析
根据动态规划的解法,我们解题步骤分为以下几步:
定义状态
假设dp(i)是以nums[i]为结尾的最大子序和(nums为整个数组)。那么数组nums(长度为n)的最大子序列为dp(1)到dp(n)的最大值,那么问题就转化为求出dp(i)。
状态转移方程
dp(i-1)表示以nums[i-1]的值为结尾的最大子序和,那么我们得出以下的公式,当dp(i-1) <= 0 时,dp(i) = nums[i],当dp(i-1) >0时,dp(i) = dp(i-1) +nums[i];
边界值
//没有dp(-1),所以先给dp(0)赋值,i从1开始
dp(0) = nums[0];
代码
if (nums == null || nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = nums[0];
int max = dp[0];
for (int i = 0; i < dp.length; i++) {
if (dp[i - 1] <= 0) {
dp[i] = nums[i];
} else {
dp[i] = dp[i - 1] + nums[i];
}
max = Math.max(dp[i], max);
}
return max;
优化
上面的代码我们使用一个额外的数组来保存dp(i),但是我们通过状态转移方程可以发现,dp(i)的状态只跟dp(i-1)的状态有关,所以我们只需使用一个额外的变量来保存dp(i-1)即可。
int pre = nums[0];
int max = nums[0];
for (int i=1;i < nums.length; i++) {
pre = Math.max(pre+nums[i],nums[i]);
max = Math.max(pre,max);
}
return max;