算法(1) 动态规划

前言:

哈哈哈哈哈哈哈哈哈,今天开个新坑,以后不定期更新算法,正好动态规划我以前有学习过一些,所以今天给大家更新动态规划这个算法:

那么我们要讲的动态规划问题有以下几类:

①:斐波那契模型:最简单的一类,作为引入

②:路径问题

③:多状态dp问题

④:子数组,子串系列

⑤:子序列问题:

⑥:回文串问题

⑦:双数组dp

⑧:01背包

⑨:完全背包

⑩:二维费用的背包问题

当然,我们也会给出相应的做题方法.......

废话少说,直接开始吧:

正文:

一般做题方法:

这种问题呢,我们分为五步:

①:状态表示:

即dp表中的值所代表的含义

从哪里来呢?

1.题目要求

2.经验+题目要求

3.重复出现的子问题

②:状态转移方程:

即 dpi 如何计算:

当然计算的时候要用到前面的数据,比如说dpi-1

③:初始化

为了保证填表的时候不会越界

④:填表顺序:

为了保证当我们计算当前状态的时候,所需的状态已经计算过了

⑤:返回值

题目要求+状态表示

那么,接下来正式开始题目:

①:斐波那契模型

1:第N个泰波那契数(leetcode 1137)

那么遇到这么一道题,我们该如何思考呢?

①:状态表示:

首先,我们要求的是第n个泰波那契数的值,所以我们根据题目要求,就知道了我们最后需要的是值,所以我们的状态表示:

第n个泰波那契数的值

②:状态转移方程:

题目已经给出了:

dpi=dpi-1+dpi-2+dpi-3

③:初始化:

题目已经给出前三个数,我们直接填好即可;

④:填表顺序:

直接从左向右填表即可

⑤:返回值:

dpn

所以我们进行编码:

复制代码
class Solution {
public:
    int tribonacci(int n) {
        //先来个dp表,首先dp表有n个数
        vector<int> dp(38);
        dp[0]=0;
        dp[1]=1;
        dp[2]=1;
        for(int i=3;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
        }
        return dp[n];
    }
};

运行一下试试呢?

OK,结果如我所料:那如果我想优化一下呢?

3我发现,我似乎一共只需要四个连续的空间就可以完成这道题了,通过连续的三个空间算出第四个数,然后后面的数再把自己的值给前面的数,那么我们只需四个空间一直滚动下去,不就可以完成这道题么,这就叫做滚动数组

复制代码
class Solution {
public:
    int tribonacci(int n) {
        if(n==0)
        return 0;
        if(n==1 || n==2)
        return 1;
        int a=0;
        int b=1;
        int c=1;
        int d=0;
        for(int i=3;i<=n;i++)
        {
            d=a+b+c;
            a=b;
            b=c;
            c=d;
        }
        return d;
    }
};

因为这是第一道题,所以我会给大家讲的啰嗦一些,接下来我们挑重点讲:

2.使用最小花费爬楼梯(746)

这道题呢,显然,要记录最小花费,是一个和,所以我们的状态表示就是:

到达第i个位置,花了多少钱

而状态转移方程,是需要前面两个状态,dpi-1+costi-1,dpi-2+costi-2的最小值

初始化前面两位初始为0即可,填表顺序从左向右,编码如下:

复制代码
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size());
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<cost.size();i++)
        {
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return min(dp[cost.size()-1]+cost[cost.size()-1],dp[cost.size()-2]+cost[cost.size()-2]);
    }
};

3.解码方法(91)

OK,现在开始上强度咯,相信大家看到这道题有点懵,所以我要画图了

ok,相信这么画一下大家的思路就清楚了,所以我们开始编码

复制代码
class Solution {
public:
    int numDecodings(string s) {
        vector<int> dp(s.size());
        int e=s[0]-'0';
        if(1<=e && e<=9)
        {
            dp[0]=1;
        }
        if(s.size()==1)
        {
            return dp[0];
        }
        e=s[1]-'0';
        int count=0;
        if(1<=e && e<=9)
        {
            count++;
        }
        int dou=(s[0]-'0')*10+e;
        if(dou>=10 && dou<=26)
        {
            count++;
        }
        dp[1]=count;
        for(int i=2;i<s.size();i++)
        {
            count=0;
            e=s[i]-'0';
            if(e>=1 && e<=9)
            {
                count+=dp[i-1];
            }
            dou=(s[i-1]-'0')*10+e;
           if(dou>=10 && dou<=26)
            {
            count+=dp[i-2];
            }
            dp[i]=count;
        }
        for(auto e:dp)
        {
            if(e==0)
            return 0;
        }
        return dp[s.size()-1];
    }
};

同时呢,通过这道题,希望大家可以记住一个状态表示确立的经验:

以i位置为结尾,.......

毕竟嘛,我们的经验也是确定状态表示的关键依据之一,做得多了才能积累经验,越来越顺手

②:路径问题

1.不同路径(62)

那么看到这道题呢,它是一个二维的数组,我们想要解决它,我们可以凭借我们的经验,在某个位置,当前有多少种走法?这不就是我们的状态表示么

所以:我们就需要创建一个二维的dp表;

那么,状态转移方程呢?

显然,我们在一个位置的时候,只能向下或者向右走,所以我们上一个状态是上面一格或者左边一格,二者相加就得到了状态转移方程

dpij=dpi-1j+dpij-1

初始化:

这题的初始化是我最想说的

我们在初始化的时候,vector默认会给你初始化成0的,这个无需考虑,但是

我们在填表第一行或者第一列的时候,那么就会越界,所以我们可以多创建一行,再多创建一列,来作为缓冲,而整个表初始都是0,不会对我们的填表造成错误,同时我们把第一行第二个元素或者第一列第二个元素设置为1,以供dp11初始化

填表:

从上往下,从左往右就可以

返回值:

我们只需返回最后一个元素即可,即dpmn

