代码随想录-高级篇之动态规划算法

思考

动态规划5步骤
- 1. dp数组的定义及其下标的含义
- 2. 递推公式
- 3. dp数组如何初始化
- 4. 遍历顺序
- 5. 打印dp数组

基础题目

斐波拉契数

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动态规划五部曲
    1. 确定dp[i]的含义:表示第i个斐波拉契数的值为dp[i]
    2. 地推公式:dp[i] = dp[i-1] + dp[i-2]
    3. dp数组如何初始化 dp[0]=1,dp[1]=2
    4. 遍历顺序  从前往后
    5. 打印dp数组
    */
    int fib(int n)
    {
        if (n == 0)
            return 0;
        if (n == 1 || n == 2)
            return 1;
        vector<int> dp(n + 1);
        dp[0] = dp[1] = 1;
        for (int i = 2; i <= n; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n - 1];
    }
};

爬楼梯

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动态规划五部曲
    1. 确定dp[i]的含义:表示到达第i阶台阶有dp[i]种方法
    2. 地推公式:dp[i] = dp[i-1] + dp[i-2]
    3. dp数组如何初始化 dp[0]=0,dp[1]=1,dp[2] = 2
    4. 遍历顺序  从前往后
    5. 打印dp数组
    */
    int climbStairs(int n)
    {
        if (n == 1)
            return 1;
        if (n == 2)
            return 2;

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

使用最小花费爬楼梯

在这里插入图片描述


#include <iostream>
#include <vector>
#include <algorithm>
using std::vector;

class Solution
{
public:
    /*
        输入:cost = [10,15,20]
           终点是:下标为3的位置
    */
    /*
    动态规划5步骤
        - 1. dp数组的定义及其下标的含义
            dp[i] 到达i位置的花费
        - 2. 递推公式
            dp[i] = dp[i-1] + cost[i-1]
            dp[i] = dp[i-2] + cost[i-2]

            dp[i] = min(dp[i-1] + cost[i-1],dp[i-2] + cost[i-2])
        - 3. dp数组如何初始化
            dp[0] =  0
            dp[1] =  0
        - 4. 遍历顺序
            从前往后遍历
        - 5. 打印dp数组

    */

    int minCostClimbingStairs(vector<int> &cost)
    {
        vector<int> dp(cost.size() + 1);
        dp[0] = 0;
        dp[1] = 0;
        int i = 2;
        while (i <= cost.size()) // 终点是:下标为3的位置
        {
            dp[i] = std::min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
            i++;
        }
        return dp[i - 1];
    }
};

在这里插入图片描述

未初始化 dp 数组的大小:dp 是 vector 类型,但未指定大小,直接访问 dp[0] 和 dp[1] 会导致未定义行为(如崩溃)。

不同路径I

在这里插入图片描述
在这里插入图片描述

  • dp[i]的含义理解很重要。
#include <iostream>
#include <vector>
#include <algorithm>
using std::vector;

class Solution
{
public:
    /*递归五部曲
        1. dp[i][j]的含义 
            从(0,0)到达(i,j)有几种路径
        2. 递归公式
            dp[i][j] = dp[i-1][j]+dp[i][j-1] ; (i>1,j>1)
        3. dp的初始化
                        dp[i][j] = 1;(i=0,j=0)
        4. 遍历顺序
            从左往右,从上往下
        5. 打印dp数组
    */
    int uniquePaths(int m, int n)
    {
        // vector定义二维数组!!!!
        vector<vector<int>> dp(m, vector<int>(n, 0)); // 定义m个大小为n的vector(初始化为0)
        for (int i = 0; i < m; i++)                   // 注意这里需要从0开始,因为需要考虑m=1时
        {
            dp[i][0] = 1;
        }
        for (int i = 0; i < n; i++) // 注意这里需要从0开始,因为需要考虑n=1时
        {
            dp[0][i] = 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];
    }
};

不同路径II

在这里插入图片描述
在这里插入图片描述

注意:无论条件如何改变,主要关注于动态五部曲是否发送改变,一条一条进行梳理。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int row = obstacleGrid.size();
        int col = obstacleGrid[0].size();
        
        if(row == 0 || obstacleGrid[0][0] == 1)
            return 0;
        
        if(row == 1 && col == 1)
        {
            return 1;
        }        
        vector<vector<long>> dp(row, vector<long>(col, 0)); //申请动态规划的数组,初始化为0
        for(int i = 1; i < row; i++)
        {
            if(obstacleGrid[i][0] != 1)
                dp[i][0] = 1;
            else
                break;
        }
        for(int i = 1; i < col; i++)
        {
            if(obstacleGrid[0][i] != 1)
                dp[0][i] = 1;
            else
                break;
        }
        
        for(int i = 1; i < row; i++)
        {
            for(int j = 1; j < col; j++)
            {
                if(obstacleGrid[i][j] != 1)        
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[row-1][col-1];
    }
};

整数拆分

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
using std::vector;

class Solution
{
public:
    /*递归五部曲
        1. dp[i]的含义
                dp[i]:拆分数i的最大乘积为dp[i]
        2. 递归公式(障碍物处无路径)
            dp[i] = max((i-j)*j,dp[i-j]*j)    (直接拆封成两个数,拆分成多个数)
            
        3. dp的初始化
            dp[0] = dp[1] = 0;
            dp[2] =1;
        4. 遍历顺序
            从前往后
        5. 打印dp数组
    */
    // 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
    int integerBreak(int n)
    {
        vector<int> dp(n + 1, 0);
        dp[0] = 0;
        dp[1] = 0; // 注意k>=2
        dp[2] = 1;
        for (int i = 3; i <= n; i++)
        {
            for (int j = 1; j < i; j++)
            {
                // 使用std::max比较2个以上的数
                dp[i] = std::max(dp[i], std::max((i - j) * j, dp[i - j] * j));
            }
        }
        return dp[n];
    }
};

注意不要忘记max(dp[i])

不同的二叉搜索树

在这里插入图片描述

递归解法

class Solution {
public:
    // 计算从节点值为left到节点值为right的范围内,可以组成的二叉搜索树的数量
    int GetExactNodeNum(int left, int right) {
        // 如果left等于right,说明只有一个节点,只有一种树的结构(单个节点本身)
        if (left == right) return 1;
 
        // 初始化当前范围内的树的数量为0
        int num = 0;
 
        // 遍历当前范围内的所有节点值,尝试将每个节点值作为根节点
        for (int i = left; i <= right; i++) {
            // 如果当前节点值i是范围内的最小值(即left),则没有左子树
            // 只需要计算右子树的数量(从i+1到right)
            if (i == left) num += GetExactNodeNum(i + 1, right);
            // 如果当前节点值i是范围内的最大值(即right),则没有右子树
            // 只需要计算左子树的数量(从left到i-1)
            else if (i == right) num += GetExactNodeNum(left, i - 1);
            // 如果当前节点值i既不是最小值也不是最大值,则既有左子树也有右子树
            // 左子树的数量为从left到i-1的树的数量
            // 右子树的数量为从i+1到right的树的数量
            // 以i为根节点的树的数量为左子树数量乘以右子树数量
            else {
                num += GetExactNodeNum(left, i - 1) * GetExactNodeNum(i + 1, right);
            }
        }
 
        // 返回当前范围内可以组成的二叉搜索树的数量
        return num;
    }
 
    // 计算由n个节点组成的二叉搜索树的数量
    int numTrees(int n) {
        // 调用GetExactNodeNum函数,从值为1的节点到值为n的节点
        return GetExactNodeNum(1, n);
    }
};

动态规划(连贯的思路)

在这里插入图片描述

#include <vector>
#include <string>
using namespace std;
class Solution
{
public:
    /*
        1. dp[i] i个数字有dp[i]种的二叉搜索树
        2. 递归公式
            dp[i] += dp[j-1] *dp[i-j] (j--->j)
                头节点是j
                    左子树:j-1(比j小的有j-1个)
                    右子树: i-j(比j大的有i-j个)
        3. 初始化
            dp[0] = 1;
            dp[1] = 1;
            dp[2] = 2;
    */
    // 动态规划函数,用于计算ans[n]的值

    // 主函数,计算由n个节点组成的BST的数量
    int numTrees(int n)
    {
        if (n == 0 || n == 1)
            return 1;
        else if (n == 2)
            return 2;
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++)
        {
            for (int j = 1; j <= i; j++) // 注意 j也可以等于i,即i也可以自己当头节点
            {
                dp[i] += (dp[j - 1] * dp[i - j]);
            }
        }
        return dp[n];
    }
};

