leetcode动态规划题型整理

Leetcode动态规划 动态规划 基本思想:问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题的最优解,再构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。 使用条件:可分为多个相关子问题,子问题的解被重复使用。 基本步骤: 首先创建一个存放子问题的解的空间,并做初始化。 将原问题尝试拆成多个子问题,并使得子问题的解可以求出原问题的解,这个通过子问题解求出原问题解的方程就叫做状态转移方程。 根据状态转移方程自底向上得到原问题解。 (附加)有时候存放子问题的解的空间可以优化得更 阅读详情

动态规划

动态规划核心问题怎么找DP,如果是矩阵路径类的题很容易想到dp[i][j],但是并非所有题都可以清楚找到DP项。

DP引子:斐波那契额数列

剑指 Offer 10- II. 青蛙跳台阶问题
//该题为斐波那契额数列变种,是dp问题中最经典题型,dp[i] = dp[i-1]+dp[i-2],由于dp只与前两项有关,所以可以优化dp将空间复杂度降低为O(1)
class Solution{
public:
    int numWays(int n) {
        if(n<=1) return 1;
        int left = 1;
        int right =1;
        int res = 0;
        for(int i = 2;i<= n;++i){
            res = (left+right)%1000000007;
            left = right;
            right = res;
        }
        return res;
    }
};

股票问题

122. 买卖股票的最佳时机 II

无限制买卖次数,不过每次买卖需要手续费

//dp[i][0]表示当天的收益(即选择是否在今天进行卖出后的收益)
//dp[i][1]表示当天买入股票后的剩余的资金
//dp[i][0] = dp[i-1][0] or dp[i-1][1]+prices[i] 。dp[i-1][0]表示上一天不进行操作,dp[i-1][1]+prices[i]表示上一天进行了买入当天进行卖出,若收益大于上一天收益则执行该操作
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()<=1) return 0;
        int n = prices.size();
        int dp[n][2];
        dp[0][0] = 0;
        dp[0][1] = -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] = dp[i][0] - prices[i];
        }
        return dp[n-1][0];
    }
};
714. 买卖股票的最佳时机含手续费

无限制买卖次数

//含有手续费用需要考虑的不能像之前一样进行频繁买卖。
//dp[i][0]表示第 i 天交易完后手里没有股票的最大利润
//dp[i][1]表示第 i 天交易完后手里持有一支股票的最大利润
//第i天持有股票情况可能是上一天已经持有了股票或者上一天未持有股票当天买入股票两种情况
class Solution{
public:
     int maxProfit(vector<int> &prices ,int fee){
         int n = prices.size();
         vector<vector<int>>dp(n,{0,0});
         dp[0][0] = 0; dp[0][1] = -prices[0];
         for(int i = 1 ; i<n; ++i){
             dp[i][0] = max(dp[i-1][0],dp[i-1][1] +prices[i]-fee);
             dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
         }
         return dp[n-1][0];
     }
};

最长递增序列问题

该类问题可以用二分查找来进行优化

300. 最长递增子序列

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

/*
dp[i]表示前i项的最大子序列长度。
解题方案:看到最长序列以及对字符串进行增删改操作此类型的题首先考虑动态规划
本题中考虑一个问题,dp[i]与之前状态有什么关系,如果第i项大于第j项,那么前i项最大子序列的一种情况便是前j项的最大子序列长度加上1。所以考虑递推式dp[i] >= dp[j]+1;所以我计算0-i-1所有能满足第i项大于第j项的项,dp[i]一定等于max(dp[j])+1(其中j满足nums[j]<nums[i]);即递推通项为:
{
    当nums[i]>nums[j]时候 dp[i] = max(dp[i],dp[j]+1)
    因为最小子串长度为1 所以初始化dp[i] = 1;
}
*/
class Solution{
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1){
            return n;
        }
        vector<int>dp(n,0);
        for(int i = 0;i< n;++i){
            dp[i] = 1;
            for(int j = 0;j< i ;++j){
                if(nums[i]>nums[j]){
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
        }
        int res =0 ;
        for(int i =0 ;i< n ;++i){
            res = max(res ,dp[i]);
        }
        return res;
    }
};
354. 俄罗斯套娃信封问题
//最长递增子序列变种问题,将数组进行排序,宽从小到大排列,高从大到小排列或从小到大均可,因为需要都遍历一遍。
//因为宽度一样优先选择更高的
class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        int n = envelopes.size();
        vector<int>dp(n,1);
        //相对于300题本题需要排序原因是无法保证第i项为结尾时前i项中包含所有数据,因为第i项后面可能存在数据也能满足,排序后逻辑与300题完全一致
        sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });
        for(int i = 0;i< n; ++i){
            for(int j = 0; j< i ;++j){
                if(envelopes[i][0]>envelopes[j][0]&&envelopes[i][1]>envelopes[j][1]){
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
        }
        return *max_element(dp.begin(),dp.end());
    }
};

最大子序列和问题

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

//dp[i]表示前i项最大和,dp[i] = max(nums[i],dp[i-1]+nums[i]);
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int>dp(nums.size(),0);
        dp[0] = nums[0];
        for(int i = 1;i<nums.size();++i)
        {
           dp[i] = max(nums[i],dp[i-1]+nums[i]);
        }
        return *max_element(dp.begin(),dp.end());
    }
};
//定义一个和为sum,sum为累加和,当sum<0时候,此时最大和一定出现在第i项后,另起炉灶,令sum = 0。
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxsum = INT_MIN;
        int sum = 0;
        for(int i = 0;i<nums.size();++i)
        {
            sum+= nums[i];
            if(sum>maxsum)
            {
                maxsum = sum;
            }
            if(sum<0) sum = 0;
        }
        return maxsum;

    }
};
918. 环形子数组的最大和

给定一个由整数数组 A 表示的环形数组 C,求 C 的非空子数组的最大可能和。

在此处,环形数组意味着数组的末端将会与开头相连呈环状。(形式上,当0 <= i < A.length 时 C[i] = A[i],且当 i >= 0 时 C[i+A.length] = C[i])

此外,子数组最多只能包含固定缓冲区 A 中的每个元素一次。(形式上,对于子数组 C[i], C[i+1], …, C[j],不存在 i <= k1, k2 <= j 其中 k1 % A.length = k2 % A.length)

