动态规划刷题总结

关于动态规划的步骤:

1.dp数组的以及下标的含义
2.递推公式
3.dp数组如何初始化
4.遍历顺序
5.打印dp数组。

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入: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;
    }
};

70. 爬楼梯

设置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];
    }
};

746. 使用最小花费爬楼梯

创建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]);


    }
};

62. 不同路径

思路:

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];
    }
};

63. 不同路径 II

解题思路:
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];    
    }
};

343. 整数拆分

确定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];
    }
};

416. 分割等和子集

思路:
这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

那么只要找到集合里能够出现 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;
    }
};
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值