0-1背包问题

0-1背包

二维数组解决0-1背包
  • 0-1背包
    n种物品,每种物品只有一个
  • 递归五部曲
    • dp数组的含义:dp[i][j]:[0~i]物品,任取放入容量为j的背包中。

    • 递推公式

      • 在这里插入图片描述
    • dp数组的初始化

      • 在这里插入图片描述
    • 遍历顺序

      • 二维的dp数组
        • 先遍历背包再物品----可以
        • 先遍历物品再背包----可以
    • 打印

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n, bagweight; // bagweight代表行李箱空间

    cin >> n >> bagweight;

    vector<int> weight(n, 0); // 存储每件物品所占空间
    vector<int> value(n, 0);  // 存储每件物品价值

    for (int i = 0; i < n; ++i)
    {
        cin >> weight[i];
    }
    for (int j = 0; j < n; ++j)
    {
        cin >> value[j];
    }

    // dp数组, dp[i][j]代表行李箱空间为j的情况下,从下标为[0, i]的物品里面任意取,能达到的最大价值
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

    // 初始化, 因为需要用到dp[i - 1]的值
    // j < weight[0]已在上方被初始化为0
    // j >= weight[0]的值就初始化为value[0]
    for (int j = weight[0]; j <= bagweight; j++)
    { // 装不下物品0的背包就为0,可以装下的就为value[0]
        dp[0][j] = value[0];
    }

    for (int i = 1; i < weight.size(); i++)
    { // 遍历科研物品
        for (int j = 0; j <= bagweight; j++)
        { // 遍历行李箱容量
            if (j < weight[i])
                dp[i][j] = dp[i - 1][j]; // 如果装不下这个物品,那么就继承dp[i - 1][j]的值
            else
            {
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
            }
        }
    }
    cout << dp[n - 1][bagweight] << endl;

    return 0;
}

一维数组解决0-1背包

在这里插入图片描述

  • 递归五部曲
    • dp数组的含义:dp[j]:容量为j的背包的最大价值

    • 递推公式

      • dp[j] = max(dp[j],dp[j-weight[i]]+value[i]) (加入物品i,不加入物品i)
    • dp数组的初始化

    • 遍历顺序

      • 在这里插入图片描述
    • 打印


/ 一维dp数组实现
#include <iostream>
#include <vector>
    using namespace std;

int main()
{
    // 读取 M 和 N
    int M, N;
    cin >> M >> N;

    vector<int> costs(M);
    vector<int> values(M);

    for (int i = 0; i < M; i++)
    {
        cin >> costs[i];
    }
    for (int j = 0; j < M; j++)
    {
        cin >> values[j];
    }

    // 创建一个动态规划数组dp,初始值为0
    vector<int> dp(N + 1, 0);

    // 外层循环遍历每个类型的研究材料
    for (int i = 0; i < M; ++i)
    {
        // 内层循环从 N 空间逐渐减少到当前研究材料所占空间
        for (int j = N; j >= costs[i]; --j)
        {
            // 考虑当前研究材料选择和不选择的情况,选择最大值
            dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
        }
    }

    // 输出dp[N],即在给定 N 行李空间可以携带的研究材料最大价值
    cout << dp[N] << endl;

    return 0;
}

分割等和子集

在这里插入图片描述

回溯方法(暴搜)

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

class Solution
{
public:
    bool backtracking(vector<int> &nums, int startIndex, int target, int curSum)
    {
        // 终止条件
        if (curSum == target)
            return true;
        if (curSum > target)
            return false;

        // 单层递归逻辑
        for (int i = startIndex; i < nums.size(); i++)
        {
            curSum += nums[i];
            if (backtracking(nums, i + 1, target, curSum))
                return true;
            curSum -= nums[i]; // 回溯
        }

        return false;
    }

    bool canPartition(vector<int> &nums)
    {
        int total = accumulate(nums.begin(), nums.end(), 0);
        if (total % 2 != 0)
            return false;
        int target = total / 2;

        // 排序优化:从大到小排列,加快剪枝效率
        sort(nums.begin(), nums.end(), greater<int>());

        return backtracking(nums, 0, target, 0);
    }
};
动态规划算法

翻译,就是这个集合中有没有,个数少于集合中元素的个数的和等于整个集合和的一半。
就是集合中的元素为物品,看是否里面的物品可以将背包(容量为和的一半)能否装满。重量就是价值。即dp[sum/2] ?= sum/2

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

class Solution
{
public:
    bool canPartition(vector<int> &nums)
    {

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);

