代码随想录笔记--动态规划篇

目录

1--动态规划理论基础

2--斐波那契数

3--爬楼梯

4--使用最小花费爬楼梯

5--不同路径

6--不同路径II

7--整数拆分

8--不同的二叉搜索树

9--背包问题

10--打家劫舍

11--打家劫舍II

12--打家劫舍III

13--买卖股票的最佳时机

14--买卖股票的最佳时机II

15--买卖股票的最佳时机III

16--买卖股票的最佳时机IV

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

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

19--最长递增子序列

20--最长连续递增序列

21--最长重复子数组

22--最长公共子序列

23--不相交的线

24--最大子序和

25--判断子序列

26--不同的子序列

27--两个字符串的删除操作

28--编辑距离

29--回文子串

30--最长回文子序列


1--动态规划理论基础

动态规划经典问题:① 背包问题;② 打家劫舍;③ 股票问题; ④ 子序列问题;

动态规划五部曲:

        ① 确定 dp 数组及其下标的含义;

        ② 确定递推公式;

        ③ 确定 dp 数组的初始化;

        ④ 确定遍历顺序,一般为从左到右;

        ⑤ 打印 dp 数组,一般用于检查;

2--斐波那契数

主要思路:

        经典动态规划,dp[i] 表示第 i 个数的斐波那契数;递推公式为:dp[i] = dp[i-1] + dp[i - 2];初始化dp[0] = 1,dp[1] = 1;遍历顺序从左到右;

#include <iostream>
#include <vector>