接下来进行编码:

复制代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        if(m==1 && n==1)
        {
            return 1;
        }
        dp[0][1]=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][n];
    }
};

2.不同路径II(63)

至于这道题嘛,不就是上一道题加了一个有障碍物么,那么就相当于新增了一个判断条件,如果当前所处位置是障碍物,那我为0不就好了,这样它之后的位置,相当于忽略了它。

但是注意,由于我们在创建dp表的时候,多创建了一行一列,所以映射到原来的题目给出的表一定不要忘了给下标分别减一;

复制代码
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m=obstacleGrid.size();
        int n=obstacleGrid[0].size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        if(m==n && m==0)
        {
            return 1;
        }
        dp[0][1]=1;
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(obstacleGrid[i-1][j-1]!=1)
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m][n];

    }
};

3、珠宝的最高价值(166)

这道题也很简单,与上面两道题的差别也就是状态表示了,这道题的状态表示是,走到了i.j,自己身上有多少钱的珠宝,我们直接编码了

复制代码
class Solution {
public:
    int jewelleryValue(vector<vector<int>>& frame) {
        int m=frame.size();
        int n=frame[0].size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        if(m==1 && n==1)
        {
            return frame[0][0];
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];
            }
        }
        return dp[m][n];
    }
};

4.下降路径最小和(931)

看着唬人,其实不就也是从上往下,可以找下面的三个与自己相邻的元素,直到最底下求最小值么,非常简单,只有初始化的时候需要考虑一下,因为我们找的是下面的三个与自己相邻的元素,那么最左边和最右边的,我们就会越界咯,所以我们左右各增加一列,同是我们在填第一行dp表的时候,需要上层的一行,所以我们还要在最上面多加一行。

上面多加的那一行呢,为了不影响结果,我们设为0;

至于左右新增的那两列,因为我们求的是三者的最小值,所以为了不影响结果,我们设置为INT_MAX;

ok,别的地方就没有什么要强调的了,直接编码

复制代码
class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int row=matrix.size();
        int col=matrix[0].size();
        vector<vector<int>> dp(row+1,vector<int>(col+2));
        for(int i=0;i<col+2;i++)
        {
            dp[0][i]=0;
        }
        for(int j=0;j<=row;j++)
        {
            dp[j][0]=INT_MAX;
            dp[j][col+1]=INT_MAX;
        }
        for(int i=1;i<=row;i++)
        {
            for(int j=1;j<=col;j++)
            {
                dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i-1][j+1]))+matrix[i-1][j-1];
            }
        }
        int min=INT_MAX;
        for(int j=1;j<=row;j++)
        {
            if(dp[row][j]<min)
            {
                min=dp[row][j];
            }
        }
        return min;
    }
};

5.最小路径和(64)

非常简单,需要注意的只有,由于我要最小值,所以我们把上面和左边新增加的那一行与那一列都设为INT_MAX。而我们第一个数,在填的时候,需要dp01和dp10,所以我们把这俩初始化为0,别的没什么好说的了

直接编码:

复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int row=grid.size();
        int col=grid[0].size();
        vector<vector<int>> dp(row+1,vector<int>(col+1,INT_MAX));
        dp[0][1]=0;
        dp[1][0]=0;
        for(int i=1;i<=row;i++)
        {
            for(int j=1;j<=col;j++)
            {
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
            }
        }
        return dp[row][col];
    }
};

6.地下城游戏(174)

本系列第一道困难题,但其实一点也不困难,别被他吓到;

上面叽里咕噜说一大堆,不还是要我求什么最小路径么;

但是我们注意,这道题一定要倒着求,别正着做;

如果我正着做,那我第一步如果就扣血呢?那我直接死了啊,所以此时就必然要有个初始血量吧,这是我们最终要求的啊?!然后如果我在路上死了呢?那我此刻就得对我的初始血量继续动刀了,太麻烦了

所以我们倒着做的话,从右下角的格子出发,我们首先要确定状态表示:

那么不就是我们走到i,j所需要的最小血量么?

然后状态转移方程呢?dpij=min(dpij+1,dpi+1j)-dungeonij

并且,如果我们此时需要的最小血量如果为负数的话,注意我如果血量为负数会直接暴毙,所以如果我血量为负,我直接设为1

切记,我们此时初始化,是在右边和下面增加了一列一行

而我们要的是最小值,所以初始值设为INT_MAX,而我初始化的时候,需要dpmn-1,dpm-1n,所以我们把它们两个设为1

最后呢,我们要返回的,自然就是初始位置dp00

所以直接编码:

复制代码
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int row=dungeon.size();
        int col=dungeon[0].size();
        vector<vector<int>> dp(row+1,vector<int>(col+1,INT_MAX));
        dp[row-1][col]=1;
        dp[row][col-1]=1;
        for(int i=row-1;i>=0;i--)
        {
            for(int j=col-1;j>=0;j--)
            {
                dp[i][j]=min(dp[i+1][j],dp[i][j+1])-dungeon[i][j];
                //为什么是min?
                //因为我要求的是可以救到公主的最小的
                dp[i][j]=max(dp[i][j],1);
                //若需要生命值为负数,那么置为一
            }
        }
        return dp[0][0];

    }
};

③.多状态dp

1.打家劫舍II(213)

这个偷东西嘛....讲究还挺多,不能同时连着偷,而且村子还是首尾相连的,看上去也是非常的麻烦啊,但是我们想想,既然他相连,我们可不可以通过分类讨论来把它转化为不相连?

还是画个图吧

如果我偷第一家,那么我第二家和最后一家就不许我偷了

如果我不偷第一家,那么我可以偷第二家和最后一家

所以我们通过偷不偷第一家的分类讨论,得到了两个范围!

一个是2------n-2,一个是1-----n-1

那不就变成两个普通的打家劫舍了么,所以我们可以写个函数

咦?那普通打家劫舍怎么写呀?没错,我们要分类当前是否要偷,而一个dp表似乎满足不了我们了