        // 也可以使用库函数一步求和
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1)
            return false;
        int target = sum / 2;

        // 开始 01背包
        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]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target)
            return true;
        return false;
    }
};

最后一块石头的重量

在这里插入图片描述
在这里插入图片描述


#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

class Solution
{
public:
    // 总体思路:让重量相近的石头先碰撞。
    //          石头分成两堆:总和一半/2,总和-总和一半的一堆  而且 前一堆>后一堆  向下取整
    // 最后的结果:
    /*动规五部曲
        1. dp[j]的含义
            重量为j的背包的最大价值为dp[j]
        2. 递归表达式
            dp[j] = max(dp[j],dp[j-weight[i]]+weight[i])
        3. 初始化
            dp[0] = 0;
            所有初始化为0
        4. 遍历顺序
        5. 打印
    */
    int lastStoneWeightII(vector<int> &stones)
    {
        vector<int> dp(1501, 0);
        int target = accumulate(stones.begin(), stones.end(), 0) / 2;
        for (int i = 0; i < stones.size(); ++i) // 先遍历物品
        {
            for (int j = target; j >= stones[i]; --j) // 遍历背包,从大往小遍历
            {                                         // j >= stones[i] 背包容量不能小于石头的容量
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }

        return (accumulate(stones.begin(), stones.end(), 0) - dp[target]) - dp[target];
    }
};

目标和(不是很懂)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

回溯

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

class Solution
{
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int> &candidates, int target, int sum, int startIndex)
    {
        if (sum == target)
        {
            result.push_back(path);
        }
        // 如果 sum + candidates[i] > target 就终止遍历
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
        {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i + 1);
            sum -= candidates[i];
            path.pop_back();
        }
    }

public:
    int findTargetSumWays(vector<int> &nums, int S)
    {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++)
            sum += nums[i];
        if (S > sum)
            return 0; // 此时没有方案
        if ((S + sum) % 2)
            return 0;                // 此时没有方案,两个int相加的时候要格外小心数值溢出的问题
        int bagSize = (S + sum) / 2; // 转变为组合总和问题,bagsize就是要求的和

        // 以下为回溯法代码
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 需要排序
        backtracking(nums, bagSize, 0, 0);
        return result.size();
    }
};
动态规划

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>

using namespace std;

class Solution
{
public:
    int findTargetSumWays(vector<int> &nums, int target)
    {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++)
            sum += nums[i];
        if (abs(target) > sum)
            return 0; // 此时没有方案
        if ((target + sum) % 2 == 1)
            return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;
        vector<int> dp(bagSize + 1, 0);
        dp[0] = 1;
        /*
if (nums[i] > j)    dp[i][j] = dp[i - 1][j];
else      dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
        */
        for (int i = 0; i < nums.size(); i++)
        {
            for (int j = bagSize; j >= nums[i]; j--)
            {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[bagSize];
    }
};

一和零

在这里插入图片描述

回溯(超时)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 使用回溯
class Solution
{
public:
    /*
        1. dp数组的含义

        2. 递推公式
        3. 初始化
        4. 遍历顺序
        5. 打印
    */
    vector<string> sub;
    int max;
    int coutZero()
    {
        int coutZero = 0;
        for (string s : sub)
        {
            coutZero += count(s.begin(), s.end(), '0');
        }
        return coutZero;
    }

    int coutOne()
    {
        int coutOne = 0;
        for (string s : sub)
        {
            coutOne += count(s.begin(), s.end(), '1');
        }
        return coutOne;
    }

    void backtracking(vector<string> &strs, int m, int n, int startIndex)
    {
        if (coutZero() > m || coutOne() > n) // 剪枝
        {
            return;
        }
        else
        {
            if (sub.size() > max)
            {
                max = sub.size();
            }
        }
        for (int i = startIndex; i < strs.size(); i++)
        {
            sub.push_back(strs[i]);
            sub.shrink_to_fit();
            backtracking(strs, m, n, i + 1);
            sub.pop_back();
            sub.shrink_to_fit();
        }
    }

    int findMaxForm(vector<string> &strs, int m, int n)
    {
        max = 0;
        backtracking(strs, m, n, 0);
        return max;
    }
};
动态规划
#include <numeric>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 使用回溯
class Solution
{
public:
    /*
    注意:每个物品的重量:x个0,y个1
    背包的容量:m个0,n个1
    注意:这里使用的二维数组,但是实际上是一维数组的解法,因为这里的重量有两个维度
        1. dp数组的含义
            dp[i][j]:i个0,j个1,最大背dp[i][j]个物品   最后求dp[i][j]
        2. 递推公式
            放入物品:dp[i-x][j-y]+1
            不放入物品:dp[i][j]
            dp[i][j] = max(dp[i-x][j-y]+1,dp[i][j])
        3. 初始化
        4. 遍历顺序
        5. 打印
    */

    int findMaxForm(vector<string> &strs, int m, int n)
    {
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for (string str : strs) // 先遍历物品
        {
            // 计算0的个数,1的个数
            int x = 0, y = 0;
            for (char c : str)
            {
                if (c == '0')
                    x++;
                else
                    y++;
            }
            // 再遍历背包
            for (int i = m; i >= x; i--) // 遍历0(也可以先遍历1)
            {
                for (int j = n; j >= y; j--) // 遍历1
                {
                    dp[i][j] = max(dp[i][j], dp[i - x][j - y] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

0-1背包问题总结

1. 纯0-1背包:装满这么大容量的最大价值
2. 分割等和子集
    能否装满背包
3. 最后一块石头的重量
    这么大容量的背包最多装多少
4. 目标和问题
    装满这么多,有多少种方法
5. 一和零问题
    装满背包,有多少个物品

统一使用一维数组进行求解:
	for (int i = 0; i < nums.size(); i++)    // 先遍历背包(0->bgsize)
	{
		for (int j = target; j >= nums[i]; j--) // 再遍历物品(target->???) j >= nums[i]:表示背包容量必须大于物品重量
		{ 
			递推公式
		}
	}
		
1.0-1背包:装满这么大容量的最大价值
	    dp[j] = max(dp[j],dp[j-weight[i]]+value[i])   (不放物品i,放物品i)
		

2. 分割等和子集
    能否装满背包
	背包的容量就是集合的和的一半,物品就是集合中的元素
	dp[j]数组的含义:容量为j的背包的最大价值
	递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
	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]);
        }
    }
3. 最后一块石头的重量
    这么大容量的背包最多装多少
	本质:让质量相近的石头碰撞。石头分成两堆,1. 一半的一堆,2. 总和-一半的  而且 121堆的和除以2会向下取整)
	结果就是:2堆的和-1堆的和
	dp[j]数组的含义:重量为j的背包的最大价值为dp[j]
	递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
	for (int i = 0; i < stones.size(); ++i) // 先遍历物品
    {
        for (int j = target; j >= stones[i]; --j) // 遍历背包,从大往小遍历
        {                                         // j >= stones[i] 背包容量不能小于石头的容量
            dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
        }
    }
	
4. 目标和问题
    装满这么多,有多少种方法
	