class Solution {
public:
    int fib(int n) {
        if(n <= 1) return n;
        std::vector<int> dp(n+1, 0);
        dp[0] = 0; dp[1] = 1;
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

int main(int argc, char argv[]){
    int test = 2;
    Solution S1;
    int res = S1.fib(test);
    std::cout << res << std::endl;
    return 0;
}

3--爬楼梯

主要思路:

        经典动态规划,dp[i] 表示到达第 i 阶楼梯的方法数;递推公式为 dp[i] = dp[i - 1] + dp[i - 2];初始化dp[1] = 1, dp[2] = 2;遍历顺序从左到右;

#include <iostream>
#include <vector>

class Solution {
public:
    int climbStairs(int n) {
        if(n <= 2) return n; 
        std::vector<int> dp(n+1, 0);
        dp[1] = 1, dp[2] = 2;
        for(int i = 3; i <= n; i++){
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

int main(int argc, char argv[]){
    int n = 2;
    Solution S1;
    int res = S1.climbStairs(n); 
    std::cout << res << std::endl;
    return 0;
}

4--使用最小花费爬楼梯

主要思路:

        dp[i] 表示到达第 i 阶楼梯需要的最小花费;初始化 dp[0] = 0, dp[1] = 0,因为可以从 0 和 1 出发,因此不需要花费;递推公式为 dp[i] = std::min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);默认从左到右遍历;

#include <iostream>
#include <vector>

class Solution {
public:
    int minCostClimbingStairs(std::vector<int>& cost) {
        std::vector<int> dp(cost.size()+1, 0); // 到达第i阶的最小花费
        dp[0] = 0;
        dp[1] = 0;
        for(int i = 2; i <= cost.size(); i++){
            dp[i] = std::min(cost[i-2]+dp[i-2], cost[i-1]+dp[i-1]);
        }
        return dp[cost.size()];
    }
};

int main(int argc, char argv[]){
    // cost = [1,100,1,1,1,100,1,1,100,1]
    std::vector<int> cost = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1};
    Solution S1;
    int res = S1.minCostClimbingStairs(cost);
    std::cout << res << std::endl;
    return 0;
}

5--不同路径

主要思路:

        dp[i][j] 表示到达 (i, j) 位置的路径数,初始化两个边界 dp[i][0] = 1,dp[0][j] = 1;状态转移方程为:dp[i][j] = dp[i-1][j] + dp[i][j-1];遍历顺序为从上到下,从左到右;

#include <iostream>
#include <vector>

class Solution {
public:
    int uniquePaths(int m, int n) {
        std::vector<std::vector<int>> dp(m, std::vector<int>(n, 0));
        // 初始化
        for(int i = 0; i < m; i++) dp[i][0] = 1;
        for(int j = 0; j < n; j++) dp[0][j] = 1;
        // 遍历
        for(int r = 1; r < m; r++){
            for(int c = 1; c < n; c++){
                dp[r][c] = dp[r-1][c] + dp[r][c-1];
            }
        }
        return dp[m-1][n-1];
    }
};

int main(int argc, char argv[]){
    // m = 3, n = 7
    int m = 3, n = 7;
    Solution S1;
    int res = S1.uniquePaths(m, n);
    std::cout << res << std::endl;
    return 0;
}

6--不同路径II

主要思路:

        与上题类似,dp[i][j] 表示到达 (i, j) 位置的路径数,初始化两个边界 dp[i][0] = 1,dp[0][j] = 1(确保边界没有障碍物,有障碍物初始化为0);状态转移方程为:(i, j)不是障碍物则 dp[i][j] = dp[i-1][j] + dp[i][j-1],(i, j)为障碍物则dp[i][j] = 0;遍历顺序为从上到下,从左到右;

#include <iostream>
#include <vector>

class Solution {
public:
    int uniquePathsWithObstacles(std::vector<std::vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size(), n = obstacleGrid[0].size();
        if(obstacleGrid[m-1][n-1] == 1) return 0;
        
        std::vector<std::vector<int>> dp(m, std::vector<int>(n, 0));
        // 初始化
        for(int i = 0; i < m && obstacleGrid[i][0] != 1; i++) dp[i][0] = 1;
        for(int j = 0; j < n && obstacleGrid[0][j] != 1; j++) dp[0][j] = 1;
        // 遍历
        for(int r = 1; r < m; r++){
            for(int c = 1; c < n; c++){
                if(obstacleGrid[r][c] == 1) dp[r][c] = 0; // 障碍
                else dp[r][c] = dp[r-1][c] + dp[r][c-1];
            }
        }
        return dp[m-1][n-1];
    }
};

int main(int argc, char argv[]){
    // obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
    std::vector<std::vector<int>> test = {{0, 0, 0}, {0, 1, 0}, {0, 0, 0}};
    Solution S1;
    int res = S1.uniquePathsWithObstacles(test);
    std::cout << res << std::endl;
    return 0;
}

7--整数拆分

主要思路:

        dp[i] 表示整数 i 拆分后的最大乘积;初始化dp[0] = 0, dp[1] = 0, dp[2] = 1; 状态转移方程为:dp[i] = max(dp[i], max(j*(i-j), j * dp[i-j]));

#include <iostream>
#include <vector>

class Solution {
public:
    int integerBreak(int n) {
        std::vector<int> dp(n+1, 0);
        // 初始化
        dp[0] = 0, dp[1] = 0, dp[2] = 1;
        // 遍历
        for(int i = 3; i <= n; i++){
            for(int j = 0; j <= i/2; j++){
                dp[i] = std::max(dp[i], std::max(j*(i-j), j * dp[i-j]));
            }
        }
        return dp[n];
    }
};

int main(int argc, char argv[]){
    // n = 10
    int test = 10;
    Solution S1;
    int res = S1.integerBreak(test);
    std::cout << res << std::endl;
    return 0;
}

8--不同的二叉搜索树

主要思路:

        dp[i] 表示由 i 个数字构成的不同二叉搜索树的数目;

        初始化:dp[0] = 1;

        状态转移方程:dp[i] += dp[j-1] * dp[i-j],0 <= j <= i;其中 dp[j-1] 来构成 j 的左子树,dp[i-j] 来构成 j 的右子树;

         遍历顺序:两个 for 循环,第 1 个 for 循环遍历 1 - n,第二个 for 循环遍历 1 - i;

#include <iostream>
#include <vector>

class Solution {
public:
    int numTrees(int n) {
		std::vector<int> dp(n+1, 0);
		// 初始化
		dp[0] = 1;
		// 遍历
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= i; j++){
				dp[i] += dp[j-1] * dp[i-j];
			}
		}
		return dp[n];
    }
};

int main(int argc, char *argv[]) {
	// n = 3
	int test = 3;
	Solution S1;
	int res = S1.numTrees(test);
	std::cout << res << std::endl;
	return 0;
}

9--背包问题

背包问题笔记

10--打家劫舍

主要思路:

        基于动态规划,dp[i]表示截止到坐标 i 时,能够偷到的最高金额;

        初始化 dp[0] = nums[0], dp[1] = max(nums[0], nums[1]);