所以,我们来两个dp表

一个dp表,我们记录偷了这家以后的此时拥有的最大财产

一个dp表,我们记录不偷这家,此时我们所拥有的最大财产

那我们的状态转移方程呢?画个图吧

至于填表顺序,自然就是从左往右,两个表一起填咯

初始化的话,没什么需要在意的,不管了

返回值的话,也没什么吧?直接编码了

复制代码
class Solution {
public:
int steal(vector<int>& nums,int left,int right)
{
    if(left>right)
    {
        return 0;
    }
    int n=nums.size();
    vector<int> dpf(n);
    vector<int> dpg(n);
    dpf[left]=nums[left];
    for(int i=left+1;i<=right;i++)
    {
        dpf[i]=dpg[i-1]+nums[i];
        dpg[i]=max(dpf[i-1],dpg[i-1]);
    }
    return max(dpf[right],dpg[right]);
}
    int rob(vector<int>& nums) {
        int n=nums.size();
        //状态
        //以i为终点,能偷到的最多金额
        //dpf,是偷第i家
        //dpg,不偷这家
        return max(steal(nums,2,n-2)+nums[0],steal(nums,1,n-1));
    }
};

2.买卖股票的最佳时间包含冷冻期(309)

老师赚钱枯燥就来看看这接下来的几道题,好好看,好好学,你也会成为下一个天才交易员

嗯.....这道题状态挺多呀,买入,可交易,冷冻期

所以我们需要三个dp表咯

我们来画个图

所以状态转移方程不是很好写么

既然我要最大的,那么:

dpi0=max(dpi-10.dpi-12-pi)

dpi1=max(dpi-11,dpi-12)

dpi2=dpi-10+pi

此时初始化无需考虑太多,只要注意dp00既然是买入,所以要减了当日股票价格

填表顺序自然就是从左向右,三个表一起填

返回值返回最后一天冷冻期和可交易期二者的最大值

复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        vector<vector<int>> dp(n,vector<int>(3));
        dp[0][0]-=prices[0];
        for(int i=1;i<n;i++)
        {
        dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
        dp[i][1]=max(dp[i-1][2],dp[i-1][1]);
        dp[i][2]=dp[i-1][0]+prices[i];
        }
        return max(dp[n-1][1],dp[n-1][2]);
    }
};

3.买卖股票的最佳时机含手续费(714)

那这道题不就更简单了?都没有冷冻状态了,所以少一个dp表,两个状态之间的关系一画就出来了

我们直接编码

复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n=prices.size();
        vector<vector<int>> dp(n,vector<int>(2));
        dp[0][0]-=prices[0];
        for(int i=1;i<n;i++)
        {
            dp[i][0]=max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]-fee);
        }
        return max(dp[n-1][0],dp[n-1][1]);
    }
};

4.买卖股票的最佳时机III(123)

就这还困难题,别逗泥梅笑了

首先这道题的大致方向,和前一道大差不差吧?

只是这里有一个新的要求,要求我只能最多完成两笔交易,也就是说,我要限制交易次数,怎么办?

很简单啊,那我记录下这是第几次交易不就是了?

那么怎么表示这是第几次交易呢?

我给dp表整成二维的不就好了?

dp1ij:在第i天,我处于买入状态,我是第j次交易了,此时我的最大利润

dp2ij: 在第i天,我处于卖出状态,此时我是第j次交易了,此时我的最大利润

至于初始化,我要求最大值,那么全部设成INT_MIN就是。

在这里我要再说一句,动态规划会把所有情况都考虑到的,所以我们要更加注重的是逻辑上,如果有想不通的地方,我们不妨考虑一下别的我们认为"没有用的状态"

复制代码
class Solution {
public:
    #define MIN 0x3f3f3f3f
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        vector<vector<int>> dp1(n,vector<int>(3,-MIN));
        //dp1是身上有股票没卖出去
        vector<vector<int>> dp2(n,vector<int>(3,-MIN));
        //dp2是身上没股票
        dp1[0][0]=-prices[0];
        dp2[0][0]=0;
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<3;j++)
            {
                dp1[i][j]=max(dp1[i-1][j],dp2[i-1][j]-prices[i]);
                dp2[i][j]=dp2[i-1][j];
                if(j-1>=0)
                {
                    dp2[i][j]=max(dp2[i-1][j],dp1[i-1][j-1]+prices[i]);
                }
            }
        }
        return max(dp2[n-1][0],max(dp2[n-1][1],dp2[n-1][2]));
    }
};

5.买卖股票的最佳时机IV(188)

虽然这是我们的最终boss了,但和我们上道题是不是差不多啊?我们把上道题稍微改一下,直接结束了

复制代码
0class Solution {
public:
    #define MIN 0x3f3f3f3f
    int maxProfit(int k, vector<int>& prices) {
        int n=prices.size();
        vector<vector<int>> buy(n,vector<int>(k+1,-MIN));
        vector<vector<int>> tradable(n,vector<int>(k+1,-MIN));
        buy[0][0]=-prices[0];
        //tradable[0][0]=0;
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<=k;j++)
            {
                buy[i][j]=max(buy[i-1][j],tradable[i-1][j]-prices[i]);
                tradable[i][j]=tradable[i-1][j];
                if(j>=1)
                {
                    tradable[i][j]=max(buy[i-1][j-1]+prices[i],tradable[i-1][j]);
                }
            }
        }
        int ans=-MIN;
        for(int j=0;j<=k;j++)
        {
            if(tradable[n-1][j]>ans)
            {
                ans=tradable[n-1][j];
            }
        }
        return ans;
    }
};

④.子数组,子串

1.最大子数组和(53)

这......难度是不是瞎标的,这还中等么

我直接从前往后遍历一遍加着,一直让和和当前数取最大值,谁大取谁不就行了

