动态规划的思想是算法中必须要掌握的一种算法
解题的思路如下:
- 穷举分析
- 分析找规律,拆分子问题
- 确定边界
- 确定最优子结构
- 写出状态转移方程
使用到的地方有:背包问题,KMP等问题
先以实列来进行说明的。
例题:给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
如下:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4
输入:nums = [0,1,0,3,2,3]
输出:4
思路解析如下:
1. 穷举分析
动态规划的核心思想包括拆分子问题,记住过往,减少重复计算。
nums=[10,9,2,5,3,7,101,18]
- 当nums只有一个元素10时,最长递增子序列是[10],长度是1.
- 当nums需要加入一个元素9时,最长递增子序列是[10]或者[9],长度是1。
- 当nums再加入一个元素2时,最长递增子序列是[10]或者[9]或者[2],长度是1。
- 当nums再加入一个元素5时,最长递增子序列是[2,5],长度是2。
- 当nums再加入一个元素3时,最长递增子序列是[2,5]或者[2,3],长度是2。
- 当nums再加入一个元素7时,,最长递增子序列是[2,5,7]或者[2,3,7],长度是3。
- 当nums再加入一个元素101时,最长递增子序列是[2,5,7,101]或者[2,3,7,101],长度是4。
- 当nums再加入一个元素18时,最长递增子序列是[2,5,7,101]或者[2,3,7,101]或者[2,5,7,18]或者[2,3,7,18],长度是4。
2. 分析规律,找出子问题
可以看出,最长递增子序列是与num[i]有关的,或者是以nums[i-1]有关的
原问题数组nums[i]的最长递增子序列 = 子问题数组nums[i-1]的最长递增子序列/nums[i]结尾的最长递增子序列
nums[i]的最长递增子序列,不就是从以数组num[i]每个元素结尾的最长子序列集合,取元素最多(也就是长度最长)那个嘛,所以原问题,我们转化成求出以数组nums每个元素结尾的最长子序列集合,再取最大值嘛。哈哈,想到这,我们就可以用dp[i]表示以num[i]这个数结尾的最长递增子序列的长度啦,然后再来看看其中的规律:
nums[i]结尾的自增子序列,只要找到比nums[i]小的子序列,加上nums[i] 就可以啦。显然,可能形成多种新的子序列,我们选最长那个,就是dp[i]的值啦
-
nums[3]=5,以5结尾的最长子序列就是[2,5],因为从数组下标0到3遍历,只找到了子序列[2]比5小,所以就是[2]+[5]啦,即dp[4]=2
-
nums[4]=3,以3结尾的最长子序列就是[2,3],因为从数组下标0到4遍历,只找到了子序列[2]比3小,所以就是[2]+[3]啦,即dp[4]=2
-
nums[5]=7,以7结尾的最长子序列就是[2,5,7]和[2,3,7],因为从数组下标0到5遍历,找到2,5和3都比7小,所以就有[2,7],[5,7],[3,7],[2,5,7]和[2,3,7]这些子序列,最长子序列就是[2,5,7]和[2,3,7],它俩不就是以5结尾和3结尾的最长递增子序列+[7]来的嘛!所以,dp[5]=3
=dp[3]+1=dp[4]+1。如果存在j属于区间[0,i-1],并且num[i]>num[j]的话,则有:dp(i) =max(dp(j))+1。
3. 确定边界
当nums数组只有一个元素时,最长递增子序列的长度dp(1)=1,当nums数组有两个元素时,dp(2) =2或者1, 因此边界就是dp(1)=1。
4 确定最优子结构
dp(i) =max(dp(j))+1,存在j属于区间[0,i-1],并且num[i]>num[j]。
max(dp(j)) 就是最优子结构。
5 写出状态转移方程
实现的代码如下所示的:
public int lengthOfls(int []nums){
if(nums.length==0)return 0;
int []dp=new int[nums.length];
dp[0]=1;//边界情况
int max=1;
for (int i = 1 ; i < nums.length; i++) {
dp[i]=1;
//下标是从0遍历到i、
for (int j = 0; j < i; j++) {
if(nums[j]<nums[i]){
//如果nums[j]小于nums[i]的话,就会存在的是多种的组合的
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
max=Math.max(dp[i],max);
}
return max;
}