//该题是最大子序列和的变种问题
//考虑两类情况,跨越边境和不跨越边境。不跨越边境下直接按照最大子序列和来求解。跨越边境下考虑在数组中找到最小子子序和minsum,其最大值为sum-minsum。比较两类情况得到最大值。
class Solution {
public:
    int maxSubarraySumCircular(vector<int>& A) {
        int n = A.size();
        if(n == 1){
            return A[0];
        }
        int maxres = INT_MIN;
        int sum = 0;
        int Asum = 0;
        for(int i = 0;i< n ;++i){
            Asum+=A[i];
            sum+=A[i];
            if(sum>maxres){
                maxres = sum;
            }
            if(sum<0){
                sum = 0;
            }
        }
        int minres = INT_MAX;
        sum = 0;
        for(int i = 0; i< n;++i){
            if(sum>0){
                sum = 0;
            }
            sum+=A[i];
            if(sum<minres){
                minres = sum;
            }
        }
        if(minres == Asum){
            return maxres;
        }
        return max(maxres,Asum-minres);
    }
};
152. 乘积最大子数组
//暴力解法
//由于会出现负数,dp数组需要保存两个状态,最大乘积(正)以及最小乘积(负)。
// 最大乘积dp[i][0] =  if(nums[i]>=0) :dp[i-1][0]*nums[i] or nums[i](防止0) else dp[i-1][1]*nums[i];
//即 dp[i][0] = max(dp[i-1][0]*nums[i],max(dp[i-1][1]*nums[i],nums[i]);
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1){
            return nums[0];
        }
        int maxres = nums[0];
        for(int i = 0;i< n ;++i){
            int res = nums[i];
            for(int j = i-1 ;j>=0;--j){
                maxres = max(max(nums[i],nums[j]*res),maxres);
                res*=nums[j];
            }
        }
        return maxres;
    }
};
//动态规划
class Solution {
public:
    int maxProduct(vector<int>& nums) {
int n = nums.size();
        if(n <= 1){
            return nums[0];
        }
        vector<vector<int>>dp(n,vector<int>{0,0});
        if(nums[0]>0){
            dp[0][0] = nums[0];
        }else{
            dp[0][1] = nums[0];
        }
        for(int i = 1;i< n ;++i){
           dp[i][0] = max(dp[i-1][0]*nums[i],max(dp[i-1][1]*nums[i],nums[i]));
           dp[i][1] = min(dp[i-1][1]*nums[i],min(dp[i-1][0]*nums[i],nums[i]));
            /*
           if(nums[i]>=0){
               dp[i][0] = max(dp[i-1][0]*nums[i],nums[i]);
               dp[i][1] = min(dp[i-1][1]*nums[i],0);
           }
           else{
               dp[i][0] = max(dp[i-1][1]*nums[i],0);
               dp[i][1] = min(dp[i-1][0]*nums[i],nums[i]);
           }*/
        }
        int maxres = 0;
        for(int i = 0;i< n ;++i){
            maxres = max(maxres,dp[i][0]);
        }
        return maxres;
    }
};
//由于dp[i]仅与dp[i-1]有关,优化空间复杂度得到:
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        if(n <= 1){
            return nums[0];
        }
        int minelement = min(nums[0],0);
        int maxelement = max(nums[0],0);
        int ans = maxelement;
        for(int i = 1;i< n ;++i){
           int mx = maxelement, mi = minelement;
           maxelement = max(mx*nums[i],max(mi*nums[i],nums[i]));
           minelement = min(mi*nums[i],min(mx*nums[i],nums[i]));
           ans = max(maxelement,ans);
        }
        return ans;
    }
};
813. 最大平均值和的分组

我们将给定的数组 A 分成 K 个相邻的非空子数组 ,我们的分数由每个子数组内的平均值的总和构成。计算我们所能得到的最大分数是多少。

//dp[i][j]表示为A的前i项备分割为K份下最大分数。
//初始化dp[i][1] = sum(A[:i]);
//建立数组sum[i]保存A数组的前i项和;
//dp[i][j] 若由dp[i-1][j-1]推出:dp[i][j] = dp[i-1][j-1]+A[i]/1;
//dp[i][j] 与dp[i-t][j-1]的关系是dp[i][j] = dp[i-t][j-1]+A[i]/t;
//要保证dp[i][j]最大,dp[i][j] = max(dp[1:t][j-1]+(sum[t:i])/(t:i));
class Solution {
public:
    double largestSumOfAverages(vector<int>& A, int K) {
        int n = A.size();
        vector<vector<double>> dp(n+1,vector<double>(K+1,0));
        vector<double> sum(n+1,0);
        for(int i = 1;i<= n ;++i){
            sum[i]=sum[i-1]+A[i-1];
            dp[i][1] = sum[i]/i;
        }
        for(int i = 1;i<= n ;++i){
            for( int j = 2 ;j <= K&&j <= i ;++j){
                double temp = 0;
                for( int t = i-1 ;t > 0 ;--t){  
                    dp[i][j] = max(dp[i][j],dp[t][j-1]+(sum[i]-sum[t])/(i-t));
                }
            }
        }
        return dp[n][K];
    }
};

打家劫舍系列

198. 打家劫舍

不可以连续抢劫

//显然dp[i] 表示抢劫前i家最大收益,小偷到第i家时候可以选择偷或者不偷,偷:dp[i] = dp[i-2]+nums[i]; 不偷dp[i] = dp[i-1],递推式很容易得到dp[i] = max(dp[i-2]+nums[i],dp[i-1])
class Solution {
public:
    int rob(vector<int>& nums) {
        int n= nums.size();
        if(n == 0){
            return 0;
        }
        if(n == 1){
            return nums[0];
        }
        vector<int>dp (n,0);
        dp[0] = nums[0];
        dp[1] = max(dp[0],nums[1]);
        for(int i = 2;i< n ;++i){
            dp[i] = max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[n-1];
    }
};
213. 打家劫舍 II

将第一个问题中的房屋设置为环形,即首尾相连

//分两类情况讨论,偷第一家还是偷最后一家,偷第一家,偷的范围为[1,n-1];偷最后一家范围为[0,n-2];对年内情况分别进行动态规划,答案为max(dp1[n-1].dp2[n-2]);
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size(),ans;
        if(n == 0) return 0;
        if(n == 1) return nums[0];
        int dp[n+1];
        dp[0] = 0;
        dp[1] = nums[0];
        for(int i = 2;i<n;i++)
        {
            dp[i] = max(dp[i-1],dp[i-2] + nums[i-1]);
        }
        ans = dp[n-1];
        dp[1] = nums[1];
        for(int i = 2;i<n;i++)
        {
            dp[i] = max(dp[i-1],dp[i-2] + nums[i]);
        }
        ans = max(dp[n-1],ans);
        return ans;
    }
};
1388. 3n 块披萨

复杂化的环形打家劫舍问题