	非负数组nums和一个整数target,添加+-。
	加法的总和:x  减法的总和sum-x  目标:target=2x-sum
	-》转为背包问题:背包重量target,物品就是集合中的元素
	dp[j]数组的含义:背包重量为j时有dp[j]中方法
	递推公式:dp[j] += dp[j - nums[i]];             ???????????????????????????
	for (int i = 0; i < nums.size(); i++)
    {
        for (int j = bagSize; j >= nums[i]; j--)
        {
            dp[j] += dp[j - nums[i]];
        }
    }
5. 一和零问题
    装满背包,有多少个物品
	
	二进制数组strs,m,n,返回strs的最大子集,含最多m个0,n个1
	dp[i][j]数组的含义:i个0,j个1,最大背dp[i][j]个物品   最后求dp[i][j]
	递推公式:dp[i][j] = max(dp[i][j], dp[i - x][j - y] + 1);
	 for (string str : strs) // 先遍历物品
     {
         // 计算0的个数,1的个数
         int x = 0, y = 0;
         for (char c : str)
         {
             if (c == '0')
                 x++;
             else
                 y++;
         }
         // 再遍历背包
         for (int i = m; i >= x; i--) // 遍历0(也可以先遍历1)
         {
             for (int j = n; j >= y; j--) // 遍历1
             {
                 dp[i][j] = max(dp[i][j], dp[i - x][j - y] + 1);
             }
         }
     }
	

完全背包问题

完全背包理论

在这里插入图片描述