不讲不讲

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int ans=nums[0];
        int cur_num=nums[0];
        for(int i=1;i<nums.size();i++)
        {
            cur_num=max(nums[i],cur_num+nums[i]);
            ans=max(ans,cur_num);
        }
        return ans;
    }
};

2.环形子数组的最大和(918)

这题倒是有些说道,不过不也是和上道题差不多么?

无非就是有一个:可以取两边连续的,我们那不是可以看做中间挖去了一块么?

那么......我们如果一边要求一个最大的连续子数组,一边再求一个连续的最小子数组

注意:此时不考虑数组是环形的,我们就看做直的

,而我题目一旦给出,我的nums的和就确定了,那么我的和减去中间那块连续的最小子数组,不就是两边连续的最大子数组么?

那我们开始编码:

复制代码
class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int n=nums.size();
        vector<int> find_max(n+1);
        vector<int> find_min(n+1);
        int fmax=INT_MIN;
        int fmin=INT_MAX;
        for(int i=1;i<=n;i++)
        {
            find_max[i]=max(find_max[i-1]+nums[i-1],nums[i-1]);
            find_min[i]=min(find_min[i-1]+nums[i-1],nums[i-1]);
            fmax=max(fmax,find_max[i]);
            fmin=min(fmin,find_min[i]);
        }
        int sum=0;
        for(int i=0;i<n;i++)
        {
            sum+=nums[i];
        }
        return fmin==sum?fmax:max(fmax,sum-fmin);
    }
};

3.乘积最大子数组(152)

这道题值得注意的地方是,负数,如果我的绝对值是最大的,但是我有个负号,那我岂不是变成最小的数了?而后面我一旦再乘一个负数,那我又变成最大的。正数同理:

所以在这道题中,我们正数和负数的最大值均要记录下来。

复制代码
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n=nums.size();
        vector<int> max_product(n+1);
        vector<int> min_product(n+1);
        max_product[0]=1;
        min_product[0]=1;
        int ans=INT_MIN;
        for(int i=1;i<n+1;i++)
        {
            max_product[i]=max(nums[i-1],max(nums[i-1]*max_product[i-1],nums[i-1]*min_product[i-1]));
            min_product[i]=min(nums[i-1],min(nums[i-1]*max_product[i-1],nums[i-1]*min_product[i-1]));
            if(max_product[i]>ans)
            {
                ans=max_product[i];
            }
        }
        return ans;
    }
};

4.乘积位正数的最长子数组(1567)

这道题也简单吧?要求乘积为正数,那么不是还是要同时记录最长的正数和负数数组么?

只是这道题要多判断一下是否为0罢了

复制代码
class Solution {
public:
    int getMaxLen(vector<int>& nums) {
        int n=nums.size();
        vector<int> just(n+1);
        vector<int> lose(n+1);
        int ans=INT_MIN;
        for(int i=1;i<=n;i++)
        {
            if(nums[i-1]>0)
            {
                just[i]=just[i-1]+1;
                lose[i]=lose[i-1]==0?0:lose[i-1]+1;
            }
            else if(nums[i-1]<0)
            {
                just[i]=lose[i-1]==0?0:lose[i-1]+1;
                lose[i]=just[i-1]+1;
            }
            else{
                just[i]=0;
                lose[i]=0;
            }
            if(just[i]>ans)
            {
                ans=just[i];
            }

        }
        return ans;
    }
};

5.等差数列划分(413)

那么也非常简单咯?

复制代码
class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n);
        //以i位置为结尾,等差数列的个数
        for(int i=2;i<n;i++)
        {
            dp[i]=nums[i]-nums[i-1]==nums[i-1]-nums[i-2]?dp[i-1]+1:0;
        }
        int ans=0;
        for(int i=2;i<n;i++)
        {
            ans+=dp[i];
        }
        return ans;
    }
};

6.最长湍流子数组(978)

这道题有意思啊,我们来好好说说

你看他叽里咕噜说了一大堆,什么意思啊?

不就是:

那不就是长这样么?

所以求这个怎么办?

只要找到状态转移,一切都迎刃而解,我们发现,不管这图如何,最终只有两种可能:

最后趋势向上,最后趋势向下,所以我们的两张dp表:

dp1i:当我以i结束的时候,我最后一个元素向上趋势,此时我数组最大长度

dp2i:当我以i结束的时候,我最后一个元素向下趋势,此时我数组最大长度

嘶,这不就是我们的多状态dp么?

状态转移方程:

虽说比较潦草,但还是可以看出来变化关系的

那么接下来就是编码环节:

复制代码
class Solution {
public:
    int maxTurbulenceSize(vector<int>& arr) {
        int n=arr.size();
        vector<int> rise(n,1);
        vector<int> decline(n,1);
        int rise_max=INT_MIN;
        int decline_max=INT_MIN;
        if(n==1)
        {
            return 1;
        }
        for(int i=1;i<n;i++)
        {
            rise[i]=arr[i]>arr[i-1]?decline[i-1]+1:1;
            decline[i]=arr[i]<arr[i-1]?rise[i-1]+1:1;
            if(rise_max<rise[i])
            {
                rise_max=rise[i];
            }
            if(decline[i]>decline_max)
            {
                decline_max=decline[i];
            }
        }
        return decline_max>rise_max?decline_max:rise_max;
    }
};

7.环绕字符串中唯一的子字符串(467)

这道题啊,依旧经验发力即可,依旧是以第i个位置为结尾,我有多少个非空子串在base中出现

但是但是.......

比如说我是个bab

我的b呢,它算作以b为结尾的子串,算一个

我bab,其中包含了b,那么我最后其实,要把以各种字符为结尾的字符串加起来的,就会重复很多次,那我怎么办?

哈希表!

我们直接建立一个有26个pair的哈希表即可,若有最后一位重复,那么大的覆盖小的即可;