//披萨为3n份,且披萨为首位相连的环,当选择i时候下一步不能选择i-1和i+1,即不能选择相邻的,与打家劫舍类似
//同样带环分为两类问题,过点slices[3n-1]和过slices[0]的情况分开。
//对两个情况打家劫舍得到结果
class Solution {
public:
    int calculate(const vector<int>& slices) {
        int n = slices.size();
        int choose = (n + 1) / 3;
        vector<vector<int>> dp(n + 1, vector<int>(choose + 1,0));
        dp[1][1] = slices[0];
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= choose; ++j) {
                dp[i][j] = max(dp[i - 1][j], dp[i - 2][j - 1] + slices[i - 1]);
            }
        }
        return dp[n][choose];
    }

    int maxSizeSlices(vector<int>& slices) {
        vector<int> v1(slices.begin() + 1, slices.end());
        vector<int> v2(slices.begin(), slices.end() - 1);
        int ans1 = calculate(v1);
        int ans2 = calculate(v2);
        return max(ans1, ans2);
    }
};
//优化空间复杂度
class Solution {
public:
    int maxSizeSlices(vector<int>& slices) {
        int n = slices.size();
        int choose = n / 3;
        int ans = 0;
        vector<vector<int>> dp(n + 1, vector<int>(choose + 1,0));
        dp[1][1] = slices[0];
        for (int i = 2; i < n; ++i) {
            for (int j = 1; j <= choose; ++j) {
                dp[i][j] = max(dp[i - 1][j], dp[i - 2][j - 1] + slices[i - 1]);
            }
        }
        ans = dp[n-1][choose];
        dp[1][1] = slices[1];
        for (int i = 2; i < n; ++i) {
            for (int j = 1; j <= choose; ++j) {
                dp[i][j] = max(dp[i - 1][j], dp[i - 2][j - 1] + slices[i]);
            }
        }
        return max(ans,dp[n-1][choose]);
    }
};
256. 粉刷房子
//不能连续选择同一颜色,有点类似打家劫舍。
class Solution {
public:
    int minCost(vector<vector<int>>& costs) {
        int n= costs.size();
        vector<vector<int>>dp(n,vector<int>(3,0));
        dp[0][0] = costs[0][0];
        dp[0][1] = costs[0][1];
        dp[0][2] = costs[0][2];
        for(int i = 1;i<n ;++i){
            dp[i][0] = min(dp[i-1][1],dp[i-1][2])+costs[i][0];
            dp[i][1] = min(dp[i-1][0],dp[i-1][2])+costs[i][1];
            dp[i][2] = min(dp[i-1][0],dp[i-1][1])+costs[i][2];
        }
        return min(min(dp[n-1][0],dp[n-1][1]),dp[n-1][2]);
    }
};

子集问题

1027. 最长等差数列
//hash表记录每个数出现的位置
//dp[i][j]表示以i、j作为最后两位的等差数列长度。
//当-A[j]+2*A[i]存在说明,A[idx] = 2*A[i]-A[j];
//等差数列存在dp[i][j] = A[idx][i]+1;
//为了避免重复元素所以遍历完毕小于i的情况下将将A[i]位置放入map中:map[A[i]] = i;
class Solution {
public:
    int longestArithSeqLength(vector<int>& A) {
        unordered_map<int, int> map;
        int n = A.size();
        vector<vector<int>> dp(n, vector<int>(n, 2));
        int ans = 0;
        for (int i = 0; i < n; i ++) {
            for (int j = i + 1; j < n; j ++) {
                int target = 2 * A[i] - A[j];
                if (map.count(target)) dp[i][j] = dp[map[target]][i] + 1;
                ans = max(ans, dp[i][j]);
            }
            //防止出现重复元素
            map[A[i]] = i;
        }
        return ans;
    }
};
368. 最大整除子集
//i:排个序,因为保证的每个元素对互相整除,所以要从大到小
//if(nums[i]%nums[j] == 0) 说明nums[i]、nums[j]满足整除,得dp[i] = dp[j]+1;
//由于需要返回数组,建立数组将位置进行储存,当该数组是最大长度时,将最大的i赋给end,last[i]中储存的是上一个整除数得位置。
class Solution {
public:
    vector<int> largestDivisibleSubset(vector<int>& nums) {
        int sz = nums.size(),mx = 0,end = -1;
        vector<int> dp(sz,1),last(sz,-1),res;
        sort(nums.begin(),nums.end());
        for(int i = 0;i<sz;i++){
            for(int j = 0;j<i;j++){
                if(nums[i]%nums[j] == 0 && dp[i]<=dp[j]){
                    dp[i] = dp[j]+1;
                    last[i] = j;
                }
            }
            if(dp[i]>mx){
                mx = dp[i];
                end = i;
            }
        }
        for(int i = end;i!=-1;i = last[i]){//倒序输出
            res.push_back(nums[i]);
        }
        return res;
    }
};
413. 等差数列划分
//建立dp[i],i为以i为结尾的等差数列个数
//if(nums[i]-nums[i-1] == nums[i-1] - nums[i-2]) dp[i] = dp[i-1]+1;
//递推结束遍历dp,其和为结果。
class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n = nums.size();
        if(n<3) return 0;
        vector<int> dp(n,0);
        int sum = 0;
        for(int i = 2;i< n;++ i){
            if(nums[i]-nums[i-1] == nums[i-1] - nums[i-2]){
                dp[i] = dp[i-1]+1;
                sum+=dp[i];
            }
        }
        return sum;
    }
};
873. 最长的斐波那契子序列的长度

如果序列 X_1, X_2, …, X_n 满足下列条件,就说它是 斐波那契式 的:

n >= 3
对于所有 i + 2 <= n,都有 X_i + X{i+1} = X{i+2}

给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。

//类似1027最长等差数列问题
//hash表记录每个数出现的位置
//dp[i][j]表示以i、j作为最后两位的斐波那契式的长度。
//当arr[j] - arr[i]出现时,说明存在连续斐波那契式,arr[idx] = arr[j-arr[i]]. 
//此时dp[i][j] = max(dp[i][j],dp[idx][i]+1);当dp[i][j]>=1时候说明存在,长度为dp[i][j]+2;
class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n = arr.size();
        unordered_map<int, int> hash;
        vector<vector<int>> f(n, vector<int>(n));
        int ans = 0;
        for (int i = 0; i < n; ++i) hash[arr[i]] = i;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (hash.count(arr[j] - arr[i]) && (arr[j] - arr[i] < arr[i])) {
                    auto idx = hash[arr[j] - arr[i]];
                    f[i][j] = max(f[i][j], f[idx][i] + 1);
                    ans = max(ans, f[i][j] + 2);
                }
            }
        }
        if (ans < 3) return 0;
        return ans;
    }
};

公共子序列问题