        状态转移方程:dp[i] = max(nums[i] + dp[i-2], dp[i-1]);dp[i-2] + nums[i] 表示偷索引为 i 的房屋,dp[i-1] 表示不偷索引为 i 的房屋;

#include <iostream>
#include <vector>

class Solution {
public:
    int rob(std::vector<int>& nums) {
        if(nums.size() == 1) return nums[0];
        std::vector<int> dp(nums.size(), 0);
        dp[0] = nums[0], dp[1] = std::max(nums[0], nums[1]);
        for(int i = 2; i < nums.size(); i++){
            dp[i] = std::max(dp[i-2] + nums[i], dp[i - 1]);
        }
        return dp[nums.size() - 1];
    }
};

int main(int argc, char argv[]){
    std::vector<int> test = {1, 2, 3, 1};
    Solution S1;
    int res = S1.rob(test);
    std::cout << res << std::endl;
    return 0;
}

11--打家劫舍II

主要思路:

        本题房屋围成一圈,因此首尾房屋不能同时打劫,将本题划分为三种情况:① 不打劫首尾房屋;② 考虑打劫首房屋,则不能打劫尾房屋;③ 考虑打劫尾房屋,则不能打劫首房屋;

#include <iostream>
#include <vector>

class Solution {
public:
    int rob(std::vector<int>& nums) {
        if(nums.size() == 1) return nums[0];
        // 不考虑首尾房屋
        std::vector<int> nums1(nums.begin() + 1, nums.end() - 1);
        // 考虑首房屋
        std::vector<int> nums2(nums.begin(), nums.end() - 1);
        // 考虑尾房屋
        std::vector<int> nums3(nums.begin() + 1, nums.end());

        return std::max(std::max(dp_solution(nums1), dp_solution(nums2)), dp_solution(nums3));
    }

    int dp_solution(std::vector<int>& nums){ // 打家劫舍I的代码
        if(nums.size() == 0) return 0;
        if(nums.size() == 1) return nums[0];
        std::vector<int> dp(nums.size(), 0);
        dp[0] = nums[0], dp[1] = std::max(nums[0], nums[1]);
        for(int i = 2; i < nums.size(); i++){
            dp[i] = std::max(dp[i-2] + nums[i], dp[i - 1]);
        }
        return dp[nums.size() - 1];
    }
};

int main(int argc, char argv[]){
    std::vector<int> test = {2, 3, 2};
    Solution S1;
    int res = S1.rob(test);
    std::cout << res << std::endl;
    return 0;
}

12--打家劫舍III

主要思路:

        基于二叉树递归(后序遍历)和动态规划,每个节点拥有两个状态,dp[0] 表示不打劫当前节点的最高金额,dp[1] 表示打劫当前节点的最高金额;

        对于每个节点 cur,dp[1]  = dp_left[0] + dp_right[0] + cur->val,即打劫当前节点时,不能打劫左孩子和右孩子;

        dp[0] = std::max(dp_left[0], dp_left[1]) + std::max(dp_right[0], dp_right[1]);不打劫当前节点时,可以打劫也可以不打劫左右孩子,取两种情况的最大值;

#include <iostream>
#include <vector>

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:
    int rob(TreeNode* root) {
        std::vector<int> res = dfs(root);
        return std::max(res[0], res[1]);
    }

    std::vector<int> dfs(TreeNode* cur){
        if(cur == nullptr) return {0, 0};
        std::vector<int> dp_left = dfs(cur->left);
        std::vector<int> dp_right = dfs(cur->right);

        int dp_cur0 = std::max(dp_left[0], dp_left[1]) + std::max(dp_right[0], dp_right[1]);
        int dp_cur1 = dp_left[0] + dp_right[0] + cur->val;
        return {dp_cur0, dp_cur1};
    }
};

int main(int argc, char argv[]){
    // root = [3, 2, 3, null, 3, null, 1]
    TreeNode *Node1 = new TreeNode(3);
    TreeNode *Node2 = new TreeNode(2);
    TreeNode *Node3 = new TreeNode(3);
    TreeNode *Node4 = new TreeNode(3);
    TreeNode *Node5 = new TreeNode(1);

    Node1->left = Node2;
    Node1->right = Node3;
    Node2->right = Node4;
    Node3->right = Node5;

    Solution S1;
    int res = S1.rob(Node1);
    std::cout << res << std::endl;

    return 0;
}

13--买卖股票的最佳时机

主要思路:

        每一天都有两个状态:即持有股票和不持有股票;

        dp[i][0] 表示第 i 天持有股票时的最大金额,dp[i][1] 表示第 i 天不持有股票时的最大金额;

        状态转移方程:dp[i][0] = max(dp[i-1][0], 0-prices[i]);dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[1]);

        初始化 dp[0][0] = -prices[0], dp[i][1] = 0;

#include <iostream>
#include <vector>

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

int main(int argc, char argv[]){
    // [7, 1, 5, 3, 6, 4]
    std::vector<int> test = {7, 1, 5, 3, 6, 4};
    Solution S1;
    int res = S1.maxProfit(test);
    std::cout << res << std::endl;
    return 0;
}

