当前位置: 首页 > news >正文

LeetCode_动态规划

动态规划

  • 1.动态规划总结
    • 1.1 01背包
      • 1.1.1 二维数组
      • 1.1.2 一维数组
    • 1.2 完全背包
  • 2.斐波那契数(力扣509)
  • 3.爬楼梯(力扣70)
  • 4.使用最小花费爬楼梯(力扣746)
  • 5.不同路径(力扣62)
  • 6.不同路径 II(力扣63)
  • 7.整数拆分(力扣343)
  • 8.不同的二叉搜索树(力扣96)
  • 9.分割等和子集(力扣416)
  • 10.最后一块石头的重量 II(力扣1049)
  • 11.目标和(力扣494)
  • 12.一和零(力扣474)
  • 13.零钱兑换 II(力扣518)
  • 14.组合总和 Ⅳ(力扣377)
  • 15.爬楼梯(力扣70)
  • 16.零钱兑换(力扣322)
  • 17.完全平方数(力扣279)
  • 18.单词拆分(力扣139)
  • 19.打家劫舍(力扣198)
  • 20.打家劫舍 II(力扣213)
  • 21.打家劫舍 III(力扣337)
  • 22.买卖股票的最佳时机(力扣121)
  • 23.买卖股票的最佳时机 II(力扣122)
  • 24.买卖股票的最佳时机 III(力扣123)
  • 25.买卖股票的最佳时机 IV(力扣188)
  • 26.最佳买卖股票时机含冷冻期(力扣309)
  • 27.买卖股票的最佳时机含手续费(力扣714)
  • 28.最长递增子序列(力扣300)
  • 29.最长连续递增序列(力扣674)
  • 30.最长重复子数组(力扣718)
  • 31.最长公共子序列(力扣1143)
  • 32.不相交的线(力扣1035)
  • 33.最大子数组和(力扣53)
  • 34.判断子序列(力扣392)
  • 34.不同的子序列(力扣115)
  • 35.两个字符串的删除操作(力扣583)
  • 36.编辑距离(力扣72)
  • 37.回文子串(力扣647)
  • 38.最长回文子序列(力扣516)

1.动态规划总结

1.1 01背包

注意01背包的遍历顺序
假设背包容量为4,物品重量分别为1,3,4,价值分别为10,15,20,使装入包中的物品的价值最大

1.1.1 二维数组