1143. 最长公共子序列
//最长公共子序列dp[i][j]。i为text1前i个数,j为text2前j个数 。
//初始化dp[i][0]和dp[0][j]。
//if(text1[i]!= text2[j]) dp[i][j] = dp[i-1][j-1];
//else dp[i][j] = dp[i-1][j] or dp[i][j-1] dp只可能是递增的。
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n = text1.size();
        int m = text2.size();
        vector<vector<int>> dp (n,vector<int>(m,0));
        int flag = false;
        for(int i = 0;i <n ;++i){
            if(text1[i] == text2[0]){
                flag = true;
            }
            if(flag){
                dp[i][0] = 1;
            }
        }
        flag = false;
        for(int i = 0;i <m ;++i){
            if(text2[i] == text1[0]){
                flag = true;
            }
            if(flag){
                dp[0][i] = 1;
            }
        }
        for(int i = 1;i <n ;++i){
            for(int j = 1; j< m ;++j){
                if(text1[i] == text2[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[n-1][m-1];
    }
};

//稍微优化一下
//优化后不需要考虑初始问题
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n = text1.size();
        int m = text2.size();
        vector<vector<int>> dp (n+1,vector<int>(m+1,0));
        for(int i = 1;i <= n ;++i){
            for(int j = 1; j<= m ;++j){
                if(text1[i-1] == text2[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[n][m];
    }
};
712. 两个字符串的最小ASCII删除和

给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。

//最长公共子序列问题
//本质上是找到最长子序列,在寻找过程中选择最小的删除方案进行处理,记录每一个删除的ASCII大小,这个需要初始化
class Solution {
public:
    int minimumDeleteSum(string s1, string s2) {
        int len1 = s1.size();
        int len2 = s2.size();
        vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
        for(int i = 1;i<= len1; ++i){
            dp[i][0] = dp[i-1][0]+s1[i-1];
        }
        for(int i = 1;i<= len2; ++i){
            dp[0][i] = dp[0][i-1]+s2[i-1];
        }
        for(int i = 1;i<= len1; ++i){
            for(int j = 1; j<=len2 ;++j){
                if(s1[i-1] == s2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }
                else{
                    //不相同需要删除一个元素,选择从两条路径选择最短删除
                    dp[i][j] = min(dp[i-1][j]+s1[i-1],dp[i][j-1]+s2[j-1]);
                }
            }
        }
        return dp[len1][len2];
    }
};
718. 最长重复子数组
//与题1143的区别在于子数组与子序列区别是子数组必须是连续的不间断的
//dp[i][j]定义为以i,j为结尾的数组的最长重复子串
//if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1]+1;
//else 另起炉灶 dp[i][j] = 0;
//遍历一遍,找到最大的dp即可
class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size();
        int len2 = nums2.size();
        int ans = 0;
        vector<vector<int>> dp (len1+1,vector<int>(len2+1,0));
        for(int i = 1 ;i<=len1 ;++i){
            for(int j = 1 ;j<= len2 ;++j){
                if(nums1[i-1] == nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j] = 0;
                }
                ans = max(ans,dp[i][j]);
            }
        }
        return ans;
    }
};
97. 交错字符串
//dp[i][j]是前i项和前j项能不能交错成为S3的前i+j项
//dp[0][0] = true;
//if(s1[i-1] == s3[i+j-1]) dp[i][j] = dp[i-1][j];
//if(s2[j-1] == s3[i+j-1]) dp[i][j] = dp[i][j-1];
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int len1 = s1.size();
        int len2 = s2.size();
        if(len1 + len2 != s3.size()){
            return false;
        }
        vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
        dp[0][0] = true;
        for(int i = 1;i<= len1 ;++i){
            if(s1.substr(0,i) == s3.substr(0,i)){
                dp[i][0] = true;
            }
        }
        for(int i = 1;i<= len2 ;++i){
            if(s2.substr(0,i) == s3.substr(0,i)){
                dp[0][i] = true;
            }
        }
        for(int i = 1;i<= len1 ;++i){
            for(int j = 1;j<= len2 ;++j){
                if(s1[i-1] == s3[i-1+j]&&dp[i-1][j]){
                    dp[i][j] = true;
                }
                if(s2[j-1] == s3[i-1+j]&&dp[i][j-1]){
                    dp[i][j] = true;
                }
            }
        }
        return dp[len1][len2];
    }
};

//完全可以省略初始化情况
class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int len1 = s1.size();
        int len2 = s2.size();
        if(len1 + len2 != s3.size()){
            return false;
        }
        vector<vector<int>>dp(len1+1,vector<int>(len2+1,0));
        dp[0][0] = true;
        for(int i = 0;i<= len1 ;++i){
            for(int j = 0;j<= len2 ;++j){
                if(i>0&&s1[i-1] == s3[i-1+j]&&dp[i-1][j]){
                    dp[i][j] = true;
                }
                if(j>0&&s2[j-1] == s3[i-1+j]&&dp[i][j-1]){
                    dp[i][j] = true;
                }
            }
        }
        return dp[len1][len2];
    }
};

583. 两个字符串的删除操作
//最长公共子序列问题
//删除字符串不就是找到两个字符串的最大子序列吗,删除大小为n+m- 2*maxlen;
class Solution {
public:
    int minDistance(string text1, string text2) {
        int n = text1.size();
        int m = text2.size();
        vector<vector<int>> dp (n+1,vector<int>(m+1,0));
        for(int i = 1;i <= n ;++i){
            for(int j = 1; j<= m ;++j){
                if(text1[i-1] == text2[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 n+m-2*dp[n][m];
    }
};
115. 不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

//dp[i][j]表示前i,j项
//if(s[i-1] != t[j-1]) dp[i][j]中末尾不是t[j-1]所以与上一个状态前(i-1)项出现t的前j项情况一致dp[i-1][j]。
//if(s[i-1] == t[j-1]) dp[i][j]有两种选择dp[i-1][j]和dp[i][j-1]都是其子情况
class Solution {
public:
    int numDistinct(string s, string t) {
        int n = s.size();
        int m = t.size();
        vector<vector<unsigned long long>>dp(n+1,vector<unsigned long long>(m+1,0));
        dp[0][0] =1;
        int a = 0;
        for(int i = 1 ;i<= n ;++i){
            dp[i][0] = 1;
        }
        for(int i = 1; i<= n;++i){
            for(int j = 1; j<= m&&j<=i ;++j){
                if(s[i-1] != t[j-1]){
                    dp[i][j] = dp[i-1][j];
                }
                else{
                    dp[i][j] = dp[i-1][j]+dp[i-1][j-1];
                }
            }
        }
        return (int)dp[n][m];
    }
};
72. 编辑距离
//当(word1[i] == word2[j])时候 dp[i][j] = dp[i-1][j-1];(即不做增删改)
//word1[i]!=word2[j]时 dp[i][j] 有三种选择 dp[i-1][j]+1:添加一个, dp[i][j-1]+1删除一个,dp[i-1][j-1]+1更改一个,选择最小情况。
class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size();
        int len2 = word2.size();
        vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
        for(int i = 0;i< len1+1 ;++i){
            dp[i][0] = i;
        }
        for(int i = 0;i< len2+1 ;++i){
            dp[0][i] = i;
        }
        for(int i = 1;i< len1+1 ;++i){
            for(int j = 1;j< len2+1 ;++j){
                if(word1[i-1] == word2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }
                else{
                    dp[i][j] = 1+min(min(dp[i-1][j-1],dp[i][j-1]),dp[i-1][j]);
                }
            }
        }
        return dp[len1][len2];
    }
};

字符串匹配

44. 通配符匹配

考虑一个问题dp[i] [j]什么时候用来表示前i、j项,什么时候表示以i、j为结尾

?可以匹配任意字符,*可以匹配任意字符串(含空串)

//dp[i][j]表示数组的前i,j项是否可以被匹配。
//if(p[j] == '?'||p[j] == s[i]) i、j位置一定被匹配: dp[i][j] = dp[i-1][j-1];
//if(p[j] == '*') 两种情况:一是看成空串 dp[i][j] = dp[i][j-1];
//                        二是看成任意字符串(将S[i]位置进行匹配)dp[i][j] = dp[i-1][j];
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
        dp[0][0] = true;
        for(int i = 1;i<= n ;++i){
            dp[0][i] = dp[0][i-1]&&(p[i-1] == '*');
        }
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                //当为?时说明当前项一定匹配成功
                if(p[j-1] == '?'||p[j-1] == s[i-1]){
                    dp[i][j] = dp[i-1][j-1];
                }
                //当为*时候,dp[i][j]可以选择要该项或者不要
                //要是dp[i][j] = dp[i-1][j];不要为dp[i][j-1];
                if(p[j-1] == '*'){
                    dp[i][j] = dp[i-1][j]||dp[i][j-1];
                }
            }
        }
        return dp[m][n];
    }
};
剑指 Offer 19. 正则表达式匹配