复制代码
class Solution {
public:
    int findSubstringInWraproundString(string s) {
        int n=s.size();
        vector<int> dp(n,1);
        for(int i=1;i<n;i++)
        {
            if(s[i]-s[i-1]==1 || (s[i]=='a' && s[i-1]=='z'))
            dp[i]=dp[i-1]+1;
        }
        //但是,会有重复的,例如bab,这里直接取那个大的b即可覆盖小的b全部情况
        map<char,int> statis;
        for(int i=0;i<n;i++)
        {
            if(statis.count(s[i]))
            {
                if(statis[s[i]]<dp[i])
                {
                    statis[s[i]]=dp[i];
                }
            }
            else
            {
                statis.insert(make_pair(s[i],dp[i]));
            }
        }
        int ans=0;
        for(auto pair:statis)
        {
            ans+=pair.second;
        }
        return ans;
    }
};

⑤.子序列

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序:

人话版:只要顺序对即可,无需保证连续

1.最长递增子序列(300)

并且呢,由于子序列的特性,它并不要求连续,所以在子序列中,对以前的dp表进行遍历的操作是非常常见的

至于判断条件推导状态转移方程的话,此题非常简单,相信大家可以直接得出

复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        if(n==1)
        {
            return 1;
        }
        vector<int> dp(n,1);
        int dp_max=INT_MIN;
        for(int i=1;i<n;i++)
        {
            int j_max=INT_MIN;
            for(int j=0;j<i;j++)
            {
                if(nums[i]>nums[j] && dp[j]>j_max)
                {
                    j_max=dp[j];
                }
            }
            dp[i]=j_max==INT_MIN?1:j_max+1;
            if(dp[i]>dp_max)
            {
                dp_max=dp[i];
            }
        }
        return dp_max;
    }
};

2.摆动序列(376)

叽里咕噜说了一大堆,什么意思?

那不就是这个意思么

所以轻轻松松,状态转移方程就出来了,那么这道题不就是在多状态dp的基础之上,再引入了子序列(多遍历一遍)么?

所以我们轻松得出:

复制代码
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n=nums.size();
        vector<int> rise(n,1);
        vector<int> decline(n,1);
        int maxr=INT_MIN;
        int maxd=INT_MIN;
        if(n==1)
        {
            return 1;
        }
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[j]<nums[i])
                {
                    //升序
                    rise[i]=max(rise[i],decline[j]+1);
                }
                if(nums[j]>nums[i])
                {
                    decline[i]=max(decline[i],rise[j]+1);
                }
                if(maxr<rise[i])
                maxr=rise[i];
                if(maxd<decline[i])
                maxd=decline[i];
            }
        }
        return max(maxd,maxr);
    }
};

3.最长递增子序列的个数(673)

至于这道题啊......他要我返回的是最长递增子序列的个数?

最简单的办法,自然是先找出最长的那个递增子序列,得到长度

那么问题此时转化为:

找出长度为N的递增子序列的个数

然后我继续遍历.......

但是这种做法,有点挫了,我想一步到位怎么办?

我可以在找的时候,就实时记录最长子序列的长度,此时就需要引入另一张dp表,用来标记以i为结尾,最长子序列的数目:

同时这是一道子序列的问题,子序列问题我要n*n遍历的,我在内部的循环中,引入判断递增子序列长度的判断,当满足最长子序列发生改变的时候,我直接将标记数目的dp表的counti赋值为countj即可,我们直接编码:

复制代码
class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> len(n,1);
        //以i为结尾,最长子序列的长度
        vector<int> count(n,1);
        //以i为结尾,最长子序列的数目
        int anslen=1;
        int anscount=1;
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[j]<nums[i])
                {
                    if(len[j]+1==len[i])
                    {
                        count[i]+=count[j];
                    }
                    else if(len[j]+1>len[i])
                    {
                        len[i]=len[j]+1;
                        count[i]=count[j];
                        //相当于找最大子序列的过程加了个找数目
                    }
                }
            }
            //这里就是找好了
            if(len[i]==anslen)
            anscount+=count[i];
            if(len[i]>anslen)
            {
                anslen=len[i];
                anscount=count[i];
            }
            
        }
        return anscount;
    }
};

4.最长定差子序列(1218)

这道题倒是有个地方值得注意:

我们发现这是个等差数列哎?等差数列的特点不就是,知道公差,还知道当前这个数,那我可以推得上一个数么?

那我如果以一个数它自身的数值为key,把以它为结尾的等差数列长度为value,那么不就可以通过一次遍历,从而得到我们的最长子序列么?

复制代码
class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) {
        int n=arr.size();
        unordered_map<int,int> hash;
        hash[arr[0]]=1;
        int ans=1;
        for(int i=1;i<n;i++)
        {
            if(hash.count(arr[i]-difference))
            {
                hash[arr[i]]=hash[arr[i]-difference]+1;
            }
            else
            {
                hash[arr[i]]=1;
            }
            if(hash[arr[i]]>ans)
            {
                ans=hash[arr[i]];
            }
        }
        return ans;
    }
};

5、最长的斐波那契子序列的长度(873)

此题注意一下,一维的dp表是解决不了这个问题的,因为我的dp表如果是一维的,那我存储的是以i位置这个元素为结尾的斐波那契子序列的最长长度,那我发现,一个数我也确定不了我的斐波那契序列啊?

所以我需要两个数,一个i位置,一个j位置,来表示我的斐波那契子序列:

复制代码
class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n=arr.size();
        unordered_map<int,int> hash;
        for(int i=0;i<n;i++)
        {
            hash[arr[i]]=i;
            //严格递增,不会重复
        }
        vector<vector<int>> dp(n,vector<int>(n,2));
        //i>j>k
        //为什么二维数组?因为一维数组无法确定斐波那契具体数字,而二维可以
        int ret=2;
        for(int i=2;i<n;i++)
        {
            for(int j=1;j<i;j++)
            {
                int pre=arr[i]-arr[j];
                if(hash.count(pre) && pre<arr[j])
                {
                     dp[j][i]=dp[hash[pre]][j]+1;
                }
                if(ret<dp[j][i])
                {
                ret=dp[j][i];
                }
            }
           
        }
        return ret==2?0:ret;
    }
};