14--买卖股票的最佳时机II

主要思路:

        每一天都有两个状态:即持有股票和不持有股票;

        dp[i][0] 表示第 i 天持有股票时的最大金额,dp[i][1] 表示第 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[1]);

        由于可以多次买卖,则第 i 天持有股票的状态可能由第 i-1 天不持有股票,在第 i 天买入股票推导而得;

        初始化dp[0][0] = -prices[0],dp[0][1] = 0;

#include <iostream>
#include <vector>

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

int main(int argc, char argv[]){
    // [7, 1, 5, 3, 6, 4]
    std::vector<int> test = {7, 1, 5, 3, 6, 4};
    Solution S1;
    int res = S1.maxProfit(test);
    std::cout << res << std::endl;
    return 0;
}

15--买卖股票的最佳时机III

主要思路:

        本题最多只能买卖两次,则对于每一天,会有以下五种状态:dp[i][0] 表示第 i 天不进行任何操作,dp[i][1] 表示第 i 天第一次买入,dp[i][2] 表示第 i 天第一次卖出,dp[i][3] 表示第 i 天第二次买入,dp[i][4] 表示第 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]);

        初始化:dp[0][0] = 0,dp[0][1] = -prices[0],dp[0][2] = 0,dp[0][3] = -prices[0],dp[0][4] = 0;

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(std::vector<int>& prices) {
        std::vector<std::vector<int>> dp(prices.size(), std::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] = std::max(dp[i-1][1], dp[i-1][0] - prices[i]);
            dp[i][2] = std::max(dp[i-1][2], dp[i-1][1] + prices[i]);
            dp[i][3] = std::max(dp[i-1][3], dp[i-1][2] - prices[i]);
            dp[i][4] = std::max(dp[i-1][4], dp[i-1][3] + prices[i]);

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

int main(int argc, char argv[]){
    // [3, 3, 5, 0, 0, 3, 1, 4]
    std::vector<int> test = {3, 3, 5, 0, 0, 3, 1, 4};
    Solution S1;
    int res = S1.maxProfit(test);
    std::cout << res << std::endl;
    return 0;
}

16--买卖股票的最佳时机IV

主要思路:

        类似于上题,上题中最多可以买卖两次,对应五种状态;本题最多可以买卖k次,对应2K+1种状态。

        状态转移方程与初始化类似于上题。

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(int k, std::vector<int>& prices) {
        std::vector<std::vector<int>> dp(prices.size(), std::vector<int>(2*k+1, 0));
        // 初始化
        for(int j = 1; j < 2*k+1; j += 2){
            dp[0][j] = -prices[0]; // 持有
            dp[0][j+1] = 0;  // 不持有     
        }
        // 递推
        for(int i = 1; i < prices.size(); i++){
            for(int j = 1; j < 2*k+1; j += 2){
                dp[i][j] = std::max(dp[i-1][j], dp[i][j - 1] - prices[i]); // 持有
                dp[i][j+1] = std::max(dp[i-1][j+1], dp[i][j] + prices[i]); // 不持有
            }
        }
        return dp[prices.size() - 1][2*k];
    }
};

int main(int argc, char *argv[]) {
    // k = 2, prices = [2,4,1]
    int k = 2;
    std::vector<int> prices = {2, 4, 1};
    Solution S1;
    int res = S1.maxProfit(k, prices);
    std::cout << res << std::endl;
}

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

主要思路:

        将第 i 天拆分为四种状态:dp[i][0] 表示持有股票,dp[i][1] 表示保持卖出股票状态(冷冻期的下一天),dp[i][2] 表示卖出股票,dp[i][3] 表示冷冻期(卖出股票的下一天)。

状态转移方程:

        dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i], dp[i-1][3] - prices[i]);

        dp[i][1] = max(dp[i-1][1], dp[i-1][3]);

        dp[i][2] = dp[i-1][0] + prices[i];

        dp[i][3] = dp[i-1][2]