先物品后背包,每次新增一个物品,判断要不要加入到背包中

		for(int i = 1;i < m;i ++){for(int j = 1;j <= n;j ++){//判断当前物品到底加不加入背包if(j >= nums[i]){dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - nums[i]] + nums[i]);}else{dp[i][j] = dp[i - 1][j];}}}

先背包后物品

		for(int j = 1;j < n;j ++){for(int i = 1;i <= m;i ++){//判断当前物品到底加不加入背包if(j >= nums[i]){dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - nums[i]] + nums[i]);}else{dp[i][j] = dp[i - 1][j];}}}

在这里插入图片描述
两种遍历方式得到的结果相同,不过一个是逐行填写,一个是逐列填写

1.1.2 一维数组

		for(int i = 1;i < m;i ++){for(int j = n;j >= nums[i];j --){dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);}}

一维数组,即滚动数组,先物品后背包,且背包从大到小

如果背包没有从大到小

		for(int i = 1;i < m;i ++){//背包从小到大for(int j = 1;j <= n;j ++){if(j >= nums[i]){dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);}}}

dp[1]=10,dp[2]=20,此时出现添加入重复物品的情况

如果不是先物品后背包

		for(int j = n;j >= 0;j --){for(int i = 1;i < m;i ++){if(j >= nums[i]){dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);}}}

每次只放入一个物品

对于二维数组,时刻想着可到达当前状态的路径(从左上,正上方,正左方)
在这里插入图片描述

1.2 完全背包

在求装满背包有几种方案的时候,认清遍历顺序是非常关键的。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。

2.斐波那契数(力扣509)

class Solution {public int fib(int n) {if (n < 2){return n;}int f0 = 0, f1 = 1;int res = 0;for (int i = 2; i <= n; i++) {res = f0 + f1;f0 = f1;f1 = res;}return res;}
}
class Solution {/* 递归,不过很费时间 */public int fib(int n) {if(n < 2) return n;return fib(n - 1) + fib(n - 2);}
}

3.爬楼梯(力扣70)

//1.动态规划public int climbStairs(int n) {if(n <= 1) return n;//dp[i]:到达第i阶有多少种走法int[] dp = new int[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];}//当然此题也可以像斐波那契数列一样将数组替换为变量

4.使用最小花费爬楼梯(力扣746)

class Solution {public int minCostClimbingStairs(int[] cost) {if(cost == null){return 0;}int len = cost.length;/* dp[i]表示到达第i阶的最小花费。特别注意本题有个坑,"楼梯顶部"也算一级,所以动规数组长度为len+1 */int[] dp = new int[len + 1];/* 第一步不花费体力 */dp[0] = 0;dp[1] = 0;for (int i = 2; i <= len; i ++){dp[i] = Math.min(dp[i - 1] + cost[i - 1],dp[i - 2] + cost[i - 2]);}return dp[len];}
}

5.不同路径(力扣62)

//1.动态规划public int uniquePaths(int m, int n) {//dp[i][j]表示到第i行,第j列的不同路径总数int[][] dp = new int[m][n];for(int i = 0;i < m;i ++){dp[i][0] = 1;}for(int i = 0;i < n;i ++){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];dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}
/* 可以转化为组合问题:即从m+n-2个数中取m-1个数,有多少种方法 */public int uniquePaths3(int m, int n) {long ans = 1;for(int i = n,j = 1;j < m;i ++,j ++){ans = ans * i / j;}return (int)ans;}

6.不同路径 II(力扣63)

class Solution {/* 动态规划 */public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;/* dp[i][j]:表示到达obstacleGrid[i][j]的可走路径数量 */int[][] dp = new int[m][n];/* 初始化第一列的值:如果发现1,则后面的行皆初始化为0 */for(int i = 0;i < m;i ++){if(obstacleGrid[i][0] != 1){dp[i][0] = 1;}else{break;}}/* 初始化第一行的值:如果发现1,则后面的列皆初始化为0 */for(int i = 0;i < n;i ++){if(obstacleGrid[0][i] != 1){dp[0][i] = 1;}else{break;}}for(int i = 1;i < m;i ++){for(int j = 1;j < n;j ++){/* 如果数组中对应位置为1,将可达路径数量置为0 */if(obstacleGrid[i][j] == 1){dp[i][j] = 0;/* 否则,统计从左边和上边到达的路径数量 */    }else{dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}}return dp[m - 1][n - 1];}
}

7.整数拆分(力扣343)

class Solution {//1.动态规划public int integerBreak(int n) {/* dp[i]为正整数i拆分结果的最大乘积 */int[] dp = new int[n + 1];/* 初始化 */dp[2] = 1;for(int i = 3; i <= n; i ++){for(int j = 1; j < i; j ++){/* j*(i-j)代表把i拆分为j和i-j两个数相乘 *//* j*dp[i-j]代表把i拆分成j和继续把(i-j)这个数拆分,* 取(i-j)拆分结果中的最大乘积与j相乘 */dp[i] = Math.max(dp[i], Math.max(j * (i - j),  j * dp[i - j]));}}return dp[n];}
}
//2.找规律/**       n          拆分*       1           0*       2           1+1*       3           1+2*       4           2+2*       5           3+2*       6           3+3*       7           3+4*       8           3+3+2*       9           3+3+3*       10          3+3+4*       11          3+3+3+2*       ...* *///按3拆分,当剩余数等于1时,和前面的3合并为4,剩余数为2时,直接相乘public int integerBreak2(int n) {if(n <= 3) return n - 1;int a = 1;while(n > 4){n -= 3;a *= 3;}return a * n;}

8.不同的二叉搜索树(力扣96)

//1.动态规划/** 解题思路:假设n个节点存在二叉排序树的个数是G(n),1为根节点,2为根节点,...,n为根节点,* 当1为根节点时,其左子树节点个数为0,右子树节点个数为n-1,同理当2为根节点时,其左子树节* 点个数为1,右子树节点为n-2,所以可得G(n) = G(0)*G(n-1)+G(1)*G(n-2)+...+G(n-1)*G(0)* */public int numTrees(int n) {//从1到i分别为头节点组成的二叉搜索树的个数为dp[i]int[] dp = new int[n + 1];dp[0] = 1;//分别以1为根节点,以2为根节点...依次类推,统计所有情况for(int i = 1;i <= n;i ++){// 当以i为根节点时,有i-1种情况:分别对应左子树的数量为0,1...i-1for(int j = 0;j < i;j ++){dp[i] += dp[i - j - 1] * dp[j];}}return dp[n];}

9.分割等和子集(力扣416)

总体思想:
做这道题需要做一个等价转换:是否可以从输入数组中挑选出一些正整数,使得这些数的和 等于 整个数组元素的和的一半。

//1.动态规划(二维数组)public boolean canPartition2(int[] nums) {if(nums == null || nums.length == 0) return false;int len = nums.length;int sum = 0;for(int i : nums){sum += i;}if(sum % 2 != 0) return false;int target = sum / 2;//dp[i][j]代表可装物品为nums[0]-nums[i](因此dp数组一维长度初始化为len),//背包容量为j的情况下,背包内容量的最大价值int[][] dp = new int[len][target + 1];for(int j = 0;j <= target;j ++){if(j >= nums[0]){dp[0][j] = nums[0];}}for(int i = 1;i < len;i ++){for(int j = 0;j <= target;j ++){if(j < nums[i]){dp[i][j] = dp[i - 1][j];}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - nums[i]] + nums[i]);}}}return dp[len - 1][target] == target;}

数组含义:因为最大容量为11,我们要看的是背包里的最大价值,自然就能判断最大子集和是否能够达到11了在这里插入图片描述> 递推公式:> 在这里插入图片描述
画图分析:
在这里插入图片描述
发现此时dp[len - 1][target]刚好为11,返回为true

//2.动态规划(一维数组)public boolean canPartition(int[] nums) {if(nums == null || nums.length == 0) return false;int len = nums.length;int sum = 0;for(int i : nums){sum += i;}if(sum % 2 != 0) return false;
//        dp[j]表示 背包总容量是j,最大可以凑成j的子集总和为dp[j]int[] dp = new int[sum / 2 + 1];for(int i = 0;i < len;i ++){for(int j = sum / 2;j >= nums[i];j --){dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);}}return dp[sum / 2] == sum / 2;}

对于2这种一维数组解法
在这里插入图片描述
遍历j的时候要从后往前遍历,因为从后往前遍历时,dp[j]的改变不会影响dp[j-nums[i]];但是如果是从前往后遍历,dp[j - nums[i]]会影响dp[j]的值,从而出错

10.最后一块石头的重量 II(力扣1049)

此题和力扣416的思路一样

//1.动态规划(二维数组)public int lastStoneWeightII(int[] stones) {if(stones == null || stones.length == 0) return 0;int len = stones.length;int sum = 0;for(int i : stones){sum += i;}int target = sum / 2;//dp[i][j]:从stones[0]到stones[i]这些石头中选择放入容量为//j的背包中,使得背包容纳的石头的重量最大int[][] dp = new int[len][target + 1];for(int j = 0;j <= target;j ++){if(j >= stones[0]){dp[0][j] = stones[0];}}for(int i = 1;i < len;i ++){for(int j = 0;j <= target;j ++){if(j < stones[i]){dp[i][j] = dp[i - 1][j];}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - stones[i]] + stones[i]);}}}return sum - dp[len - 1][target] - dp[len - 1][target];}
//2.动态规划(一维数组)public int lastStoneWeightII2(int[] stones) {if(stones == null || stones.length == 0) return 0;int len = stones.length;int sum = 0;for(int i : stones){sum += i;}int target = sum / 2;int[] dp = new int[target + 1];for(int i = 0;i < len;i ++){for(int j = target;j >= stones[i];j --){//容量为j的背包所能放下的最大石头重量dp[j] = Math.max(dp[j],dp[j - stones[i]] + stones[i]);}}return sum - dp[target] - dp[target];}

11.目标和(力扣494)

这次和之前遇到的背包问题不一样了,之前都是求容量为j的背包,最多能装多少。
本题则是装满背包有几种方法。其实这就是一个组合问题了。
在求装满背包有几种方法的情况下,递推公式一般为dp[j] += dp[j - nums[i]];

//1.动态规划(一维数组)public int findTargetSumWays2(int[] nums, int target) {if(nums == null || nums.length == 0) return 0;int len = nums.length;int sum = 0;for(int i : nums){sum += i;}if((sum + target) % 2 == 1) return 0;int size = (target + sum) / 2;if(size < 0) size = -size;//dp[j]:填满j(包括j)这么大容积的包,有dp[j]种方法int[] dp = new int[size + 1];dp[0] = 1;for(int i = 0;i < len;i ++){for(int j = size;j >= nums[i];j --){dp[j] += dp[j - nums[i]];}}return dp[size];}

12.一和零(力扣474)

//1.动态规划(三维数组)public int findMaxForm(String[] strs, int m, int n) {if(strs == null || strs.length == 0) return 0;int len = strs.length;//dp[i][m][n]:从strs[0]到strs[i]中选取子集,使得子集中最多有m个0和n个1//的最大子集的长度int[][][] dp = new int[len + 1][m + 1][n + 1];for(int i = 1;i <= len;i ++){int[] cur = caculateOneAndZero(strs[i - 1]);for(int j = 0;j <= m;j ++){for(int k = 0;k <= n;k ++){dp[i][j][k] = dp[i - 1][j][k];int zeros = cur[0];int ones = cur[1];if(j >= zeros && k >= ones){//判断当前元素要不要放入背包,如果不放入,保持原样,如果放入,比较原样和加上当前元素之后哪个子集更长dp[i][j][k] = Math.max(dp[i - 1][j][k],dp[i - 1][j - zeros][k - ones] + 1);}}}}return dp[len][m][n];}public int[] caculateOneAndZero(String s){int[] res = new int[2];for(char c : s.toCharArray()){res[c - '0'] ++;}return res;}
//2.动态规划(空间优化)public int findMaxForm2(String[] strs, int m, int n) {if(strs == null || strs.length == 0) return 0;int len = strs.length;//dp[m][n]:从strs[0]到strs[i]中选取子集,使得子集中最多有m个0和n个1//的最大子集的长度int[][] dp = new int[m + 1][n + 1];for(int i = 0;i < len;i ++){int[] cur = caculateOneAndZero(strs[i]);int zeros = cur[0];int ones = cur[1];//后两维都是倒序for(int j = m;j >= zeros;j --){for(int k = n;k >= ones;k --){dp[j][k] = Math.max(dp[j][k],dp[j - zeros][k - ones] + 1);}}}return dp[m][n];}public int[] caculateOneAndZero(String s){int[] res = new int[2];for(char c : s.toCharArray()){res[c - '0'] ++;}return res;}

13.零钱兑换 II(力扣518)

完全背包问题求不同组合的个数:
先遍历物品,再遍历背包;

//1.动态规划(一维数组)public int change(int amount, int[] coins) {if(coins == null || coins.length == 0) return 0;int len = coins.length;//dp[i]:从coins[0]到coins[i]中选取组合,使得总和为amount的不同组合的个数int[] dp = new int[amount + 1];//因为后续dp数组的结果都起于dp[0],所以初始化为1dp[0] = 1;for(int i = 0;i < len;i ++){for(int j = coins[i];j <= amount;j ++){//求个数要累积之前的所有情况dp[j] += dp[j - coins[i]];}}return dp[amount];}

好了,继续之前的问题,这里的内外循环能换吗?
显然不能,因为我们这里定义的子问题是,必须选择第k个硬币时,凑成金额i的方案。如果交换了,我们的子问题就变了,那就是对于金额 i, 我们选择硬币的方案。

14.组合总和 Ⅳ(力扣377)

完全背包问题求不同排列的个数:
先遍历背包,再遍历物品;

//1.动态规划(完全背包求排列总个数)一维数组public int combinationSum4(int[] nums, int target) {if(nums == null || nums.length == 0) return 0;int len = nums.length;//dp[i]:满足总合为i的所有组合的个数(题意中表示的是排列的所有情况)int[] dp = new int[target + 1];dp[0] = 1;//排列个数问题,先遍历背包,再遍历物品//每一次遍历,确定了容量,去找物品for(int j = 0;j <= target;j ++){for(int i = 0;i < len;i ++){if(j >= nums[i]){dp[j] += dp[j - nums[i]];}}}return dp[target];}

15.爬楼梯(力扣70)

//1.动态规划(一维数组)public int climbStairs(int n) {int[] nums = {1,2};//从nums[0]到nums[i]中选取组合,使得总和为n的所有排列的总数目int[] dp = new int[n + 1];dp[0] = 1;for(int j = 0;j <= n;j ++){for(int i = 0;i < nums.length;i ++){if(j >= nums[i]){dp[j] += dp[j - nums[i]];}}}return dp[n];}
//2.动态规划public int climbStairs(int n) {int[] dp = new int[n + 1];dp[0] = 1;dp[1] = 1;for(int i = 2;i <= n;i ++){dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}//当然此题也可以像斐波那契数列一样将数组替换为变量

16.零钱兑换(力扣322)

  1. dp[j] = Math.max(dp[j],dp[j - coins[i]] + 1);
    如果求最大个数,数组初始化为0;使得dp[j]能够及时更新,防止被dp[j]原来的值覆盖
    2.如果求最小个数,数组初始化为Integer.MAX_VALUE;原因也是防止被覆盖
public int coinChange(int[] coins, int amount) {if(coins == null || coins.length == 0) return -1;int len = coins.length;int[] dp = new int[amount + 1];for(int j = 0;j <= amount;j ++){dp[j] = Integer.MAX_VALUE;}dp[0] = 0;for(int i = 0;i < len;i ++){for(int j = coins[i];j <= amount;j ++){if(dp[j - coins[i]] != Integer.MAX_VALUE){dp[j] = Math.min(dp[j],dp[j - coins[i]] + 1);}}}return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}

17.完全平方数(力扣279)

public int numSquares(int n) {int maxIndex = (int)Math.sqrt(n) + 1;int[] weight = new int[maxIndex];for(int i = 1;i <= maxIndex;i ++){weight[i - 1] = i * i;}int[] dp = new int[n + 1];for(int i = 0;i <= n;i ++){dp[i] = Integer.MAX_VALUE;}dp[0] = 0;for(int i = 0;i < weight.length;i ++){for(int j = weight[i];j <= n;j ++){dp[j] = Math.min(dp[j],dp[j - weight[i]] + 1);}}return dp[n];}

18.单词拆分(力扣139)

//1.动态规划(一维数组)public boolean wordBreak(String s, List<String> wordDict) {//valid[j]:长度为j的字符串能否由wordDict中的字符串组成boolean[] valid = new boolean[s.length() + 1];//数组初始化为falseArrays.fill(valid,false);//valid[0]初始化为0,其他结果由valid[0]推出valid[0] = true;for(int i = 1;i <= s.length();i ++){for(int j = 0;j < i;j ++){if(wordDict.contains(s.substring(j,i)) && valid[j]){valid[i] = true;}}}return valid[s.length()];}

19.打家劫舍(力扣198)

//1.动态规划(一维数组)public int rob(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;//dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。int[] dp = new int[len + 1];dp[1] = nums[0];for(int i = 2;i <= len;i ++){dp[i] = Math.max(dp[i - 1],dp[i - 2] + nums[i - 1]);}return dp[len];}

20.打家劫舍 II(力扣213)

//1.动态规划(一维数组)public int rob(int[] nums) {if(nums == null || nums.length == 0) return 0;if(nums.length == 1) return nums[0];if(nums.length == 2) return Math.max(nums[0],nums[1]);//因为首尾不能同时取,将nums分成两部分,返回两部分的最大值中的较大值return Math.max(robRange(nums,0,nums.length - 2),robRange(nums,1,nums.length - 1));}//下面也可以使用变量代替数组进行空间优化public int robRange(int[] nums,int start,int end){int len = end - start + 1;int[] dp = new int[len];dp[0] = nums[start];dp[1] = Math.max(nums[start],nums[start + 1]);for(int i = 2;i < len;i ++){dp[i] = Math.max(dp[i - 1],dp[i - 2] + nums[i + start]);}return dp[len - 1];}

21.打家劫舍 III(力扣337)

//1.递归(超出时间限制)public int rob(TreeNode root) {if(root == null){return 0;}int money = root.val;if(root.left != null){money += (rob(root.left.left) + rob(root.left.right));}if(root.right != null){money += (rob(root.right.left) + rob(root.right.right));}//两种情况,当前节点取和不取return Math.max(money,rob(root.left) + rob(root.right));}
//2.树形动态规划public int rob2(TreeNode root) {int[] ans = robAction(root);//最终取 当前节点取和不取的最大值return Math.max(ans[0],ans[1]);}public int[] robAction(TreeNode root){//res[0]:当前节点不取时获得的最大金额//res[1]:当前节点取时获得的最大金额int[] res = new int[2];if(root == null) return res;int[] left = robAction(root.left);int[] right = robAction(root.right);//当前节点不取,最大值 = (左孩子取和不取的最大值) + (右孩子取和不取的最大值)res[0] = Math.max(left[0],left[1]) + Math.max(right[0],right[1]);//当前节点取,最大值 = 左孩子不取 + 右孩子不取 + 当前节点的值res[1] = left[0] + right[0] + root.val;return res;}

买卖股票问题总结:共有六个股票相关问题
1.只能买卖一次
2.可以买卖多次
3.最多买卖两次
4.最多买卖k次
5.买卖多次,卖出有一天冷冻期
6.买卖多次,每次有手续费

做股票问题时一定要注意定义的是第i天处于j状态,这个状态有可能是之前延续过来的,也有可能是今天才变为这个状态,理解这一点很重要

22.买卖股票的最佳时机(力扣121)

只能买卖一次

//1.动态规划(二维数组)public int maxProfit(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//dp[i][0]:表示第i天手里没有股票获取的最大利润//dp[i][1]:表示第i天手里有股票获取的最大利润int[][] dp = new int[len][2];dp[0][0] = 0;dp[0][1] = -prices[0];for(int i = 1;i < len;i ++){dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]);//因为整个过程中只进行一次股票买卖,dp[i][1] = Math.max(dp[i - 1][1],-prices[i]);dp[i][1] = Math.max(dp[i - 1][1],-prices[i]);}return Math.max(dp[len - 1][0],dp[len - 1][1]);}
//2.数组中记录的不是最大利润,而是最低价格public int maxProfit2(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//dp[i]:到第i天为止的最低价格int[] dp = new int[len];dp[0] = prices[0];int max = 0;for(int i = 1;i < len;i ++){dp[i] = dp[i - 1] < prices[i] ? dp[i - 1] : prices[i];max = max < prices[i] - dp[i] ? prices[i] - dp[i] : max;}return max;}
//3.对2的改进,用变量代替数组public int maxProfit3(int[] prices) {if(prices == null || prices.length == 0) return 0;//记录前i天中的最低价格int min = prices[0];int max = 0;for(int i = 1;i < prices.length;i ++){min = min < prices[i] ? min : prices[i];max = max < prices[i] - min ? prices[i] - min : max;}return max;}

23.买卖股票的最佳时机 II(力扣122)

可以买卖多次

//1.动态规划(二维数组)public int maxProfit(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;int[][] dp = new int[len][2];dp[0][0] = 0;dp[0][1] = -prices[0];for(int i = 1;i < len;i ++){dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]);dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] - prices[i]);}return dp[len - 1][0];}
//2.动态规划优化public int maxProfit2(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;int dp0 = 0;int dp1 = -prices[0];for(int i = 1;i < len;i ++){dp0 = Math.max(dp0,dp1 + prices[i]);dp1 = Math.max(dp1,dp0 - prices[i]);}return dp0;}
//3.贪心算法public int maxProfit3(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;int max = 0;for(int i = 1;i < len;i ++){max += (prices[i] - prices[i - 1] > 0 ? prices[i] - prices[i - 1] : 0);}return max;}

24.买卖股票的最佳时机 III(力扣123)

最多买卖两次

//1.动态规划(二维数组)public int maxProfit(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//dp[i][j]:表示到第i天为止,处于状态j时所获得的最大利润//j=0:没有操作 1:第一次买入 2:第一次卖出 3:第二次买入 4:第二次卖出int[][] dp = new int[len][5];dp[0][1] = -prices[0];dp[0][3] = -prices[0];for(int i = 1;i < len;i ++){dp[i][0] = dp[i - 1][0];dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] - prices[i]);dp[i][2] = Math.max(dp[i - 1][2],dp[i - 1][1] + prices[i]);dp[i][3] = Math.max(dp[i - 1][3],dp[i - 1][2] - prices[i]);dp[i][4] = Math.max(dp[i - 1][4],dp[i - 1][3] + prices[i]);}return dp[len - 1][4];}
//2.动态规划(空间优化)public int maxProfit2(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//第一次买int buy1 = -prices[0];//第一次卖int sale1 = 0;//第二次买int buy2 = -prices[0];//第二次卖int sale2 = 0;for(int i = 1;i < len;i ++){buy1 = Math.max(buy1,-prices[i]);sale1 = Math.max(sale1,buy1 + prices[i]);buy2 = Math.max(buy2,sale1 - prices[i]);sale2 = Math.max(sale2,buy2 + prices[i]);}return sale2;}

25.买卖股票的最佳时机 IV(力扣188)

最多买卖k次

//1.动态规划(三维数组)public int maxProfit(int k, int[] prices) {if(prices == null || prices.length == 0 || k == 0) return 0;int len = prices.length;//dp[i][j][0]:到第i天经过j次买卖最终手里没有股票的最大利润//dp[i][j][1]:到第i天经过j次买卖最终手里有股票的最大利润int[][][] dp = new int[len][k + 1][2];for(int i = 0;i <= k;i ++){dp[0][i][1] = -prices[0];}for(int i = 1;i < len;i ++){for(int j = 1;j <= k;j ++){dp[i][j][0] = Math.max(dp[i - 1][j][0],dp[i - 1][j][1] + prices[i]);dp[i][j][1] = Math.max(dp[i - 1][j][1],dp[i - 1][j - 1][0] - prices[i]);}}return dp[len - 1][k][0];}
//2.动态规划(二维数组)public int maxProfit2(int k, int[] prices) {if(prices == null || prices.length == 0 || k == 0) return 0;int len = prices.length;//dp[i][j]:表示到第i天经过j/2次买(卖),获取的最大利润//j为奇数表示买,j为偶数表示卖int[][] dp = new int[len][2 * k + 1];for(int i = 1;i < 2 * k + 1;i += 2){dp[0][i] = -prices[0];}for(int i = 1;i < len;i ++){for(int j = 1;j < 2 * k + 1;j ++){if(j % 2 == 0){dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] + prices[i]);}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] - prices[i]);}}}return dp[len - 1][2 * k];}

26.最佳买卖股票时机含冷冻期(力扣309)

买卖多次,卖出有一天冷冻期

//1.动态规划(二维数组)public int maxProfit(int[] prices) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//本题的dp可以区分出如下四个状态://0状态一:买入股票状态(今天买入股票,或者是之前就买入了股票然后没有操作)//卖出股票状态,这里就有两种卖出股票状态//1状态二:两天前就卖出了股票,度过了冷冻期,一直没操作,今天保持卖出股票状态//2状态三:今天卖出了股票//3状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天int[][] dp = new int[len][4];dp[0][0] = -prices[0];dp[0][1] = 0;dp[0][2] = 0;dp[0][3] = 0;for(int i = 1;i < len;i ++){dp[i][0] = Math.max(dp[i - 1][0],Math.max(dp[i - 1][1] - prices[i],dp[i - 1][3] - prices[i]));dp[i][1] = Math.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 Math.max(dp[len - 1][1],Math.max(dp[len - 1][2],dp[len - 1][3]));}

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

买卖多次,每次有手续费

//1.动态规划(一维数组)//手续费既可以买入时算,也可以卖出时算public int maxProfit(int[] prices, int fee) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//dp[i][j]:表示到第i天处于j状态时的最大利润//j:0 未持有股票,j:1 持有股票int[][] dp = new int[len][2];dp[0][0] = 0;//手续费买入时算dp[0][1] = -prices[0] - fee;for(int i = 1;i < len;i ++){dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]);dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] - prices[i] - fee);}return dp[len - 1][0];}
//2.动态规划(优化空间复杂度)public int maxProfit2(int[] prices, int fee) {if(prices == null || prices.length == 0) return 0;int len = prices.length;//dp[i][j]:表示到第i天处于j状态时的最大利润//j:0 未持有股票,j:1 持有股票int dp0 = 0;int dp1 = -prices[0] - fee;for(int i = 1;i < len;i ++){dp0 = Math.max(dp0,dp1 + prices[i]);dp1 = Math.max(dp1,dp0 - prices[i] - fee);}return dp0;}

子序列问题总结:注意要求连续/非连续
在这里插入图片描述

28.最长递增子序列(力扣300)

//1.动态规划(一维数组)public int lengthOfLIS(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;//dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度int[] dp = new int[len];Arrays.fill(dp,1);for(int i = 1;i < len;i ++){for(int j = 0;j < i;j ++){if(nums[i] > nums[j]){dp[i] = Math.max(dp[i],dp[j] + 1);}}}//如果当前位置的值比他前面的值都小,则当前位置的最长序列长度为1//所以不能直接返回dp[len - 1],应该取dp数组中的最大值int max = 0;for(int i : dp){max = Math.max(max,i);}return max;}

在这里插入图片描述

//2.降低时间复杂度到O(nlogn)//相当于维护一个结果数组,如果当前元素比结果数组的值都大的的话,就追加在结果数组后//面(相当于递增序列长度加了1);否则的话用当前元素覆盖掉第一个比它大的元素(这样//做的话后续递增序列才有可能更长,即使并没有更长,这个覆盖操作也并没有副作用哈,当//然这个覆盖操作可能会让最终的结果数组值并不是最终的递增序列值,这无所谓)public int lengthOfLIS2(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = 1, n = nums.length;int[] d = new int[n + 1];d[len] = nums[0];for (int i = 1; i < n; ++i) {if (nums[i] > d[len]) {d[++len] = nums[i];} else {// 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将pos设为 0int l = 1, r = len, pos = 0;while (l <= r) {int mid = (l + r) >> 1;if (d[mid] < nums[i]) {pos = mid;l = mid + 1;} else {r = mid - 1;}}d[pos + 1] = nums[i];}}return len;}

29.最长连续递增序列(力扣674)

//1.动态规划(一维数组)public int findLengthOfLCIS(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;//dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。int[] dp = new int[len];dp[0] = 1;for(int i = 1;i < len;i ++){if(nums[i] > nums[i - 1]){dp[i] = dp[i - 1] + 1;}else{//如果不满足递增条件,则下一个序列起始为此位置的值,因此重设为1dp[i] = 1;}}int max = 0;for(int i : dp){max = Math.max(max,i);}return max;}
//2.优化空间复杂度public int findLengthOfLCIS2(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;int count = 1,max = 1;for(int i = 1;i < len;i ++){if(nums[i] > nums[i - 1]){count ++;}else{count = 1;}max = Math.max(max,count);}return max;}

30.最长重复子数组(力扣718)

//1.动态规划public int findLength(int[] nums1, int[] nums2) {int max = 0;//dp[i][j] :以下标i-1为结尾的A,和以下标j-1为结尾的B,最长重复子数组长度为dp[i][j]。int[][] dp = new int[nums1.length + 1][nums2.length + 1];for(int i = 1;i < nums1.length + 1;i ++){for(int j = 1;j < nums2.length + 1;j ++){if(nums1[i - 1] == nums2[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;}max = max < dp[i][j] ? dp[i][j] : max;}}return max;}
//2.滚动数组public int findLength2(int[] nums1, int[] nums2) {int max = 0;int[] dp = new int[nums2.length + 1];for(int i = 1;i <= nums1.length;i ++){for(int j = nums2.length;j > 0;j --){if(nums1[i - 1] == nums2[j - 1]){dp[j] = dp[j - 1] + 1;}else{dp[j] = 0;}if(dp[j] > max) max = dp[j];}}return max;}

31.最长公共子序列(力扣1143)

//1.动态规划public int longestCommonSubsequence(String text1, String text2) {int len1 = text1.length(),len2 = text2.length();//dp[i][j]:长度为[0, i-1]的字符串text1与长度为[0,j-1]的字符串text2的最长公共子序列为dp[i][j]int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(text1.charAt(i - 1) == text2.charAt(j - 1)){dp[i][j] = dp[i - 1][j - 1] + 1;}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);}}}return dp[len1][len2];}

32.不相交的线(力扣1035)

//1.动态规划(原理和1143一样)public int maxUncrossedLines(int[] nums1, int[] nums2) {if(nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) return 0;int len1 = nums1.length,len2 = nums2.length;int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(nums1[i - 1] == nums2[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);}}}return dp[len1][len2];}

33.最大子数组和(力扣53)

//1.动态规划public int maxSubArray(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;//dp[i]:表示nums在下标为[0,i]范围内连续子数组的最大和为dp[i]int[] dp = new int[len];dp[0] = nums[0];int max = nums[0];for(int i = 1;i < len;i ++){if(dp[i - 1] + nums[i] <= nums[i]){dp[i] = nums[i];}else{dp[i] = dp[i - 1] + nums[i];}max = dp[i] > max ? dp[i] : max;}return max;}
//2.public int maxSubArray2(int[] nums) {if(nums == null || nums.length == 0) return 0;int len = nums.length;int max = nums[0],pre = nums[0];for(int i = 1;i < len;i ++){pre = Math.max(pre + nums[i],nums[i]);max = Math.max(max,pre);}return max;}
//3.从头开始遍历,如果发现前面的和对后面没有帮助,直接舍弃,更新为当前位置元素的值public int maxSubArray3(int[] nums) {int len = nums.length;int res = nums[0],sum = 0;for(int i = 0;i < len;i ++){if(sum > 0){sum += nums[i];}else{sum = nums[i];}res = Math.max(res,sum);}return res;}

34.判断子序列(力扣392)

//1.双指针public boolean isSubsequence(String s, String t) {int len1 = s.length(),len2 = t.length();if(len1 == 0) return true;if(len1 != 0 && len2 == 0) return false;int i = 0,j = 0;while(i < len1 && j < len2){if(s.charAt(i) == t.charAt(j)){i ++;j ++;}else{j ++;}}return i == len1;}
//2.动态规划public boolean isSubsequence2(String s, String t) {int len1 = s.length(),len2 = t.length();//dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(s.charAt(i - 1) == t.charAt(j - 1)){dp[i][j] = dp[i - 1][j - 1] + 1;}else{//因为从t中找s,如果当前对应字符不相同,则t删除当前字符,相当于回退dp[i][j] = dp[i][j - 1];}}}return dp[len1][len2] == len1;}

34.不同的子序列(力扣115)

为啥状态方程是:
s[i] == t[j] 时 dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
s[i] != t[j] 时 dp[i][j] = dp[i-1][j]

先看s[i] == t[j] 时,以s = “rara” t = “ra” 为例,当i = 3, j = 1时,s[i] == t[j]。

此时分为2种情况,s串用最后一位的a + 不用最后一位的a。

如果用s串最后一位的a,那么t串最后一位的a也被消耗掉,此时的子序列其实=dp[i-1][j-1]

如果不用s串最后一位的a,那就得看"rar"里面是否有"ra"子序列的了,就是dp[i-1][j]

所以 dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

再看s[i] != t[j] 比如 s = “rarb” t = “ra” 还是当i = 3, j = 1时,s[i] != t[j]

此时显然最后的b想用也用不上啊。所以只能指望前面的"rar"里面是否有能匹配"ra"的

所以此时dp[i][j] = dp[i-1][j]

//1.动态规划public int numDistinct(String s, String t) {int len1 = s.length(),len2 = t.length();//dp[i][j]:以s[i-1]为结尾的s子序列中出现以t[j-1]为结尾的t的子序列的个数为dp[i][j]。int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 0;i < len1 + 1;i ++){dp[i][0] = 1;}for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(s.charAt(i - 1) == t.charAt(j - 1)){dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];}else{dp[i][j] = dp[i - 1][j];}}}return dp[len1][len2];}

35.两个字符串的删除操作(力扣583)

//1.动态规划(先求两字符串的最大公共子序列的长度max,再用len1 + len2 - 2 * max即为结果)public int minDistance(String word1, String word2) {int len1 = word1.length(),len2 = word2.length();int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(word1.charAt(i - 1) == word2.charAt(j - 1)){dp[i][j] = dp[i - 1][j - 1] + 1;}else{dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);}}}return len1 + len2 - 2 * dp[len1][len2];}
//2.动态规划public int minDistance2(String word1, String word2) {int len1 = word1.length(),len2 = word2.length();//dp[i][j]:表示使得以word1[i-1]结尾的子序列和以word2[j-1]结尾的子序列相等的最小删除步数int[][] dp = new int[len1 + 1][len2 + 1];for(int i = 0;i < len1 + 1;i ++){dp[i][0] = i;}for(int j = 0;j < len2 + 1;j ++){dp[0][j] = j;}for(int i = 1;i < len1 + 1;i ++){for(int j = 1;j < len2 + 1;j ++){if(word1.charAt(i - 1) == word2.charAt(j - 1)){//如果相等,不需要删除dp[i][j] = dp[i - 1][j - 1];}else{//如果不相等dp[i][j] = Math.min(dp[i - 1][j - 1] + 2,Math.min(dp[i - 1][j] + 1,dp[i][j - 1] + 1));}}}return dp[len1][len2];}

36.编辑距离(力扣72)

//1.动态规划/** 两个字符串a,b,三种操作,所以总共有六种情况* a删除一个和b添加一个是等效的,例如:a为doge,b为dog,a删除最后的e和b后面添加e的结果是等价的* a添加一个和b删除一个是等价的,同理;* a替换一个和b替换一个是等价的,例如:a为bat,b为cat,a将'b'换为'c'和b将'c'换为'b'是等价的,* 所有六种情况就缩减为三种* 1.a删除一个* 2.b删除一个* 3.a替换一个* */public int minDistance(String word1, String word2) {int len1 = word1.length();int len2 = word2.length();//dp[i][j]:表示使得以word1[i-1]结尾的子序列和以word2[j-1]结尾的子序列相等的//对word1和word2的最少操作次数int[][] dp = new int[len1 + 1][len2 + 1];// 初始化for (int i = 0; i <= len1; i++) {dp[i][0] =  i;}for (int j = 0; j <= len2; j++) {dp[0][j] = j;}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {//如果当前两个字符相等,不做任何操作if (word1.charAt(i - 1) == word2.charAt(j - 1)) {dp[i][j] = dp[i - 1][j - 1];//如果当前两个字符不相等/** dp[i - 1][j - 1]:a替换一个* dp[i][j - 1]:b删除一个* dp[i - 1][j]:a删除一个* */} else {dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;}}}return dp[len1][len2];}

37.回文子串(力扣647)

//1.动态规划//由于dp[i][j]的状态由dp[i+1][j-1]的状态决定,所以遍历顺利是从下向上,从左向右//从此题也可以看出“动规5部曲的重要性”public int countSubstrings(String s) {int len = s.length();//布尔类型的dp[i][j]:表示区间范围[i,j]左闭右闭的子串是否是回文子串,//如果是 dp[i][j]为true,否则为false。boolean[][] dp = new boolean[len][len];for(int i = len - 1;i >= 0;i --){for(int j = 0;j < len;j ++){//如果相同if(s.charAt(i) == s.charAt(j)){//如果下标相等,说明只有一个字符,自然为trueif(i == j) dp[i][j] = true;//如果下标值相差为1,说明有两个字符,为trueif(j - i == 1) dp[i][j] = true;//如果下标差值相差大于1,其状态由其内部的字符串决定if(j - i > 1) dp[i][j] = dp[i + 1][j - 1];}}}int count = 0;for(int i = 0;i < len;i ++){for(int j = i;j < len;j ++){//统计所有为true的数量if(dp[i][j]){count ++;}}}return count;}
//2.双指针public int countSubstrings2(String s) {if(s == null || s.length() == 0) return 0;int len = s.length();int count = 0;//一个长度为len的字符串,可作为中心点的位置共有len*2-1个/** 例如:以abc为例* a   b   c* 1 2 3 4 5* 一共5个可以作为中心点的位置* */for(int i = 0;i < 2 * len - 1;i ++){int left = i / 2,right = left + i % 2;while(left >= 0 && right < len && s.charAt(left) == s.charAt(right)){//固定住一个中心,分别向两边扩展left --;right ++;count ++;}}return count;}

38.最长回文子序列(力扣516)

//1.动态规划public int longestPalindromeSubseq(String s) {if(s == null || s.length() == 0) return 0;int len = s.length();//dp[i][j]:表示下标为[i,j]范围的子序列的最长回文串长度int[][] dp = new int[len][len];//因为dp[i][j] = dp[i+1][j-1] + 2,所以j-i一定要大于1,for(int i = 0;i < len;i ++){dp[i][i] = 1;}for(int i = len - 1;i >= 0;i --){for(int j = i + 1;j < len;j ++){if(s.charAt(i) == s.charAt(j)){dp[i][j] = dp[i+1][j-1] + 2;}else{dp[i][j] = Math.max(dp[i][j-1],dp[i+1][j]);}}}return dp[0][len - 1];}
http://www.dtcms.com/a/343160.html

相关文章:

  • 【NLP(01)】NLP(自然语言处理)基础
  • nginx-自制证书实现
  • Python学习 -- MySQL数据库的查询及案例
  • 自然语言处理——03 RNN及其变体
  • C++ 命名规范示意表
  • iOS 应用上架瓶颈与解决方案 从开发到审核的全流程实战
  • 机器学习中的聚类与集成算法:从基础到应用
  • word参考文献对齐
  • week3-[循环嵌套]好数
  • 交易所开发实战:打造安全高效的数字货币交易平台
  • 使用java制作minecraft3.0版本
  • 什么是默克尔树
  • Android系统框架知识系列(十三):Sensor Manager Service - Android的感官世界
  • Trae配置rules与MCP
  • 企业微信+AI在金融行业落地:从部署到场景的实践路径
  • CLruCache::BucketFromIdentifier函数分析
  • CroCT
  • 在互联网大厂的Java面试:谢飞机的搞笑历险记
  • Uniapp非脚手架项目打包为5+ App后,在Android端按返回键会意外退出应用。
  • 基于昇腾玩转电影级视频生成模型Wan 2.2
  • ES_索引的操作
  • 基础网络模型
  • 【矩池云】实现Pycharm远程连接,上传数据并解压缩
  • 为什么程序部署到线上,就无法读取环境变量了
  • B2B工业品制造业TOB大客户营销培训老师培训师唐兴通谈AI数字化销售AI销冠底层逻辑数字化转型创新增长业绩
  • MyBatis-Plus MetaObjectHandler的几个坑(主要是id字段)
  • 《AI智脉速递》2025 年 8 月15 日 - 21 日
  • JetBrains 内的 GitHub Copilot Agent Mode + MCP:从配置到实战
  • vmware安装centos7
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第二章知识点问答(21题)