匹配包含'. ''*'的正则表达式

模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。

//dp[i][j]表示数组的前i,j项是否可以被匹配。
//if(p[j] == '.'||p[j] == s[i]) i、j位置一定被匹配: dp[i][j] = dp[i-1][j-1];
//if(p[j] == '*') 两种情况 一是看成空串(前一字符出现0次) dp[i][j] = dp[i][j-2];
//           二是看成前一字符串进行匹配 if(s[i] == p[j-1]||p[j-1] == '.') 说明匹配成功dp[i][j] = dp[i-1][j]
class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size() + 1, n = p.size() + 1;
        vector<vector<bool>> dp(m, vector<bool>(n, false));
        dp[0][0] = true;
        // 初始化首行
        for(int j = 2; j < n; j += 2)
            dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
        // 状态转移
        for(int i = 1; i < m; i++) {
            for(int j = 1; j < n; j++) {
                if(p[j - 1] == '*') {
                    //不看*的情况
                    //由于*不可能出现在第一位所以不会出现越界
                    if(dp[i][j - 2]) dp[i][j] = true;
                    // 看*的情况分为两类,一类是s[i-1] == p[j-2]此时说明第i项存在匹配,接着判断第i-1项是否匹配
                    else if(dp[i - 1][j] && s[i - 1] == p[j - 2]) dp[i][j] = true;
                    else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true;    
                } else {
                    if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true; 
                    else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true; 
                }
            }
        }
        return dp[m - 1][n - 1];
    }
};

背包问题专栏

416. 分割等和子集

给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

//直接采用0-1背包二维解法
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0,maxnumber = 0, n = nums.size();
        if (n < 2) {
            return false;
        }
        for(int i = 0;i< n ;++i){
            sum+=nums[i];
            maxnumber = max(maxnumber,nums[i]);
        }
        int target = sum/2;
        //当元素和为奇数或者最大元素大于和的一半时,不可能存在等和子集
        if(sum%2!=0||maxnumber >target) {
            return false;
        }
        if(maxnumber == target){
            return true;
        }
        vector<vector<int>>dp(n+1,vector<int>(target+1,0));
        //将题目转化为背包容量为sum/2的背包问题,看当背包容量为sum/2下背包元素和最大是否存在等于sum/2。
        for(int i = 0;i<=n;++i){
            for(int j = target ; j>=0;--j){
                if(j>=nums[i]){
                    dp[i][j] = max(dp[i-1][j],dp[i-1][j-nums[i]]+nums[i]);
                }
                else {
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n][target] == target;
    }
};


//将二维DP简化为一维DP问题
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0,maxnumber = 0, n = nums.size();
        if (n < 2) {
            return false;
        }
        for(int i = 0;i< n ;++i){
            sum+=nums[i];
            maxnumber = max(maxnumber,nums[i]);
        }
        int target = sum/2;
        if(sum%2!=0||maxnumber >target) {
            return false;
        }
        //将二维DP转化为一维DP,因为二维DP公式只与DP[i-1]状态有关,故可以合并。但是一定考虑需要逆序遍历DP。
        vector<int>dp(target+1,0);
        for(int i = 1;i<=n;++i){
            for(int j = target ; j>=nums[i-1];--j){
                dp[j] = max(dp[j],dp[j-nums[i-1]]+nums[i-1]);
            }
        }
        return dp[target] == target;
    }
};

//背包问题变种,因为是考虑是否存在平衡数,不需要进行考虑,
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        if (n < 2) {
            return false;
        }
        int sum = 0, maxNum = 0;
        for (auto& num : nums) {
            sum += num;
            maxNum = max(maxNum, num);
        }
        if (sum & 1) {
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }
        vector<int> dp(target + 1, 0);
        dp[0] = true;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            //当存在数组之和为j时,dp[j] = true;
            for (int j = target; j >= num; --j) {
                dp[j] |= dp[j - num];
            }
        }
        return dp[target];
    }
};
474. 一和零
class Solution {
private:
    int check0(string s){
        int res = 0;
        for(int i = 0;i< s.size() ;++i){
            if(s[i] == '0') res++;
        }
        return res;
    }
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>>dp(m+1,vector<int>(n+1,0));
        int strsize = strs.size();
        for(int i = 0;i< strsize;++i){
            int zeros = check0(strs[i]);
            int ones =strs[i].size()-zeros;
            for(int j = m;j>=zeros ;--j){
                for(int k = n; k>=ones;--k){
                    dp[j][k] = max(dp[j][k],dp[j-zeros][k-ones]+1);
                }
            }
        }
        return dp[m][n];
    }
};
494. 目标和

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
X − Y = S ; X + Y = s u m ; X = ( S + s u m ) / 2 X-Y=S; X+Y=sum; X = (S+sum)/2 XY=S;X+Y=sum;X=(S+sum)/2

//设添加符号完成后数组中正整数和为X,负数和的绝对值为Y。原数组数据和为sum,目标值为S,得:
//X-Y=S,X+Y=sum,X=(S+sum)/2;
//由上可看出该题转化为在数组中找到和为(S+sum)/2的数组。显然该题转化为0-1背包问题,背包大小为(S+sum)/2,背包中每个元素Values与其消耗空间相同。
//易得递推公式为dp[j] = dp[j]+dp[j-nums[i]];
//递推原理,递推中该次情况由两种情况推出,即该次不向背包添加数据(上轮dp[j])以及向背包添加数据(上轮dp[j-nums[i]]);

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int n = nums.size();
        int sum = 0;
        for(int i = 0;i<n;++i){
            sum+=nums[i];
        }
        if(abs(S)>sum||(S+sum)%2!=0){
            return false;
        }
        int target = (S+sum)/2;
        vector<int> dp(target+1,0);
        dp[0] =1;
        for(int i = 0;i<n ;++i){
            for(int j = target ;j>=nums[i] ;--j){
                dp[j] = dp[j]+dp[j-nums[i]];
            }
        }
        return dp[target];
    }
};
879. 盈利计划

集团里有 n 名员工,他们可以完成各种各样的工作创造利润。

第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。

工作的任何至少产生 minProfit 利润的子集称为盈利计划。并且工作的成员总数最多为 n 。

有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值。

class Solution {
public:
    int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
        int len = group.size();
        const int M = 1e9+7;
        vector<vector<int>>dp(n+1,vector<int>(minProfit+1,0));
        for (int j = 0; j <= n; ++j) {
            dp[j][0] = 1;
        }
        for(int i = 0;i<len ;++i){
            int g = group[i];
            int p = profit[i];
            for(int j = n; j>=g ;--j){
                for(int k = minProfit ;k>=0 ;--k){
                    dp[j][k]+=dp[j-g][max(k-p,0)];
                    dp[j][k]%=M;
                }
            }
        }
        return dp[n][minProfit];
    }
};
322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