  • 纯完全背包问题,两个for循环是可以颠倒的

零钱兑换II

在这里插入图片描述

回溯

超时

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution
{
public:
    int result;
    vector<vector<int>> elems;
    vector<int> elem;
    bool backing(int curSum, int amount, vector<int> &coins)
    {
        if (curSum == amount)
            return true;
        else if (curSum > amount)
        {
            return false;
        }

        for (int i = 0; i < coins.size(); i++)
        {
            curSum += coins[i];
            elem.push_back(coins[i]);
            bool ret = backing(curSum, amount, coins);
            if (ret)
            {
                vector<int> tmp(elem);
                sort(tmp.begin(), tmp.end());
                vector<vector<int>>::iterator it = find(elems.begin(), elems.end(), tmp);
                if (it == elems.end()) // elems不存在elem元素
                {
                    elems.push_back(elem);
                    for_each(elem.begin(), elem.end(), [](int x)
                             { cout << x << " "; });
                    cout << endl;
                    ++result;
                }
                // elem.clear();// ❌ 会影响回溯中其它分支
            }
            curSum -= coins[i];
            elem.pop_back();
        }

        return false;
    }
    int change(int amount, vector<int> &coins)
    {
        if (amount == 0)
            return 1;
        result = 0;
        backing(0, amount, coins);
        return result;
    }
};

动态规划

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution
{
public:
    int change(int amount, vector<int> &coins)
    {
        /*回溯5部曲
            1. dp[j]含义
                dp[j]: amount为j是,求和的方法数
            2. 递推公式(有多少种方法,都是这个递推公式:一般dp[0]=1)
                dp[j] += dp[j - coins[i]];
            3. 初始化
                dp[0] = 1;  // 背包为0,默认有1种装法
            4. 遍历顺序
            5. 打印

        */
        // vector<int> dp(amount + 1, 0);
        vector<uint64_t> dp(amount + 1, 0); // 防止相加数据超int  题目数据保证结果符合 32 位带符号整数。

        dp[0] = 1;
        for (int i = 0; i < coins.size(); i++) // 先遍历物品
        {
            for (int j = coins[i]; j <= amount; j++) // 再遍历背包
            {
                // dp[j] = max(dp[j], dp[j - coins[i]] + coins[i]);
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

组合总和

在这里插入图片描述

回溯算法

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Solution
{
public:
    int result;
    vector<int> elem;
    bool backing(int curSum, int amount, vector<int> &coins)
    {
        if (curSum == amount)
            return true;
        else if (curSum > amount)
        {
            return false;
        }

        for (int i = 0; i < coins.size(); i++)
        {
            curSum += coins[i];
            elem.push_back(coins[i]);
            bool ret = backing(curSum, amount, coins);
            if (ret)
            {
                    ++result;
                // elem.clear();// ❌ 会影响回溯中其它分支
            }
            curSum -= coins[i];
            elem.pop_back();
        }

        return false;
    }
    int combinationSum4(vector<int> &nums, int target)
    {
        if (target == 0)
            return 1;
        result = 0;
        backing(0, target, nums);
        return result;
    }
};
;

动态规划IV

在这里插入图片描述

  • 先遍历物品再遍历背包,得到的是组合数
  • 先遍历背包再遍历物品,得到的是排列数

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    int combinationSum4(vector<int> &nums, int target)
    {
        vector<unsigned int> dp(target + 1, 0);
        dp[0] = 1;
        for (int i = 0; i <= target; i++)
        { // 遍历背包
            for (int j = 0; j < nums.size(); j++)
            /*
            j < nums.size() && i >= nums[j] 的条件会导致 j 的循环在 i >= nums[j] 不满足时提前终止。
            这意味着如果 nums 中有多个元素,其中一些满足 i >= nums[j] 而另一些不满足,
            循环会在第一个不满足条件的 j 处终止,从而跳过后续可能满足条件的 j
            */
            { // 遍历物品
                if (i >= nums[j])
                    dp[i] += dp[i - nums[j]];
            }
        }
        return dp[target];
    }
};

零钱兑换问题

在这里插入图片描述

回溯

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    int result;
    vector<vector<int>> elems;
    vector<int> elem;
    int min;
    bool backing(unsigned int curSum, int amount, vector<int> &coins)
    {
        if (curSum == amount)
            return true;
        else if (curSum > amount)
        {
            return false;
        }

        for (int i = 0; i < coins.size(); i++)
        {
            curSum += coins[i];
            elem.push_back(coins[i]);
            bool ret = backing(curSum, amount, coins);
            if (ret)
            {
                ++result;
                if (min > elem.size())
                {
                    min = elem.size();
                }
            }
            curSum -= coins[i];
            elem.pop_back();
        }

        return false;
    }
    int coinChange(vector<int> &coins, int amount)
    {
        min = INT_MAX;
        if (amount == 0)
            return 0;
        backing(0, amount, coins);
        if (min == INT_MAX)
            return -1;

        return min;
    }
};

动态规划


#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义:dp[j] 装满量为j的背包,最少的数目为dp[j]
        2. dp递推公式
            dp[j]  = min(dp[j-conis[i]],dp[j])
        3. 初始化
        4. 遍历顺序
        5. 打印
    */
    int coinChange(vector<int> &coins, int amount)
    {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i < coins.size(); i++)
        { // 遍历物品
            for (int j = coins[i]; j <= amount; j++)
            { // 遍历背包
                if (dp[j - coins[i]] != INT_MAX)
                { // 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        if (dp[amount] == INT_MAX)
            return -1;
        return dp[amount];
    }
};

完全平方数

在这里插入图片描述

在这里插入图片描述

动态规划

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义:dp[j] 装满量为j的背包,最少的数目为dp[j]
        2. dp递推公式
            dp[j]  = min(dp[j-conis[i]],dp[j])
        3. 初始化
        4. 遍历顺序
        5. 打印
    */
    // int numSquares(int n);

    int numSquares(int n)
    {

        vector<int> coins;
        for (int i = 1; i <= n && i * i <= n; ++i)
        {
            coins.push_back(i * i);
        }
        coins.shrink_to_fit();
        int amount = n;

        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i < coins.size(); i++)
        { // 遍历物品
            for (int j = coins[i]; j <= amount; j++)
            { // 遍历背包
                if (dp[j - coins[i]] != INT_MAX)
                { // 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        if (dp[amount] == INT_MAX)
            return -1;
        return dp[amount];
    }
};

单词拆分

在这里插入图片描述

纯回溯

#include <iostream>
#include <vector>
using namespace std;

class Solution
{
public:
    // 纯回溯判断从start位置开始,能否用wordDict拼接成功
    bool backing(const string &s, vector<string> &wordDict, int start)
    {
        // 到达字符串末尾说明匹配成功
        if (start == s.size())
            return true;

        // 尝试字典中每个单词
        for (int i = 0; i < wordDict.size(); ++i)
        {
            int len = wordDict[i].size();
            // 判断当前单词是否能匹配s的[start, start+len)子串
            if (start + len <= s.size() && s.substr(start, len) == wordDict[i])
            {
                // 匹配成功则递归判断剩余部分
                if (backing(s, wordDict, start + len))
                    return true;
            }
        }

        // 所有单词均不匹配则返回false
        return false;
    }

    bool wordBreak(string s, vector<string> &wordDict)
    {
        return backing(s, wordDict, 0);
    }
};

回溯+记忆

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    // 记忆化数组,memo[i]表示从字符串s的第i个位置开始是否能成功分割
    // 0 表示未访问,1 表示能分割成功,-1 表示不能分割成功
    vector<int> memo;

    // 递归函数,从s的start位置开始判断能否用wordDict拼接成功
    bool backing(const string &s, vector<string> &wordDict, int start)
    {
        // 如果start已经到达字符串末尾,说明成功匹配完整个字符串
        if (start == s.size())
            return true;

        // 如果memo[start]不为0,说明之前已经计算过,直接返回结果,避免重复计算
        if (memo[start] != 0)
            return memo[start] == 1;

        // 遍历字典中所有单词,尝试匹配当前字符串位置的子串
        for (int i = 0; i < wordDict.size(); ++i)
        {
            int len = wordDict[i].size();

            // 判断剩余字符串长度是否足够匹配wordDict[i]
            // 并且s从start开始的子串是否等于wordDict[i]
            if (start + len <= s.size() && s.substr(start, len) == wordDict[i])
            {
                // 如果匹配成功,则递归从start+len位置继续判断
                if (backing(s, wordDict, start + len))
                {
                    // 记忆化记录当前位置能成功分割,返回true
                    memo[start] = 1;
                    return true;
                }
            }
        }

        // 所有单词尝试完均无法匹配成功,记录失败状态并返回false
        memo[start] = -1;
        return false;
    }

    // 主函数,初始化记忆化数组,调用递归函数判断
    bool wordBreak(string s, vector<string> &wordDict)
    {
        // 初始化memo,长度为s.size(),初始值均为0(未访问)
        memo = vector<int>(s.size(), 0);

        // 从字符串起始位置开始递归判断
        return backing(s, wordDict, 0);
    }
};

打家劫舍

打家劫舍I

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    /*本房间偷与不偷与前两个房间有关,类似与爬楼梯

    */
    /*动态规划五部曲
        1. dp数组的含义
            dp[i]:房价数为i时偷取的最大金币数量dp[i],return dp[num.size()-1]
        2. 递推公式
            偷i: dp[i-2]+nums[i] (偷i的话那么上一个房间就不能偷)
            不偷i:dp[i-1]
            dp[i] = max(dp[i-2]+nums[i],dp[i-1])
        3. 初始化
            dp[0] = num[0]; 必须偷 (只有一个房间时必偷)
            dp[1] = max(num[0],num[1])
        4. 遍历顺序
            从小到大
        5. 打印
    */
    int rob(vector<int> &nums)
    {
        vector<int> dp(nums.size(), 0);
        if (nums.size() == 0)
            return 0;
        else if (nums.size() == 1)
            return nums[0];
        else if (nums.size() == 2)
            return max(nums[0], nums[1]);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }

        return dp[nums.size() - 1];
    }
};

打家劫舍II

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

class Solution
{
public:
    /* 环形解耦为线性

        // 注意:这里的要或不要是指考虑或者不考虑(那么其实1,2包含3的)
        1. 要首不要尾部

        2. 要尾不要首部

        3. 首尾都不需要
    */
    int rob1(vector<int> &nums)
    {
        vector<int> dp(nums.size(), 0);

        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < nums.size(); i++)
        {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }

        return dp[nums.size() - 1];
    }
    int rob(vector<int> &nums)
    {
        if (nums.size() == 0)
            return 0;
        else if (nums.size() == 1)
            return nums[0];
        else if (nums.size() == 2)
            return max(nums[0], nums[1]);
        vector<int> begin(nums.begin(), nums.end() - 1);
        vector<int> end(nums.begin() + 1, nums.end());

        return max(rob1(begin), rob1(end));
    }
};

打家劫舍III(树形DP)

在这里插入图片描述

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution
{
public:
    /*每个节点两种状态:(偷与不偷)
        dp[0]:不偷该节点的最大金钱数量  dp[1]:偷该节点的最大金钱数量
    后序遍历,最终的结果:max(root的dp[0],root的dp[1])
    为什么后序遍历:

     */
    vector<int> robTree(TreeNode *cur)
    {
        if (cur == nullptr)
            return vector<int>(2, 0);
        vector<int> leftDp = robTree(cur->left);
        vector<int> rightDp = robTree(cur->right);
        int val1 = cur->val + leftDp[0] + rightDp[0];                       // 偷当前节点,那么其左右孩子就不能被偷
        int val2 = max(leftDp[0], leftDp[1]) + max(rightDp[0], rightDp[1]); // 不偷当前节点

        return vector<int>{val2, val1}; //(不偷当前节点,偷当前节点)
    }
    int rob(TreeNode *root)
    {
        vector<int> rootDp = robTree(root);
        return max(rootDp[0], rootDp[1]);
    }
};

股票问题

买卖股票的最佳时机I(只卖一次)

在这里插入图片描述

暴力搜索

超出时机限制

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;

class Solution
{
public:
    // 1。 暴力法
    int maxProfit(vector<int> &prices)
    {
        int max = 0;
        for (int i = 0; i < prices.size(); i++)
        {
            for (int j = i + 1; j < prices.size(); j++)
            {
                if (max < (prices[j] - prices[i]))
                    max = prices[j] - prices[i];
            }
        }
        return max;
    }
};

贪心算法

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
using namespace std;

class Solution
{
public:
    /*连续贪
        每次先贪最小值,然后根据最小值来贪最大差值
    */
    int maxProfit(vector<int> &prices)
    {
        if (prices.empty())
            return 0;

        int minPrice = INT_MAX; // 初始化最低价格为最大值
        int maxProfit = 0;      // 初始化最大利润为0

        for (int price : prices)
        {
            // 更新最低价格
            minPrice = min(minPrice, price);
            // 更新最大利润
            maxProfit = max(maxProfit, price - minPrice);
        }

        return maxProfit;
    }
};

动态规划

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
using namespace std;

class Solution
{
public:
    /*动态规划
        1. dp[i]的含义
                dp[i][0]:持有这支股票的最大利润
                dp[i][1]:不持有这支股票的最大利润
                return max(dp[prices.size-1][0],dp[prices.size-1][1])
        2. 递推公式
            dp[i][0] = max(dp[i-1][0],-price[i])两种状态
                继续持有这张股票:dp[i-1][0]
                买入这张股票:   -price[i]
            dp[i][0] = max(dp[i-1][1],dp[i-1][0]+price[i])两种状态
                继续不持有这张股票: dp[i-1][1]
                卖了这张股票:      dp[i-1][0]+price[i]
        3. 初始化
            dp[0][0] = -price[0]
            dp[0][1]  = 0

        4. 遍历顺序

        5. 打印
    */
    int maxProfit(vector<int> &prices)
    {
        vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < prices.size(); i++)
        {
            dp[i][0] = max(dp[i - 1][0], -prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        }
        return max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);
    }
};

买卖股票的最佳时机II(可买卖多次)

在这里插入图片描述

贪心

在这里插入图片描述

int maxProfit(vector<int>& prices) {
    int profit = 0;
    for (int i = 1; i < prices.size(); ++i) {
        if (prices[i] > prices[i - 1]) {
            profit += prices[i] - prices[i - 1];  // 贪心地加上每一次上涨的差值
        }
    }
    return profit;
}

动态规划

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
using namespace std;

class Solution
{
public:
    /*动态规划
        1. dp[i]的含义
                dp[i][0]:持有这支股票的最大利润
                dp[i][1]:不持有这支股票的最大利润
                return max(dp[prices.size-1][0],dp[prices.size-1][1])
        2. 递推公式
            dp[i][0] = max(dp[i-1][0], dp[i-1][1]-price[i])两种状态
                继续持有这张股票:dp[i-1][0]
                买入这张股票:   dp[i-1][1]-price[i]    //注意此时的利润就不是从0开始了,
            dp[i][1] = max(dp[i-1][1],dp[i-1][0]+price[i])两种状态
                继续不持有这张股票: dp[i-1][1]
                卖了这张股票:      dp[i-1][0]+price[i]
        3. 初始化
            dp[0][0] = -price[0]
            dp[0][1]  = 0

        4. 遍历顺序

        5. 打印
    */
    int maxProfit(vector<int> &prices)
    {
        vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < prices.size(); 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]);
        }
        return max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);
    }
};