初始化:

        dp[0][0] = -prices[0], dp[0][1] = 0, dp[0][2] = 0, dp[0][3] = 0;对于不能直接推导出初始化值的状态,可以根据状态转移方程的合理性来推导,即把数据代入到状态转移方程,得出初始化结果。

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(std::vector<int>& prices) {
        std::vector<std::vector<int>>dp(prices.size(), std::vector<int>(4, 0));
        // 初始化
        dp[0][0] = -prices[0], dp[0][1] = 0, dp[0][2] = 0, dp[0][3] = 0;
        for(int i = 1; i < prices.size(); i++){
            // 状态转移
            dp[i][0] = std::max(std::max(dp[i-1][0], dp[i-1][1] - prices[i]), dp[i-1][3] - prices[i]); // 持有股票可以是前一天就持有股票;也可以是前一天是保持卖出状态,然后当天买入股票;还可以前一天是冷冻期,然后当天买入股票;
            dp[i][1] = std::max(dp[i-1][1], dp[i-1][3]); // 保持卖出股票的状态,前一天可以是保持卖出股票的状态,前一天也可以是冷冻期
            dp[i][2] = dp[i-1][0] + prices[i]; // 卖出股票,前一天只能是持有股票
            dp[i][3] = dp[i-1][2]; // 冷冻期,前一天只能是卖出股票
        }
        // 不持有股票
        return std::max(std::max(dp[prices.size() - 1][1], dp[prices.size() - 1][2]), dp[prices.size() - 1][3]);
    }
};

int main(int argc, char argv[]){
    // prices = [1,2,3,0,2]
    std::vector<int> test = {1, 2, 3, 0, 2};
    Solution S1;
    int res = S1.maxProfit(test);
    std::cout << res << std::endl;
    return 0;
}

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

主要思路:

        与买卖股票的最佳时机II类似,只是在卖出股票的时候需要减去对应的手续费。

#include <iostream>
#include <vector>

class Solution {
public:
    int maxProfit(std::vector<int>& prices, int fee) {
        // 持有股票和不持有股票两种状态
        std::vector<std::vector<int>> dp(prices.size(), std::vector<int>(2, 0)); 
        dp[0][0] = -prices[0]; // 持有股票
        dp[0][1] = 0; // 不持有股票 // 这里可能会误初始化为 -fee,但考虑实际价值如果当天买入就卖出,白亏一笔手续费,所以干脆不买,初始化为0
        for(int i = 1; i < prices.size(); i++){
            dp[i][0] = std::max(dp[i-1][0], dp[i-1][1] - prices[i]);
            dp[i][1] = std::max(dp[i-1][1], dp[i-1][0] + prices[i] - fee);
        }
        return dp[prices.size() - 1][1];
    }
};

int main(int argc, char argv[]){
    // prices = [1, 3, 2, 8, 4, 9], fee = 2
    std::vector<int> test = {1, 3, 2, 8, 4, 9};
    int fee = 2;
    Solution S1;
    int res = S1.maxProfit(test, fee);
    std::cout << res << std::endl;
    return 0;
}

19--最长递增子序列

主要思路:

        dp[i] 表示以字母 nums[i] 结尾的最长子序列长度;

        状态转移方程:当 j  < i 且 nums[j] < nums[i] 时,nums[i] = std::max(dp[i], dp[j] + 1);

#include <iostream>
#include <vector>

class Solution {
public:
    int lengthOfLIS(std::vector<int>& nums) {
        std::vector<int> dp(nums.size(), 1); // 初始化为1,至少是以自己作为结尾,其最小长度是1
        int res = 1;
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    dp[i] = std::max(dp[i], dp[j] + 1);
                }
            }
            if (dp[i] > res) res = dp[i]; // 记录最长的长度
        }
        return res;
    }
};

int main(int argc, char argv[]){
    // nums = [10, 9, 2, 5, 3, 7, 101, 18]
    std::vector<int> test = {10, 9, 2, 5, 3, 7, 101, 18};
    Solution S1;
    int res = S1.lengthOfLIS(test);
    std::cout << res << std::endl;
    return 0;
}

20--最长连续递增序列

主要思路:

        与上题类似,但本题要求连续递增的子序列,因此不需要遍历索引为[0, i)的数值,只需要判断前面一个数值;

#include <iostream>
#include <vector>

class Solution {
public:
    int findLengthOfLCIS(std::vector<int>& nums) {
        std::vector<int> dp(nums.size(), 1);
        int res = 1;
        for(int i = 1; i < nums.size(); i++){
            if(nums[i-1] < nums[i]) dp[i] = std::max(dp[i], dp[i-1] + 1);
            if(dp[i] > res) res = dp[i];
        }
        return res;
    }
};

int main(int argc, char argv[]){
    // nums = [1, 3, 5, 4, 7]
    std::vector<int> test = {1, 3, 5, 4, 7};
    Solution S1;
    int res = S1.findLengthOfLCIS(test);
    std::cout << res << std::endl;
    return 0;
}

21--最长重复子数组