你可以认为每种硬币的数量是无限的。

//dp[j]代表含义:填满容量为j的背包最少需要多少硬币
//初始化dp数组:因为硬币的数量一定不会超过amount,所以初始化数组为amount+1;dp[0] = 0;
//转移方程:dp[j] = min(dp[j], dp[j - coins[i]] + 1)当前填满容量j最少需要的硬币 = min( 之前填满容量j最少需要的硬币, 填满容量 j - coin 需要的硬币 + 1个当前硬币)
//返回dp[amount],如果dp[amount]的值为10001没有变过,说明找不到硬币组合,返回-1
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int Max = amount + 1;
        vector<int> dp(amount + 1, Max);
        dp[0] = 0;
        for(int i = 0; i< coins.size() ;++i){
            for(int j = coins[i];j<=amount ;++j){
                dp[j] = min(dp[j],dp[j-coins[i]]+1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};
518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

//完全背包问题,因为可重复使用硬币
//若为二维dp,dp[i][j] = dp[i-1][j]+dp[i-1][j-coins[i]];因为当前硬币选择数等于该轮不选硬币以及该轮选择硬币的方案之和
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int len = coins.size();
        int minnumber = 0;
        for(int i = 0;i<len ;++i){
            minnumber = min(minnumber,coins[i]);
        }
        if(minnumber>amount){
            return 0;
        }
        vector<int>dp(amount+1,0);
        dp[0] = 1;
        for(int i =0;i<len ;++i){
            for(int j = coins[i] ; j<=amount ; ++j){
                dp[j] = dp[j]+dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
};
449. 数位成本和为目标值的最大数字

给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数:

给当前结果添加一个数位(i + 1)的成本为 cost[i] (cost 数组下标从 0 开始)。
总成本必须恰好等于 target 。
添加的数位中没有数字 0 。
由于答案可能会很大,请你以字符串形式返回。

如果按照上述要求无法得到任何整数,请你返回 “0” 。

//本题为完全背包问题中将背包恰好装满模型,问题在于如何处理字符串比较问题。其余与完全背包完全一致
class Solution {
public:
   //string dp[9 + 5][5000 + 5];
   // 返回两者较大的一个
   string string_max(const string &lhs, const string &rhs) {
       if (lhs.size() > rhs.size()) return lhs;
       if (rhs.size() > lhs.size()) return rhs;
       // 当两字符串长度相等时
       if (lhs > rhs) return lhs;
       else return rhs;
   }
   
   string largestNumber(vector<int> cost, int target) {
       int len = cost.size();
       vector<string>dp(target+1,"#"); 
       dp[0] = "";
       for (int i = 0; i < len; ++i) {
           for (int j = cost[i]; j <= target; ++j) {
               string a, b;
               // 不选第i个
               a = dp[j];
               // 加选一个
               if (dp[j - cost[i]] != "#")
                   b = to_string(i+1) + dp[j - cost[i]];
               dp[j] = string_max(a, b);
           }
       }
       if (dp[target] == "#") return "0";
       else return dp[target];
   }
};
满减优惠券(非力扣题)

给定一张满减优惠券,问在购物车内如何选择可以使得优惠券力度最大

路径规划问题

45. 跳跃游戏 II

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

//方法一:动态规划 复杂度较大
class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n,INT_MAX);
        dp[0] = 0;
        for(int i = 1;i< n;++i){
            for(int j = 0; j<i ;++j){
                if(nums[j]+j >= i){
                    dp[i] = min(dp[i],dp[j]+1);
                }
            }
        }
        return dp[n-1];
    }
};
//贪心算法,动态规划中记录每个位置到该点的情况非常多余,因为考虑到一定会到最后一点,只需要记录前i项可以跳到的最远距离即可。当我走到第i项时候,直接选择跳到最远距离重新开始向前走。
class Solution {
public:
    int jump(vector<int>& nums) {
        int maxPos = 0, n = nums.size(), end = 0, step = 0;
        for (int i = 0; i < n - 1; ++i) {
            //maxpos最大距离
            if (maxPos >= i) {
                maxPos = max(maxPos, i + nums[i]);
                if (i == end) {
                    //下一次起跳点
                    end = maxPos;
                    ++step;
                }
            }
        }
        return step;
    }
};
63. 不同路径 II
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int row = obstacleGrid.size();
        int col = obstacleGrid[0].size();
        vector<vector<int>> dp(row,vector<int>(col,0));
        if(obstacleGrid[0][0] == 0)dp[0][0] = 1;
        for(int i = 1;i<row ;++i){
            dp[i][0] = dp[i-1][0];
            if(obstacleGrid[i][0] == 1){
                dp[i][0] = 0;
            }
        }
        for(int i = 1;i< col;++i){
            dp[0][i] = dp[0][i-1];
            if(obstacleGrid[0][i] == 1){
                dp[0][i] = 0;
            }
        }
        for(int i = 1 ;i< row ;++i){
            for(int j = 1; j< col ;++j){
                if(obstacleGrid[i][j] == 1){
                    continue;
                }else{
                    dp[i][j] = dp[i-1][j] +dp[i][j-1];
                }
            }
        }
        return dp[row-1][col-1];
    }
};
403. 青蛙过河

检测青蛙是否可以过河,如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1kk + 1 个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

class Solution {
public:
    bool canCross(vector<int>& stones) {
        int n = stones.size();
        if(n<=1) return true;
        unordered_map<int,int> map;
        vector<vector<int>>dp(n+1,vector<int>(n+1,0));
        dp[0][0] = 1;
        for(int i = 0;i<n ;++i){
            map[stones[i]] = i;
        }
        if(stones[1] > 1){
            return false;
        }
        dp[1][1] = true;
        for(int i = 2;i< n ;++i){
            for(int k = 1 ;k<= i ;++k){
                if(map.count(stones[i]-k)>0){
                    int j = map[stones[i]-k];
                    dp[i][k]  = dp[j][k]||dp[j][k-1]||dp[j][k+1];
                }
            }
        }
        for(int i = 0;i< n ;++i){
            if(dp[n-1][i] == true) return true;
        }
        return false;
    }
};
64. 最小路径和
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size();
        int m = grid[0].size();
        vector<vector<int>>dp(n,vector<int>(m,0));
        dp[0][0] = grid[0][0];
        for(int i = 1;i<n ;++i){
            dp[i][0] = dp[i-1][0]+grid[i][0];
        }
        for(int i = 1;i<m ;++i){
            dp[0][i] = dp[0][i-1]+grid[0][i];
        }
        for(int i = 1 ;i< n ;++i){
            for(int j = 1; j < m ;++j){
                dp[i][j] = min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[n-1][m-1];
    }
};

其他