买卖股票的最佳时机III(只卖2次)

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
using namespace std;

class Solution
{
public:
    /*动态规划
        1. dp[i]的含义
               dp[i][0]  不操作
               dp[i][1]  第一次持有
               dp[i][2]  第一次不持有
               dp[i][3]  第二次持有
               dp[i][4]  第二次不持有
        2. 递推公式
                dp[i][0] = dp[i-1][0]
                dp[i][1] = max(dp[i-1][1],dp[i-1][0]-price[i]) (延续前一日状态,第i天买入)
                dp[i][2] = max(dp[i-1][2],dp[i-1][1]+price[i]) (延续前一日状态,第i天卖出)
                dp[i][3] = max(dp[i-1][3],dp[i-1][2]-price[i]) (延续前一日状态,第i天买入)
                dp[i][4] = max(dp[i-1][4],dp[i-1][3]+price[i]) (延续前一日状态,第i天卖出)
        3. 初始化


        4. 遍历顺序

        5. 打印
    */
    int maxProfit(vector<int> &prices)
    {
        if (prices.size() == 0)
            return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;
        dp[0][3] = -prices[0];
        dp[0][4] = 0;
        for (int i = 1; i < prices.size(); i++)
        {
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
            dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[prices.size() - 1][4];
    }
};

买卖股票的最佳时机IV

买卖股票的最佳时机含冷冻期

买卖股票的最佳时机含手续费

子序列问题

最长递增子序列

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i]:  以nums[i]结尾的最长递增子序列的长度
        2. 递推公式
            dp[i] = max(dp[i],dp[j]+1)  (j<i且dp[j]<dp[i])
        3. 初始化
                dp[i]=1
        4. 遍历顺序
        5. 打印
     */
    int lengthOfLIS(vector<int> &nums)
    {
        if (nums.size() <= 1)
            return nums.size();
        vector<int> dp(nums.size(), 1);
        int result = 0;
        for (int i = 1; i < nums.size(); i++)
        {
            for (int j = 0; j < i; j++) // 从小到大和从大到小都可以
            {
                if (nums[i] > nums[j])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            if (dp[i] > result)
                result = dp[i]; // 取长的子序列
        }
        return result;
    }
};