主要思路:

       dp[i][j] 表示以 nums1[i] 和 nums2[j] 结尾的, 公共的长度最长的子数组的长度;

#include <iostream>
#include <vector>

class Solution {
public:
    int findLength(std::vector<int>& nums1, std::vector<int>& nums2) {
        // dp[i][j] 表示以 nums1[i] 和 nums2[j]结尾的, 公共的长度最长的子数组的长度 
        std::vector<std::vector<int>> dp(nums1.size(), std::vector<int>(nums2.size(), 0));

        int res = 0;
        // 初始化
        for(int i = 0; i < nums1.size(); i++){
            if(nums1[i] == nums2[0]){
                res = 1;
                dp[i][0] = 1;
            }
        }
        for(int j = 0; j < nums2.size(); j++){
            if(nums1[0] == nums2[j]){
                res = 1;
                dp[0][j] = 1;
            }
        }
        // 状态转移
        for(int i = 1; i < nums1.size(); i++){
            for(int j = 1; j < nums2.size(); j++){
                if(nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
                if (dp[i][j] > res) res = dp[i][j];
            }
        }
        return res;
    }
};

int main(int argc, char *argv[]) {
    // nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
    std::vector<int> nums1 = {1, 2, 3, 2, 1};
    std::vector<int> nums2 = {3, 2, 1, 4, 7};
    Solution S1;
    int res = S1.findLength(nums1, nums2);
    std::cout << res << std::endl;
    return 0;
}

22--最长公共子序列

主要思路:

        dp[i][j]表示在 text1 [0, i] 和 text2 [0, j] 范围内最长的公共子序列长度;

状态转移方程:

        text1[i] == text2[j] 时,dp[i][j] = dp[i-1][j-1] + 1;