6,最长等差数列(1027)

这题和以前的那一道差不多吧?

嗯。。。不讲了不讲了,我直接给出代码吧?

复制代码
class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
        int n=nums.size();
        unordered_map<int,vector<int>> hash;
        for(int i=0;i<n;i++)
        {
            hash[nums[i]].push_back(i);
        }
        int ans=2;
        vector<vector<int>> dp(n,vector<int>(n,2));
        for(int i=2;i<n;i++)
        {
            for(int j=1;j<i;j++)
            {
                //i>j
                int sub=nums[i]-nums[j];
                int pre=nums[j]-sub;
                if(hash.count(pre) )
                {
                    
                    auto it=hash[pre].rbegin();
                    while(it!=hash[pre].rend())
                    {
                        if(*it<j)
                        {
                            dp[j][i]=dp[*it][j]+1;
                            break;
                        }
                    it++;
                    }
                }
                if(ans<dp[j][i])
                {
                    ans=dp[j][i];
                }
            }
        }
        return ans;
    }
};

7.等差数列划分(446)

虽然标的困难题,其实很简单

和我们之前做过的差不多,还是注意一个一维的dp表是确定不了等差数列的,所以需要一个二维dp表,需要注意的也只有这个了

复制代码
class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n=nums.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        unordered_map<long long,vector<int>> hash;
        for(int i=0;i<n;i++)
        {
            hash[nums[i]].push_back(i);
        }
        int sum=0;
        for(int i=2;i<n;i++)
        {
            for(int j=1;j<i;j++)
            {
                //j<i,所以k<j
                long long tmp=(long long)nums[j]*2-nums[i];
                if(hash.count(tmp))
                {
                    auto it=hash[tmp].rbegin();
                    while(it!=hash[tmp].rend())
                    {
                        if(*it<j)
                        {
                            dp[j][i]+=dp[*it][j]+1;
                        }
                        it++;
                    }
                }
                sum+=dp[j][i];
            }
        }
        return sum;
    }
};

当然,不想用哈希也是可以的,哈希属于优化之后为了优化时间的解法,直接使用普通的动态规划思路也可以?

⑥. 回文串问题

1.回文子串(647)

至于这道题的话,我们又要使用我们的经验了:

以第i个位置为结尾.......

但是不够啊,以一个位置为结尾,谁知道能有多少子串?

所以我们需要一个二维的dp表了,所以dpij表示字符串i,j区间内,是否是回文子串;

所以我们的dp表类型是vector<vector<bool>>

同时我们需要注意:

回文子串的判断,有这三种情况,我们需要细细讨论......

同时。填表顺序也有些讲究:

我需要的是左下方的数据,所以在我填此地的时候,我的左边好了,下面也好了,所以我的填表顺序是:

从下往上,从左向右

复制代码
class Solution {
public:
    int countSubstrings(string s) {
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int ans=0;
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]!=s[j])
                {
                    dp[i][j]=false;
                    continue;
                }
                if(s[i]==s[j])
                {
                    if(i==j)
                    {
                        dp[i][j]=true;
                    }
                    else if(i+1==j)
                    {
                        dp[i][j]=true;
                    }
                    else{
                        dp[i][j]=dp[i+1][j-1];
                    }
                }
                if(dp[i][j]==true)
                ans++;
            }
        }
        return ans;
    }
};

2.最长回文子串(5)

这道题就和上道题是不是一模一样咯?

我们只需最后把最长的回文子串取出来即可

怎么取?

先确定一个头,一个长度,我们再通过长度的改变来判断头该变到什么地方

最后头确定了,长度也已知,只需用库函数substr即可

复制代码
class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int len=0;
        int begin=0;
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]!=s[j])
                {
                    dp[i][j]=false;
                    continue;
                }
                if(s[i]==s[j])
                {
                    if(i==j)
                    {
                        dp[i][j]=true;
                    }
                    else if(i+1==j)
                    {
                        dp[i][j]=true;
                    }
                    else{
                        dp[i][j]=dp[i+1][j-1];
                    }
                }
                if(dp[i][j]==true && j-i+1>len)
                 {
                    len=j-i+1;
                    begin=i;
                 }
            }
           
        }
        string ans=s.substr(begin,len);
        return ans;
    }
};

3.分割回文串IV(1745)

要我们把一个串分成三个回文子串啊。。。。

怎么分?

我们若有一个长为n的串,把分割点定为i,j

那么三个区域分别为0----i i+1------j j+1------n-1

那不是只需两个变量即可咯?从此,它与上题一样,只需找到两个点,,使这三个范围内皆为true即可

复制代码
class Solution {
public:
    bool checkPartitioning(string s) {
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]==s[j])
                {
                    dp[i][j]=i+1<j?dp[i+1][j-1]:true;
                }
            }
        }
        //分为三块0-i,i+1-j,j+1-n-1;
        for(int i=0;i<n-2;i++)
        {
            for(int j=i+1;j<n-1;j++)
            {
                if(dp[0][i] && dp[i+1][j] && dp[j+1][n-1])
                return true;
            }
        }
        return false;
    }
};

4.分割回文串II(132)

由此,我们编码得:

复制代码
class Solution {
public:
    #define MAX 0x3f3f3f3f
    int minCut(string s) {
        int n=s.size();
        vector<vector<bool>> palin(n,vector<bool>(n,false));
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]==s[j])
                palin[i][j]=i+1<j?palin[i+1][j-1]:true;
            }
        }
        vector<int> dp(n,MAX);
        for(int i=0;i<n;i++)
        {
            if(palin[0][i])
            {
                dp[i]=0;
            }
            else
            {
                for(int j=1;j<=i;j++)
                {
                    if(palin[j][i])
                    {
                        dp[i]=min(dp[i],dp[j-1]+1);
                    }
                }
            }
        }
        return dp[n-1];
    }
};