最长连续递增序列

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i]:  以nums[i]结尾的最长递增子序列的长度
        2. 递推公式
            dp[i] = max(dp[i],dp[j]+1)  (j<i且dp[j]<dp[i])
        3. 初始化
            dp[i]=1
        4. 遍历顺序
        5. 打印
     */
    int findLengthOfLCIS(vector<int> &nums)
    {
        if (nums.size() <= 1)
            return nums.size();
        vector<int> dp(nums.size(), 1);
        int result = 0;
        for (int i = 1; i < nums.size(); i++)
        {
            for (int j = 0; j < i; j++) // 从小到大和从大到小都可以
            {
                if (nums[i] > nums[j] && i == j + 1)
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            if (dp[i] > result)
                result = dp[i]; // 取长的子序列
        }
        return result;
    }
};

在这里插入图片描述

最长重复子数组

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i][j]:  以i-1结尾的nums1数组和以j-2结尾的nums2数组的最大重复子组的长度
        2. 递推公式
            if(nums1[i-1]==nums[j-1])
                dp[i][j] = dp[i-1][j-1]+1;
        3. 初始化
            dp[i][0]和dp[0][j]没有意义
            全都初始化为0
        4. 遍历顺序
        5. 打印
    */
    int findLength(vector<int> &nums1, vector<int> &nums2)
    {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int result = -1;
        for (int i = 1; i <= nums1.size(); i++)
        {
            for (int j = 1; j <= nums2.size(); j++)
            {

                if (nums1[i - 1] == nums2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                if (result < dp[i][j])
                    result = dp[i][j];
            }
        }

        return result;
    }
};