        text1[i] != text2[j] 时, dp[i][j] = std::max(dp[i-1][j], dp[i][j-1]);即取[0, i] [0, j-1] 和 [0, i-1][0, j]的最大值;

#include <iostream>
#include <vector>
#include <string>

class Solution {
public:
    int longestCommonSubsequence(std::string text1, std::string text2){
        // dp[i][j]表示在 text1 [0, i] 和 text2 [0, j] 范围内最长的公共子序列长度
        std::vector<std::vector<int>> dp(text1.length(), std::vector<int>(text2.length(), 0));   
        // 初始化
        for(int i = 0; i < text1.length(); i++){
            if(text1[i] == text2[0]){
                dp[i][0] = 1; 
            }
            else if(i > 0){
                dp[i][0] = dp[i-1][0];
            }
        }
        for(int j = 0; j < text2.length(); j++){
            if(text1[0] == text2[j]){
                dp[0][j] = 1;
            }
            else if(j > 0){
                dp[0][j] = dp[0][j-1];
            }
        }
        // 状态转移
        for(int i = 1; i < text1.length(); i++){
            for(int j = 1; j < text2.length(); j++){
                if(text1[i] == text2[j]){
                    dp[i][j] = dp[i-1][j-1] + 1;        
                }
                else{
                    dp[i][j] = std::max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }        
        return dp[text1.length() - 1][text2.length() - 1];
    }
};

int main(int argc, char *argv[]) {
    // text1 = "abcde", text2 = "ace"
    std::string str1 = "abcde";
    std::string str2 = "ace";
    Solution S1;
    int res = S1.longestCommonSubsequence(str1, str2);
    std::cout << res << std::endl;
    return 0;
}

23--不相交的线

主要思路:

        本题等价于求两个序列的最长公共子序列;

#include <iostream>
#include <vector>

class Solution {
public:
    int maxUncrossedLines(std::vector<int>& nums1, std::vector<int>& nums2) {
        std::vector<std::vector<int>> dp(nums1.size(), std::vector<int>(nums2.size(), 0));
        for(int i = 0; i < nums1.size(); i++){
            if(nums1[i] == nums2[0]) dp[i][0] = 1;
            else if(i > 0) dp[i][0] = dp[i-1][0];
        }
        for(int j = 0; j < nums2.size(); j++){
            if(nums1[0] == nums2[j]) dp[0][j] = 1;
            else if(j > 0) dp[0][j] = dp[0][j-1];
        }
        for(int i = 1; i < nums1.size(); i++){
            for(int j = 1; j < nums2.size(); j++){
                if(nums1[i] == nums2[j]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = std::max(dp[i-1][j], dp[i][j-1]);
            }
        }
        return dp[nums1.size() - 1][nums2.size() - 1];
    }
};

int main(int argc, char argv[]){
    // nums1 = [1,4,2], nums2 = [1,2,4]
    std::vector<int> nums1 = {1, 4, 2}, nums2 = {1, 2, 4};
    Solution S1;
    int res = S1.maxUncrossedLines(nums1, nums2);
    std::cout << res << std::endl;
    return 0;
}

24--最大子序和

主要思路:

        dp[i] 表示以 nums[i] 结尾的最大连续子数组的和;

        dp[i] = std::max(dp[i-1] + nums[i], nums[i]);

#include <iostream>
#include <vector>

class Solution {
public:
    int maxSubArray(std::vector<int>& nums) {
        std::vector<int> dp(nums.size(), 0);
        int max = nums[0];
        dp[0] = nums[0];
        for(int i = 1; i < nums.size(); i++){
            dp[i] = std::max(dp[i-1] + nums[i], nums[i]);
            if(dp[i] > max) max = dp[i];
        }
        return max;
    }
};

int main(int argc, char argv[]){
    // nums = [-2,1,-3,4,-1,2,1,-5,4]
    std::vector<int> nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    Solution S1;
    int res = S1.maxSubArray(nums);
    std::cout << res << std::endl;
    return 0;
}

25--判断子序列

主要思路:

        与最长公共子序列类似,只需要判断最长公共子序列是否等于 s 字符串的长度即可;

        即 dp[s.length() - 1][t.length() - 1] 是否等于 s.length();

#include <iostream>
#include <vector>

class Solution {
public:
    bool isSubsequence(std::string s, std::string t) {
        if(s.length() == 0) return true;
        if(t.length() == 0 && s.length() != 0) return false;
        std::vector<std::vector<int>> dp(s.length(), std::vector<int>(t.length(), 0));
        for(int j = 0; j < t.length(); j++){
            if(s[0] == t[j]) dp[0][j] = 1;
            else if(j > 0) dp[0][j] = dp[0][j-1];
        }
        for(int i = 1; i < s.length(); i++){
            for(int j = 1; j < t.length(); j++){
                if(s[i] == t[j]) dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = dp[i][j-1];
            }
        }
        if(dp[s.length() - 1][t.length() - 1] == s.length()) return true;
        else return false;
    }
};

int main(int argc, char argv[]){
    // s = "abc", t = "ahbgdc"
    std::string str1 = "abc", str2 = "ahbgdc";
    Solution S1;
    bool res = S1.isSubsequence(str1, str2);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

26--不同的子序列

主要思路:

       本题可以转化为有多少种删除的方式,可以将字符串s转化为字符串t;

        定义 dp[i][j] 表示以字符串s中第i个字符结尾,通过删除s中字符,构成t中前j个字符(以第j个字符结尾)的方式的个数; 

        当s[i] = t[j] 时,dp[i][j] = dp[i-1][j-1] + dp[i-1][j];加上dp[i-1][j]的原因是,可以不使用s[i]字符,由前i-1个字符来构成t的前j个字符;

#include <iostream>
#include <vector>

class Solution {
public:
    int numDistinct(std::string s, std::string t) {
        std::vector<std::vector<int>> dp(s.length(), std::vector<int>(t.length(), 0));
        if(s[0] == t[0]) dp[0][0] = 1;
        for(int i = 1; i < s.length(); i++){
            if(s[i] == t[0]) dp[i][0] = dp[i-1][0] + 1;
            else dp[i][0] = dp[i-1][0];
        }
        for(int i = 1; i < s.length(); i++){
            for(int j = 1; j < t.length(); j++){
                if(s[i] == t[j]) dp[i][j] = (dp[i-1][j-1] + dp[i-1][j]) % 1000000007;
                else dp[i][j] = dp[i-1][j]; // 用s中前i-1个字符来构成t中的前j个字符
            }
        }
        return dp[s.length() - 1][t.length() - 1] % 1000000007;
    }
};

int main(int argc, char argv[]){
    // s = "rabbbit", t = "rabbit"
    std::string str1 = "rabbbit", str2 = "rabbit";
    Solution S1;
    int res = S1.numDistinct(str1, str2);
    std::cout << res << std::endl;
    return 0;
}

27--两个字符串的删除操作

主要思路:

        基于动态规划,dp[i][j]表示以单词word1中第[i-1]字符结尾,word2中第[j-1]字符结尾,使得word1和word2相同所需的最小步数。

        当word[i-1]==word2[j-1]时,dp[i][j] = dp[i-1][j-1];

        当word[i-1] != word2[j-1]时,dp[i][j] = std::min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 2);

#include <iostream>
#include <string>
#include <vector>

class Solution {
public:
    int minDistance(std::string word1, std::string word2) {
        std::vector<std::vector<int>>dp(word1.size() + 1, std::vector<int>(word2.size() + 1, 0));
        // 初始化
        for(int i = 1; i <= word1.size(); i++){
            dp[i][0] = i;
        }
        for(int j = 1; 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] = std::min(dp[i-1][j] + 1, std::min(dp[i][j-1] + 1, dp[i-1][j-1] + 2));
                }
            }
        }
        return dp[word1.size()][word2.size()];
    }
};

int main(int argc, char *argv[]){ 
    // word1 = "sea", word2 = "eat"
    std::string word1 = "sea";
    std::string word2 = "eat";
    Solution S1;
    int res = S1.minDistance(word1, word2);
    std::cout << res << std::endl;

    return 0;
}

28--编辑距离

主要思路:

        dp[i][j] 表示使得word1[0, i-1]和word2[0, j-1]相等的最少操作次数;

        当 word1[i-1] 不等于 word2[j-1] 时,dp[i][j] = std::min(dp[i-1][j-1] + 1, std::min(dp[i-1][j] + 1, dp[i][j-1] + 1));

#include <iostream>
#include <vector>
#include <string>

class Solution {
public:
    int minDistance(std::string word1, std::string word2) {
        // dp[i][j] 表示使得word1[0, i-1]和word2[0, j-1]相等的最少操作次数
        std::vector<std::vector<int>> dp(word1.length() + 1, std::vector<int>(word2.length()+1, 0));
        // 初始化
        for(int i = 1; i <= word1.length(); i++){
            dp[i][0] = i; // word2为空串
        }
        for(int j = 1; j <= word2.length(); j++){
            dp[0][j] = j; // word1为空串
        }

        for(int i = 1; i <= word1.length(); i++){
            for(int j = 1; j <= word2.length(); j++){
                if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1];
                else{
                    // dp[i-1][j-1] + 1 表示替换操作
                    // dp[i-1][j] + 1 表示删除word1[i-1]
                    // dp[i][j-1] + 1 表示增加word1[i-1] 
                    dp[i][j] = std::min(dp[i-1][j-1] + 1, std::min(dp[i-1][j] + 1, dp[i][j-1] + 1));
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
};

int main(int argc, char argv[]){
    // word1 = "horse", word2 = "ros"
    std::string word1 = "horse";
    std::string word2 = "ros";
    Solution S1;
    int res = S1.minDistance(word1, word2);
    std::cout << res << std::endl;
    return 0;
}

29--回文子串

主要思路:

        起始本题更适合用双指针来做,动态规划可以参考代码随想录的思路:回文子串

        布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串;

30--最长回文子序列

主要思路:

        dp[i][j]表示在s[i, j]范围内的最长回文子串;

状态转移

        s[i] == s[j]时,dp[i][j] = dp[i+1][j-1] + 2

        s[i] != s[j]时,dp[i][j] = max(dp[i+1][j], dp[i][j-1])

        dp[i+1][j]表示考虑s[j],不考虑s[i]; dp[i][j-1]表示考虑s[i],不考虑s[j]

        根据递归方程中的i+1和j-1,可知遍历顺序为从下到上,从左到右

#include <iostream>
#include <string>
#include <vector>

class Solution {
public:
    int longestPalindromeSubseq(std::string s) {
        // dp[i][j]表示在s[i, j]范围内的最长回文子串
        std::vector<std::vector<int>> dp(s.length(), std::vector<int>(s.length(), 0));
        // 初始化
        for(int i = 0; i < s.length(); i++) dp[i][i] = 1;
        // 状态转移
        // s[i] == s[j]时,dp[i][j] = dp[i+1][j-1] + 2
        // s[i] != s[j]时,dp[i][j] = max(dp[i+1][j], dp[i][j-1]) 
        // dp[i+1][j]表示考虑s[j],不考虑s[i]; dp[i][j-1]表示考虑s[i],不考虑s[j]
        // 根据递归方程中的i+1和j-1,可知遍历顺序为从下到上,从左到右
        for(int i = s.length() - 2; i >= 0; i--){
            for(int j = i+1; j < s.length(); j++){
                if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2;
                else{
                    dp[i][j] = std::max(dp[i][j-1], dp[i+1][j]);
                }
            }
        }
        return dp[0][s.length() - 1];
    }
};

int main(int argc, char* argv[]){
    // s = "bbbab"
    std::string test = "bbbab";
    Solution S1;
    int res = S1.longestPalindromeSubseq(test);
    std::cout << res << std::endl;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值