5,最长回文子序列(516)

至于这一道么.....由于不是子数组那种连续的,所以我们在状态转移方程的时候要多考虑一下,其余的话,倒是全都一样了

复制代码
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n=s.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        //i,j代表某一范围内回文子序列最大值,这里不是回文子串,所以不创建那种存储
        //所有子串是否回文的二维数组
        for(int i=n-1;i>=0;i--)
        {
            for(int j=i;j<n;j++)
            {
                if(s[i]==s[j])
                {
                    if(i==j)
                    {
                        dp[i][j]=1;
                    }
                    else if(i+1==j)
                    {
                        dp[i][j]=2;
                    }
                    else{
                        dp[i][j]=dp[i+1][j-1]+2;
                    }
                }
                else{
                    dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
                }
            }
        }
        return dp[0][n-1];
    }
};

6.让字符串成为回文串的最少插入次数(1312)

所以,万变不离其宗嘛,还是和前面几道题差不多的套路

复制代码
class Solution {
public:
    int minInsertions(string s) {
        int n=s.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        for(int i=n-1;i>=0;i--)
        {
            dp[i][i]=0;
            for(int j=i+1;j<n;j++)
            {
                if(s[i]==s[j])
                dp[i][j]=dp[i+1][j-1];
                else
                {
                    dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1;
                }
            }
        }
        return dp[0][n-1];
    }
};

⑦.双数组dp

1.最长公共子序列(1143)

两个字符串哎,在此之前呢,我先来给大家说说一个新的初始化方式:

在dp中,字符串前面加一个空格,可以帮助我们更好的对齐。

在这道题中就会有所涉及,大家可以体会一下

复制代码
class Solution {
public:
    int longestCommonSubsequence(string s1, string s2) {
        s1=" "+s1;
        s2=" "+s2;
        int m=s1.size();
        int n=s2.size();
        vector<vector<int>> dp(m,vector<int>(n));
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                if(s1[i]==s2[j])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m-1][n-1];
    }
};

2.不相交的线(1035)

这什么乐乐题目:

不能相交,那不就是公共子序列么

我直接一边编码一边讲咯:

复制代码
class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        //状态表示:i,j范围内的最长公共子序列;
        //状态转移方程式:nums1[i]==nums[j],那么是dp[i-][j-1]+1;
        //初始化:要用到左,左上,上,没有就初始化为0;
        int m=nums1.size();
        int n=nums2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(nums1[i-1]==nums2[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
};

3,不同的子序列(115)

复制代码
class Solution {
public:
    int numDistinct(string s, string t) {
        int n=s.size();
        int m=t.size();//目标子串,列,第一列为1
        vector<vector<double>> dp(n+1,vector<double>(m+1,0));
        for(int i=0;i<=n;i++)
        {
            dp[i][0]=1;
        }
        s=' '+s;
        t=' '+t;
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(s[i]==t[j])
                {
                    dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                }
                else
                {
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[n][m];
    }
};

4.通配符匹配(44)

干讲太麻烦,我直接画图咯:

所以我们就可以编码了:

复制代码
class Solution {
public:
    bool isMatch(string s, string p) {
        //首先,p是用来匹配s的
        int m=s.size();
        int n=p.size();
        vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
        s=' '+s;
        p=' '+p;
        //m是每一行,代表s,n是每一列,代表p
        dp[0][0]=true;
        for(int i=1;i<=n;i++)
        {
            if(p[i]=='*')
            {
                dp[0][i]=true;
            }
            else
            {
            break;
            }
        }
       
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                //i代表s(被匹配的)
                //三种情况,相等,?,*
                if(p[j]=='*')
                {
                    dp[i][j]=dp[i-1][j] || dp[i][j-1];
                }
                else{
                    dp[i][j]=(s[i]==p[j] || p[j]=='?') && dp[i-1][j-1];
                }
            }
        }
        return dp[m][n];

    }
};

5.正则表达式匹配(10)

这一道题和前面一道也差不了太多,所以我们直接编码咯:

复制代码
class Solution {
public:
    bool isMatch(string s, string p) {
        int m=s.size();
        int n=p.size();
        s=' '+s;
        p=' '+p;
        vector<vector<bool>> dp(m+1,vector<bool>(n+1));
        //m行,n列,上面第一行表示s大小为0,左边第一列表示p大小为0
        dp[0][0]=true;
        for(int j=2;j<=n;j+=2)
        {
            if(p[j]=='*')
            {
                dp[0][j]=true;
            }
            else
            break;
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(p[j]=='*')
                {
                    dp[i][j]=dp[i][j-2] || (p[j-1]==s[i] || p[j-1]=='.') && dp[i-1][j];
                }
                else{
                    dp[i][j]=(s[i]==p[j] || p[j]=='.')&& dp[i-1][j-1];
                }
            }
        }
        return dp[m][n];
    }
};

6.交错字符串(97)

那么:

复制代码
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int m=s1.size();
        int n=s2.size();
        vector<vector<bool>> dp(m+1,vector<bool>(n+1));
        if(m+n!=s3.size()) return false;
        s1=' '+s1;
        s2=' '+s2;
        s3=' '+s3;
        dp[0][0]=true;
        for(int i=1;i<=m;i++)
        {
            if(s1[i]==s3[i])
            dp[i][0]=true;
            else
            break;
        }
        for(int j=1;j<=n;j++)
        {
            if(s2[j]==s3[j])
            dp[0][j]=true;
            else
            break;
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
               dp[i][j]=(s1[i]==s3[i+j] && dp[i-1][j]) ||
                        (s2[j]==s3[i+j] && dp[i][j-1]);
            }
        }
        return dp[m][n];
    }
};

7.两个字符串的最小ASCII删除和(712)