最长公共子序列

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i][j]:  以[0,i-1]结尾的nums1数组和以[0,j-1]结尾的nums2数组的最长公共子序列的长度
        2. 递推公式
           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]);
        3. 初始化
            dp[i][0]和dp[0][j] 全都初始化为0
        4. 遍历顺序
        5. 打印
    */
    int longestCommonSubsequence(string text1, string text2)
    {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        int result = -1;
        for (int i = 1; i <= text1.size(); i++)
        {
            for (int j = 1; j <= text2.size(); 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[text1.size()][text2.size()];
    }
};

不相交的线

在这里插入图片描述在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
public:
    /*
    不相交的线:本质上就是在求相同的公共的子序列问题(可以不相邻)
    动规五部曲
        1. dp数组的含义
        2. 递推公式
        3. 初始化
        4. 遍历顺序
        5. 打印
    */
    int maxUncrossedLines(vector<int> &nums1, vector<int> &nums2)
    {
        vector<vector<int>> dp(nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int result = -1;
        for (int i = 1; i <= nums1.size(); i++)
        {
            for (int j = 1; j <= nums2.size(); 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[nums1.size()][nums2.size()];
    }
};

最大子序和

在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
public:
    /*
    不相交的线:本质上就是在求相同的公共的子序列问题(可以不相邻)
    动规五部曲
        1. dp数组的含义
            dp[i]:以nums[i]结尾的最大子序列的和
        2. 递推公式
            dp[i] = max(dp[i-1]+nums[i],nums[i])   (延续前面的和,前面的和不要了从头开始算)
            // 注意是子序列:需要连续啊!!!,dp[i] = max(dp[i-1]+nums[i],dp[i-1])是错误的,dp[i-1]是错误的,断开了不是子序列了
        3. 初始化
            dp[0] = nums[0]
        4. 遍历顺序
        5. 打印
    */
    int maxSubArray(vector<int> &nums)
    {
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        int maxV = dp[0];
        for (int i = 1; i < nums.size(); i++)
        {
            dp[i] = max(nums[i], dp[i - 1] + nums[i]);
            /*注意:这里的dp数组中的每个元素,求的是以nums[i]结尾的最大的值,而不是整个数组最大的值
            什么时候需要求出max,什么时候返回dp[size()]???????
                求最长公共子序列:直接返回dp[size()]
                求最大子序和:
             */
            if (maxV < dp[i])
                maxV = dp[i];
        }
        return maxV;
    }
};

判断子序列

在这里插入图片描述

转化为最长公共子序列问题


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
public:
    int longestCommonSubsequence(string text1, string text2)
    {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        int result = -1;
        for (int i = 1; i <= text1.size(); i++)
        {
            for (int j = 1; j <= text2.size(); 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[text1.size()][text2.size()];
    }

    // 判断子序列:本质就是求公共子序列的长度
    //只不过这次子序列的长度为s.size()
    bool isSubsequence(string s, string t)
    {
        return s.size() == longestCommonSubsequence(s, t) ? true : false;
    }
};

直接动态规划

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
public:
    /*
    动规五部曲
        1. dp数组的含义
            dp[i][j]:以i-1结尾字符串s和以j-1结尾的字符串t最长子序列的长度
        2. 递推公式
                    if (s[i - 1] == t[j - 1])
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                    else
                        dp[i][j] = dp[i][j - 1]; // 只能删除t中的字符串
        3. 初始化
        4. 遍历顺序
        5. 打印
    */
    bool isSubsequence(string s, string t)
    {
        // 什么时候+1什么时候不加1:看是否描述的j-1结尾
        vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
        for (int i = 1; i <= s.size(); i++) // 注意这里是有等号的,因为描述的是i-1结尾的
        {
            for (int j = 1; j <= t.size(); j++)
            {
                if (s[i - 1] == t[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                    dp[i][j] = dp[i][j - 1]; // 只能删除t中的字符串
            }
        }
        return dp[s.size()][t.size()] == s.size() ? true : false; // 描述的是s.size()-1结尾的s和以t.size()-1结尾的t
    }
};

不同的子序列

在这里插入图片描述
当发现int溢出的时候,可以使用uint64_t

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
    /*两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
    动规五部曲
        1. dp数组的含义
            dp[i][j]:以i-1结尾的字符串s有以字符串j-1结尾的字符串t的个数
        2. 递推公式
            if(s[i-1] == t[j-1])
                dp[i][j] = dp[i-1][j-1]+dp[i-1][j]  //dp[i][j-1]是不考虑s[i-1]  例如:bagg 和bag中的   bag(不考虑第二个g)
            else
                dp[i][j] = dp[i-1][j]
        3. 初始化
        4. 遍历顺序
        5. 打印
     */
public:
    // 当发现int溢出的时候,可以使用uint64_t
    int numDistinct(string s, string t)
    {
        // vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
        // ine 41: Char 49: runtime error: signed integer overflow: 1474397256 + 891953512 canno
        vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1, 0));
        // ***********************************************************************************
        for (int i = 0; i <= s.size(); i++)
        {
            dp[i][0] = 1; // s中有空字符串的个数:将s中的字符全部删除
        }
        // ***********************************************************************************

        for (int i = 1; i <= s.size(); i++)
        {
            for (int j = 1; j <= t.size(); j++)
            {
                if (s[i - 1] == t[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                else
                    dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[s.size()][t.size()];
    }
};


两个字符串的删除操作

在这里插入图片描述

动态规划(最长公共子序列转换)

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
    /*本质就是在求两个字符串的公共子序列,转换的步数=两个字符串的总长度-公共子序列的长度*2
     */
public:
    int longestCommonSubsequence(string text1, string text2)
    {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        int result = -1;
        for (int i = 1; i <= text1.size(); i++)
        {
            for (int j = 1; j <= text2.size(); 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[text1.size()][text2.size()];
    }
    int minDistance(string word1, string word2)
    {
        return (word1.size() + word2.size() - longestCommonSubsequence(word1, word2) * 2);
    }
};

动态规划


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
#include <map>
#include <numeric>
using namespace std;

class Solution
{
    /*两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
    动规五部曲
        1. dp数组的含义
            dp[i][j]:以i-1结尾的字符串world1和以字符串j-1结尾的字符串world2的最少转换次数
        2. 递推公式
        3. 初始化
        4. 遍历顺序
        5. 打印
     */
public:
    int minDistance(string word1, string word2)
    {
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
        // ***********************************************************************************
        for (int i = 0; i <= word1.size(); i++)
        {
            dp[i][0] = i;
        }
        for (int j = 0; j <= word2.size(); j++)
        {
            dp[0][j] = j;
        }
        // ***********************************************************************************

        for (int i = 1; i <= word1.size(); i++)
        {
            for (int j = 1; j <= word2.size(); j++)
            {
                if (word1[i - 1] == word2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1];
                else
                    // dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2); // (删除world1,删除workld2,都删)
                    dp[i][j] = min(min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 2);
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

编辑距离

在这里插入图片描述
在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i][j]:以i-1结尾的word1和以j-1结尾的word2的最少操作次数
        2. 递推公式(word1->word2)
            if(word1[i-1] == word2[j-1])
                dp[i][j]=dp[i-1][j-1]
            else
                删/增(直接删除)
                    (word1转word2增 相当于word2转word1删)
                    dp[i][j]=dp[i][j-1]+1(1表示删除操作)
                    dp[i][j]=dp[i-1][j]+1(1表示删除操作)
                替(直接替换)
                    dp[i-1][j-1]+1
                dp[i][j]=min(dp[i][j-1]+1,dp[i-1][j]+1,dp[i-1][j-1]+1)
        3. 初始化
            dp[i][0] = i  word1删除i次变为空串
            dp[0][j] = j  word2删除j次变为空串
        4. 遍历顺序
        5. 打印
     */
    int minDistance(string word1, string word2)
    {
        vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1, 0));
        for (int i = 0; i <= word1.size(); i++)
            dp[i][0] = i;
        for (int j = 0; j <= word2.size(); j++)
            dp[0][j] = j;

        for (int i = 1; i <= word1.size(); i++)
        {
            for (int j = 1; j <= word2.size(); j++)
            {
                if (word1[i - 1] == word2[j - 1])
                {
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else
                {
                    dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1] + 1);
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

回文子串

在这里插入图片描述

动态规划

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i][j]:[i,j]表示是否是回文字串,bool值
        2. 递推公式(s)
            if(s[i] == s[j])
            {
                if(i == j)          // 指向同一个字符
                {
                    dp[i][j] = true;
                    result++;
                }
                else if((j-i)==1)   //只有两个字符,且这两个字符相等
                {
                    dp[i][j] = true;
                    result++;
                }
                else if((j-i)>1) // 需要判断[i+1,j-1]区间是否是回文字串
                {
                    if(dp[i+1][j-1] == true)
                    {
                        dp[i][j] = true;
                        result++;
                    }
                }
            }
        3. 初始化

        4. 遍历顺序
            有dp[i+1][j-1]--->dp[i][j]   从左往右从下往上
        5. 打印
     */
    int countSubstrings(string s)
    {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--)
        {
            for (int j = i; j < s.size(); j++)
            {
                if (s[i] == s[j])
                {
                    if ((j - i) <= 1)
                    {
                        dp[i][j] = true;
                        result++;
                    }
                    else if (dp[i + 1][j - 1] == true)
                    {
                        dp[i][j] = true;
                        result++;
                    }
                }
            }
        }
        return result;
    }
};

双指针

  • 双指针的方式是每次从可能会有的回文串中心,然后向两侧进行延伸,判定是否为回文串。当子串为奇数长度时,回文串中心唯一;当子串为偶数长度时,会由两个数作为回文串中心。

#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    int countSubstrings(string s)
    {
        int n = s.size(), res = 0;
        for (int i = 0; i < n; i++)
        {
            // 分别尝试奇数时回文串中心情况和偶数时回文串中心情况
            res += palindromicNums(s, i, i, n);
            res += palindromicNums(s, i, i + 1, n);
        }

        return res;
    }
    int palindromicNums(const string &s, int i, int j, int n)
    {
        int res = 0;
        // 从中间向两边伸展探寻
        while (i >= 0 && j < n && s[i] == s[j])
        {
            i--;
            j++;
            res++;
        }

        return res;
    }
};

最长回文子序列

在这里插入图片描述
在这里插入图片描述


#include <iostream>
#include <cstring>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;

class Solution
{
public:
    /*动规五部曲
        1. dp数组的含义
            dp[i][j]:表示[i,j]回文字串的长度
        2. 递推公式(s)
            if(s[i] == s[j])
            {
                dp[i][j] = dp[i+1][j-1] + 2;
            }
            else
            {
                dp[i][j] = max(dp[i][j-1],dp[i+1][j]); // 范围左移,范围右移
            }
        3. 初始化

        4. 遍历顺序
            有dp[i+1][j-1]--->dp[i][j]   从左往右从下往上
        5. 打印
     */
    int longestPalindromeSubseq(string s)
    {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        for (int i = 0; i < s.size(); i++)
            dp[i][i] = 1;

        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--)
        {
            for (int j = i + 1; j < s.size(); j++) // j>i 所以j从i+1开始
            {
                if (s[i] == s[j])
                {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                }
                else
                {
                    dp[i][j] = max(dp[i][j - 1], dp[i + 1][j]);
                }
            }
        }
        return dp[0][s.size() - 1];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值