赞
踩
1.dp数组的以及下标的含义
2.递推公式
3.dp数组如何初始化
4.遍历顺序
5.打印dp数组。
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
输入:nums = [0,1,0,3,2,3]
输出:4
输入:nums = [7,7,7,7,7,7,7]
输出:1
使用动态规划的方法,首先,创建一个dp数组,初始值为1,长度为nums的长度。这里将每个数组元素的初始值设为1,是当一个子序列中只有一个元素时,也就是这个元素本身,所以长度设为1。
然后我们分别设置2个指针进行遍历这个数组,设置left、right,让right指针从第0个元素进行遍历这个数组,让left也从第0个元素进行遍历元素且left<right
,当left等于right时,让left=0从第0个元素进行遍历,right++.
当nums[left]<nums[right]
时,我们比较dp[left]+1和dp[right]的大小,若dp[left]+1>dp[right]
,我们就让dp[right]=dp[left]+1
,否则,就让dp[right]=dp[right]
。
最后我们返回dp数组的最大值即可。
时间复杂度:O(n的平方),n为nums的长度,动态规划的状态数为n,所以时间复杂度为O(n的平方)。
空间复杂度:O(n),n为使用的dp数组的长度。
class Solution { public: int lengthOfLIS(vector<int>& nums) { int n=nums.size(); if(n==0) return 0; int maxes=1; vector<int> dp(n,1); for(int i=0;i<n;i++) { for(int j=0;j<i;j++){ if(nums[j]<nums[i]) { dp[i]=max(dp[i],dp[j]+1); } } maxes=max(maxes,dp[i]); } return maxes; } };
设置dp数组,用于保存从第一层到第n层的种数。则dp[n]=dp[n-1]+dp[n-2],从第三层开始,初始化dp[1]=1,dp[2]=2.
class Solution {
public:
int climbStairs(int n) {
if(n<=1)
return n;
vector<int> dp(n+1);
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
创建dp数组,用于保存从第0层或第1层到第n层所需要的花费。dp[i]=min(dp[i-1],dp[i-2])+cost[i],为第i层的花费为第i-1层与第i-2层的最小值加上第i层的花费。到达最后一层时,认为不用花费精力,所以最后返回的是第n-1层或n-2层的最小值。
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size());
dp[0]=cost[0];
dp[1]=cost[1];
for(int i=2;i<cost.size();i++)
dp[i]=min(dp[i-1],dp[i-2])+cost[i];
return min(dp[cost.size()-1],dp[cost.size()-2]);
}
};
1.dp数组的含义:
dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
2.递推公式:
想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。
3.dp数组的初始化
如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。
4.确定遍历顺序
这里要看一下递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。
时间复杂度:O(mn)。
空间复杂度:O(mn),
class Solution { public: int uniquePaths(int m, int n) { vector<vector<int>> dp(m,vector<int>(n,0)); for(int i=0;i<m;i++) dp[i][0]=1; for(int j=0;j<n;j++) dp[0][j]=1; for(int i=1;i<m;i++) for(int j=1;j<n;j++) { dp[i][j]=dp[i-1][j]+dp[i][j-1]; } return dp[m-1][n-1]; } };
解题思路:
1.初始化dp数组
当增加了障碍物后,在初始化数组时,我们对dp[i][0],dp[0][j]元素初始化为1时,因为有障碍物,我们对障碍物前的元素初始化为1,对障碍物后的元素不进行初始化。
2.递推公式
当obstacleGrid[i][j]为1时,我们停止对dp[i][j]的点进行递推遍历。
3.确定遍历顺序
这里要看一下递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。
代码:
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { int m=obstacleGrid.size(); int n=obstacleGrid[0].size(); vector<vector<int>> dp(m,vector<int>(n,0)); for(int i=0;i<m&&obstacleGrid[i][0]==0;i++) dp[i][0]=1; for(int j=0;j<n&&obstacleGrid[0][j]==0;j++) dp[0][j]=1; for(int i=1;i<m;i++) for(int j=1;j<n;j++){ if(obstacleGrid[i][j]==1) continue; dp[i][j]= dp[i-1][j] +dp[i][j-1]; } return dp[m-1][n-1]; } };
确定dp数组(dp table)以及下标的含义
dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。
dp[i]的定义讲贯彻整个解题过程,下面哪一步想不懂了,就想想dp[i]究竟表示的是啥!
确定递推公式
可以想 dp[i]最大乘积是怎么得到的呢?
其实可以从1遍历j,然后有两种渠道得到dp[i].
一个是j * (i - j) 直接相乘。
一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
这里我只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,
确定遍历顺序,先来看看递归公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
dp[i] 是依靠 dp[i - j]的状态,所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。
枚举j的时候,是从1开始的。i是从3开始,这样dp[i - j]就是dp[2]正好可以通过我们初始化的数值求出来。
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+1);
dp[2]=1;
for(int i=3;i<=n;i++)
for(int j=1;j<i-1;j++)
dp[i]=max(dp[i],max(dp[i-j]*j,(i-j)*j));
return dp[n];
}
};
思路:
这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。
确定dp数组以及下标的含义
01背包中,dp[j] 表示: 容量为j的背包,所背的物品价值可以最大为dp[j]。
套到本题,dp[j]表示 背包总容量是j,最大可以凑成j的子集总和为dp[j]。
确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
dp数组如何初始化
在01背包,一维dp如何初始化,已经讲过,
从dp[j]的定义来看,首先dp[0]一定是0。
如果如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。
本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
代码:
class Solution { public: bool canPartition(vector<int>& nums) { int sums=0; for(int i=0;i<nums.size();i++) { sums+=nums[i]; } int target=sums/2; if(sums%2==1) return false; vector<int> dp(10001,0); for(int i=0;i<nums.size();i++) for(int j=target;j>=nums[i];j--) dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]); if(dp[target]==target) return true; else return false; } };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。