都看到这里了,相信各位都能一眼丁真,直接看出来状态转移方程怎么写:

如果最后两位相同,不同......

复制代码
class Solution {
public:
    int minimumDeleteSum(string s1, string s2) {
        //i,j表示0到i,0到j范围内,两个字序列最大的公共子序列ASCII码大小
        int m=s1.size();
        int n=s2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        int count=0;
        for(auto e:s1)
        {
            count+=e;
        }
        for(auto e:s2)
        {
            count+=e;
        }
        s1=' '+s1;
        s2=' '+s2;
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(s1[i]==s2[j])
                {
                    dp[i][j]=dp[i-1][j-1]+s1[i];
                }
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return count-dp[m][n]*2;
    }
};

8.最长重复子数组(718)

我们在讲最长公共子序列,大家是不是想如果是最长重复子数组会怎么样,这不就来了?

知道为什么把最长公共子序列放前面么,因为最长重复子数组根本没难度,随便做做就出来了

复制代码
class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        //i,j表示以i,j为结尾的的公共子数组
        int m=nums1.size();
        int n=nums2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        int ans=INT_MIN;
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(nums1[i-1]==nums2[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else
                {
                    dp[i][j]=0;
                }
                if(dp[i][j]>ans)
                {
                    ans=dp[i][j];
                }
            }
        }
        return ans;
    }
};

⑧. 01背包

1、分割等和子集(416)

所以:

复制代码
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        for(auto e:nums)
        {
            sum+=e;
        }
        if(sum%2!=0)
        {
            return false;
        }
        //i代表,0到i区间内,满足子序列的和等于j
        vector<vector<bool>> dp(n+1,vector<bool>(sum/2+1));
        for(int i=0;i<=n;i++)
        {
            dp[i][0]=true;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=sum/2;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(j-nums[i-1]>=0)
                {
                    dp[i][j]=dp[i-1][j] || dp[i-1][j-nums[i-1]];
                }
            }
        }
        return dp[n][sum/2];
    }
};

2.目标和(494)

当年的步骤写的比较详细,那我就边写边给大家讲咯:

复制代码
class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int n=nums.size();
        int tar=target;
        for(auto e:nums)
        {
            tar+=e;
        }
        if(tar%2 || tar<0)
        {
            return 0;
        }
        tar/=2;
        //i,j,代表0到i范围内,满足和等于j的子序列的个数;
        vector<int> dp(tar+1);
        dp[0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=tar;j>=nums[i-1];j--)
            {
                //最后一个位置要不要
                dp[j]=dp[j]+dp[j-nums[i-1]];
            }
        }
        return dp[tar];
    }
};

⑨.完全背包

1.零钱兑换(322)

轻轻松松嘛

复制代码
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n=coins.size();
        vector<int> dp(amount+1);
        for(int j=1;j<=amount;j++)
        {
            dp[j]=0x3f3f3f3f;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=coins[i-1];j<=amount;j++)
            {
                dp[j]=min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount]>=0x3f3f3f3f?-1:dp[amount];
    }
};

2.零钱兑换II(518)

注意,此时变成了,凑成目标金额的组合数,但是总体与上一道题还是类似的,只不过状态转移方程后面不+1了

复制代码
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        #define INF 0x3f3f3f3f
        int n=coins.size();
        vector<vector<double>> dp(n+1,vector<double>(amount+1));
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=amount;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(j>=coins[i-1])
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i-1]];
                }
            }
        }
        return dp[n][amount];
    }
};

⑩.二维费用的背包问题:

1.盈利计划(879)

复制代码
class Solution {
public:
    int profitableSchemes(int n, int minprofit, vector<int>& group, vector<int>& profit) {
        //i,j,k分别表示0到i范围内,不超过n个员工,所获得的利润不小于k,的计划
        const int MOD=1e9+7;
        int m=group.size();
        vector<vector<vector<int>>> dp(m+1,vector<vector<int>>(n+1,vector<int>(minprofit+1)));
        for(int j=0;j<=n;j++)
        {
            dp[0][j][0]=1;
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<=n;j++)
            {
                for(int k=0;k<=minprofit;k++)
                {
                    dp[i][j][k]=dp[i-1][j][k];
                    if(group[i-1]<=j)
                    {
                        dp[i][j][k]=(dp[i-1][j][k]+dp[i-1][j-group[i-1]][max(0,k-profit[i-1])]);
                        dp[i][j][k]%=MOD;
                    }
                }
            }
        }
        return dp[m][n][minprofit];
    }
};

尾:

终于结束了,这篇写了十个小时,第一次花了两天才写出一篇文章,应该一共有五十道题左右吧?

希望可以帮助大家理解动态规划这一个算法。

同时,也想提高一下大家的信心,很多看上去很麻烦的算法题呢,有时候我们不需要彻彻底底的从代码上了解它,这很难的,但是我们可以在逻辑上把它理解透,这样写的时候,答案会自己蹦出来

最后呢,我想说的是:

月缺不改光

剑折不改刚

月缺魄易满

剑折铸复良

相关推荐
青少儿编程课堂32 分钟前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:Bellman-Ford与SPFA含负权最短路及负环检测全解析
c++·python·算法·bfs·信息学竞赛
octopus_c2 小时前
STL 之 string:auto 与范围 for、常用接口、模拟实现与深浅拷贝
开发语言·c++·算法
老师我太想进步了202617 小时前
【数据结构-4】算法美学之顺序表
数据结构·算法
风影同志7832417 小时前
编码器是激活函数吗?—— 一个看起来很像、其实不是的类比
算法
UIU11417 小时前
P1481 魔族密码
c++·算法·动态规划
加宝的起点雷达20 小时前
40 条候选里 38 条来自同一个源,主卡整屏重样
人工智能·算法·创业
秋天的怀念_David20 小时前
算法一千零一夜 · 第二十六夜 协议的母语
java·spring boot·算法