871. 最低加油次数
class Solution {
public:
    int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
        int n = stations.size();
        vector<long long>dp(n+1,0);
        vector<bool>flag(n,false);
        dp[0] = startFuel;
        for(int i = 1 ;i< n+1;++i){
            int maxfuel = 0;
            int pos = -1;
            for(int j = 0;j<n&&stations[j][0]<= dp[i-1] ;++j){
                if(maxfuel < stations[j][1]&&flag[j] == false){
                    maxfuel = stations[j][1];
                    pos = j;
                }
            }
            if(pos>=0) flag[pos] = true;
            dp[i] = dp[i-1]+maxfuel;
        }
        for(int i =0;i<= n ;++i){
            if(target<=dp[i]){
                return i;
            }
        }
        return -1;
    }
};
91. 解码方法
//dp简单,重点在于边界信息的处理
class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        vector<int>dp(n,0); 
        if(s[0] == '0'){
            return 0;
        }else{
            dp[0] = 1;
        }
        for(int i = 1;i< n;++i){
            if(s[i] == '0'){
                if(s[i-1] == '2'||s[i-1] == '1'){
                    if(i>=2)dp[i] = dp[i-2];
                    else dp[i] = 1;
                }else{
                    return 0;
                }
            }
            else{
                if(s[i-1]>'2'||(s[i-1] == '2'&&s[i]>'6')||s[i-1] == '0'){
                    dp[i] = dp[i-1];
                }else{
                    if(i>=2)dp[i] = dp[i-1]+dp[i-2];
                    else dp[i] = 2;
                }
            }
        }
        return dp[n-1];
    }
};

二分法搜索

410. 分割数组的最大值

找使得最大值最小的一般都可以用二分搜索

//动态规划

class Solution {
public:
    int splitArray(vector<int>& nums, int m) {
        int n = nums.size();
        vector<vector<long long>> f(n + 1, vector<long long>(m + 1, LLONG_MAX));
        vector<long long> sub(n + 1, 0);
        for (int i = 0; i < n; i++) {
            sub[i + 1] = sub[i] + nums[i];
        }
        f[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= min(i, m); j++) {
                for (int k = 0; k < i; k++) {
                    f[i][j] = min(f[i][j], max(f[k][j - 1], sub[i] - sub[k]));
                }
            }
        }
        return (int)f[n][m];
    }
};
//*****二分搜索法
class Solution {
    bool check(vector<int>& nums , int x ,int m){
        long long sum = 0;
        //连续和大于目标值的分割个数
        int cnt = 1;
        for(int i =0;i< nums.size() ;++i){
            sum+=nums[i];
            if(sum > x){
                sum = nums[i];
                cnt++;
            }
        }
        //当分割次数小于m,说明这个分割次数太少了,那么说明x太大了,下一步要缩小x
        return cnt<=m;
    }
public:
    int splitArray(vector<int>& nums, int m) {
        long long left = 0, right = 0;
        int n = nums.size();
        for(int i = 0;i< n ;++i){
            right+=nums[i];
            if(nums[i]>left){
                left = nums[i];
            }
        }
        while(left<right){
            long long mid = (left+right)>>1;
            //当check == true说明mid这个数大了,将搜索范围设置在左侧(较小侧)进行搜索。
            if(check(nums,mid,m)){
                right = mid;
            }
            else{
                left = mid+1;
            }
        }
        return left;
    }
};
LCP 12. 小张刷题计划
class Solution {
    bool check(vector<int>& time ,int x ,int m){
        int cnt = 1;
        long long totaltime = 0;
        int maxtime = 0;
        for(int i = 0;i< time.size() ;++i){
            //将最大值和当前的数进行对比,将较小的添加进入,这样便将整个数组的最大项进行保存并始终使最大项不加入数组和中,得到的数组和是连续数组不包含最大项的数组和
            int nexttime = min(maxtime,time[i]);
            if(nexttime + totaltime <= x){
                totaltime+=nexttime;
                //记录当前最大值
                maxtime = max(maxtime,time[i]);
            }
            else{
                ++cnt;
                totaltime = 0;
                maxtime = time[i];
            }
        }
        return cnt <= m;
    }
public:
    int minTime(vector<int>& time, int m) {
        int n = time.size();
        long long left = 0, right = 0;
        for(int i = 0;i< n;++i){
            right+=time[i];
        }
        while(left<right){
            int mid = (left+right)>>1;
            if(check(time,mid,m)){
                right = mid;
            }else{
                left =mid+1;
            }
        }
        return left;
    }
};
887. 鸡蛋掉落
//二分法加动态规划
class Solution {
    //由于二分法的递归原因,采用哈希map作为dp。k<=100,所以100*n+k为每个dp[n][k]的对应位置
    unordered_map<int, int> map;
    int dp(int k, int n) {
        //当map中不存在n * 100 + k,表示dp[n][k]尚未被遍历
        //n为楼层,k为鸡蛋数量
        //对每一个dp[i][j]进行一次二分搜索
        if (map.find(n * 100 + k) == map.end()) {
            int ans;
            if (n == 0) {
                ans = 0;
            } else if (k == 1) {
                ans = n;
            } else {
                int left = 1, right = n;
                while (left + 1 < right) {
                    int x = (left + right) / 2;
                    int t1 = dp(k - 1, x - 1);
                    int t2 = dp(k, n - x);
                    if (t1 < t2) {
                        left = x;
                    } else if (t1 > t2) {
                        right = x;
                    } else {
                        left = right = x;
                    }
                }
                
                ans = 1 + min(max(dp(k - 1, left - 1), dp(k, n - left)),
                        max(dp(k - 1, right - 1), dp(k, n - right)));
            }
            map[n * 100 + k] = ans;
        }
        return map[n * 100 + k];
    }
public:
    int superEggDrop(int k, int n) {
        return dp(k, n);
    }
};
1723. 完成所有工作的最短时间

方法1: 暴力回溯



方法2:剪枝

  1. 对数组进行降序排序

  2. 如果当前工人未被分配工作,那么下一个工人也必然未被分配工作
    或者当前工作恰能使该工人的工作量达到了上限

  3. 当工人 i 还没被分配工作时,我们不给工人 i+1分配工作


class Solution {
private:
    int temp = 0;
    bool binarySearch(vector<int>& jobs, int k ,int target){
        vector<int> workloads(k,0);
        return backtrack(jobs,workloads,0,target);
    }
    bool backtrack(vector<int>& jobs, vector<int>& workloads, int idx, int limit) {
        if (idx >= jobs.size()) {
            return true;
        }
        int cur = jobs[idx];
        for(int i = 0;i< workloads.size() ;++i){
            if(workloads[i]+cur > limit){
                continue;
            }
            workloads[i]+=cur;
            if(backtrack(jobs,workloads,idx+1,limit)){
                return true;
            };
            workloads[i]-=cur;
            if (workloads[i] == 0 || workloads[i] + cur == limit) {
                break;
            }
        }
        return false;
    }
public:
    int minimumTimeRequired(vector<int>& jobs, int k) {
        int n = jobs.size();
        //采用sort排序后可以有效剪枝,因为处理更耗时间的更加
        
        sort(jobs.begin(), jobs.end(), greater<int>());
        int left = jobs[0];
        int right = 0;
        for(int i = 0;i< n ;++i){
            right+=jobs[i];
        }
        while(left<right){
            int mid = (left+right)>>1;
            if(binarySearch(jobs,k,mid)){
                right = mid;
            }else{
                left = mid+1;
            }
        }
        return left;
    }
};

LeetCode动态规划类题目详解 一文学会动态规划算法 阅读详情

相关推荐

LeetCode算法刷题笔记,值得参考!

LeetCode算法刷题笔记

m0_55849656的博客 1020

Leetcode动态规划专题(共38道)

某一问题有很多重叠子问题 每一状态一定由上一状态推导出来 而贪心没有状态推导,而是直接选局部最优 解决方式: 确定dp数组(dp table)以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组 模拟:举例推导dp数组 检查:打印dp数组 1.Leetcode509. 斐波那契数 class Solution { public: int fib(int n) { //第i个数的值 vector<int>n

weixin_45627369的博客 6752

单细胞数据导入实战:Read10X函数常见报错与解决方案

本文针对单细胞转录组分析中Read10X函数数据导入的常见问题,提供了详细的实战解决方案。重点解析了因文件路径错误、压缩文件未解压、版本命名不符及内存不足导致的报错,并给出了检查路径、正确解压、重命名文件、调整参数及优化内存等具体操作步骤,帮助用户高效完成单细胞数据导入,为后续分析奠定坚实基础。

weixin_28235513的博客 205

动态规划经典例题leetcode思路代码详解

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。这一篇的例题解答是严格按照我上一篇写的动态规划三部曲做的,对动态规划不太了解或者比较感兴趣的朋友可以看我上一篇文章。给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。个台阶向上爬需要支付的费用。,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

xiaoliii0401的博客 1859

leetcode 系列之-动态规划题型总结

动态规划问题一般性总结 动态规划题目通常用于解决计数类问题。常见有线性DP,区间DP,背包问题等。解决背包问题通常要明白下面几点。 属性:Max / Min /数量 动态规划问题最终通常让求解某一问题的最大值,最小值,数量。出现这几个字段,我们就要考虑是否应用动态规划能够解决该问题。 比如说: 最大连续子序列和,最长不下降子序列。 状态表示: 解决动态规划问题的一个关键点就是 状态表示,状态表示根据问题而变化,最终能求出问题的最优解。状态表示的本质就是记录子问题的解,来避免下次遇到相同子问题重复计算。比

weixin_39802680的博客 1011

[leetcode] 题型整理动态规划

动态规划属于技巧性比较强的题目,如果看到过原题的话,对解题很有帮助 55. Jump Game Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maxi...

weixin_33920401的博客 136

动态规划十大经典题型状态转移、模版等整理(包括leetcode、洛谷题号)

LeetCode题号:1143。LeetCode题号:72。动态规划十大经典题目整理。洛谷OJ题号:P2758。洛谷OJ题号:P1439。

Jaylon Wang的专栏 704

【力扣Hot 100+经典题型】刷题记录(Python版)

力扣hot 100刷题记录

我的技术栈碎碎念空间 7667

Leetcode文章整理

LeetCode的题目种类比较多,感觉应该将自己联系过的题目进行分类,这个就是根据自己做过的题目进行划分,并做一定的总结,会持续更新 Sort: Two Pointer: 二叉树: DP: Math: 栈: DFS: LeetCode—**Combination Sum 利用DFS算法 Recursi

xietingcandice的专栏 634

我的LeetCode刷题笔记会包含哪些内容?

关注微信公众号“小争哥”,回复PDF获取Google工程师的算法学习和面试经验 最近就要开始刷题了,登上LeetCode,面对两千多道题,感觉有点手足无措,该从哪一题开始刷起呢?按照什么顺序来刷呢? 我个人觉得有几种刷法肯定先要直接pass掉。第一种是按照编号顺序刷,这个不用多解释。第二种是按照难易程度刷,先把easy的全刷完,再把medium的全刷完,最后刷hard的。从易到难的原则不错,但是,刷题顺序绝不能仅仅只参考这一个原则。 最被大家认可的刷题顺序应该是按照题型来刷,这样做的目的是强化训练、刻意.

小争哥的博客 216

拿到腾讯字节快手 offer 后,他的 LeetCode 刷题经验在 GitHub 火了!

杨净 发自 凹非寺量子位 报道 | 公众号 QbitAI今天,你刷LeetCode了吗?最近,GitHub上这样一套LeetCode笔记火了。一位Java研发工程师分享了一个名为「Le...

zandaoguang的博客 346

LeetCode | 图文详细描述动态规划DP算法及经典题型

F(0)=0F(1)=1F(n)=F(n−1)+F(n−2)(当 n≥2)例:n = 211 是斐波那契数列的第 2 个数字。n = 555 是斐波那契数列的第 5 个数字。

weixin_44649780的博客 2797

力扣双指针刷题(算法日记)

思路:用一个变量odd指向奇数(初始为1)dou指向偶数(初始为0) 然后盯着最后一个元素看,如果是奇数,那么和odd指向的变量交换,同时odd指向下一个奇数,偶数同理,当odd或者dou有一个越界了(指向的位置大于等于length)就说明奇数或者偶数已经摆好了,因为奇偶个数相同,所以另一者也一定摆好了,这个时候的数组即符合要求。直到两个指针相邻或相撞。上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

Jerryle__的博客 1989

Leetcode-动态规划典型题

LC053.Maximum Subarray最大子数组和 一、题目 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 示例 1: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组[4,-1,2,1] 的和最大,为6 。 示例 2: 输入:nums = [1] 输出:1 示例 3: 输入:nums = [5,4,-1,7,8] 输出:23 提示...

JINGYUSHAN的博客 770

Leetcode动态规划题目

Leetcode10 正则表达式匹配 class Solution { public: bool isMatch(string s, string p) { int n = s.size(), m = p.size(); s = ' ' + s, p = ' ' + p; vector<vector<bool>> f(n+1,vector<bool>(m+1)); //vector<int> vec(

weixin_44972129的博客 744

leetcode算法专题训练:十二.动态规划专题

十二.动态规划专题 62.不同路径 63.不同路径2 剑指 Offer 13. 机器人的运动范围 剑指 Offer 47. 礼物的最大价值 64.最小路径和 120.三角形最小路径和 118.杨辉三角 119.杨辉三角2 343. 整数拆分 53.最大子序和 5.最长回文子串 44.通配符匹配 10.正则表达式匹配 97.交错字符串 72.编辑距离 剑指 Offer 46. 把数字翻译成字符串 91.解码方法 115.不同的子序列 139.单词拆分 140.单词拆分2 剑指 Offer 49. 丑数 剑.

Worthy_Wang的博客 1068

代码随想录python笔记8 贪心

贪心

m0_63843129的博客 193

LaMAR数据集下载和使用流程

LaMAR数据集

qq_51745206的博客 922

基于stm32的睡眠质量&环境检测仪(可做为热成像仪使用

基于stm32的睡眠质量&环境检测仪(可做为热成像仪使用

下一篇: leetcodeDFS题型汇总
A24565
博客等级 码龄6年 0粉丝 2原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值