LeetCode——动态规划(Java)

动态规划

  • 简介
  • [简单] 509. 斐波那契数
  • [简单] 70. 爬楼梯
  • [简单] 746. 使用最小花费爬楼梯
  • [中等] 62. 不同路径
  • [中等] 63. 不同路径 II
  • [中等] 343. 整数拆分
  • [中等] 96. 不同的二叉搜索树
  • 背包问题
    • 01背包
      • [中等] 416. 分割等和子集
      • [中等] 1049. 最后一块石头的重量 II
      • [中等] 494. 目标和
      • [中等] 474. 一和零
    • 完全背包
      • [中等] 518. 零钱兑换 II
      • [中等] 377. 组合总和 Ⅳ
      • [中等] 322. 零钱兑换
      • [中等] 279. 完全平方数
      • [中等] 139. 单词拆分
  • [中等] 198. 打家劫舍
  • [中等] 213. 打家劫舍 II
  • 树形DP
    • [中等] 337. 打家劫舍 III
  • [简单] 121. 买卖股票的最佳时机
  • [中等] 122. 买卖股票的最佳时机 II
  • [中等] 123. 买卖股票的最佳时机 III
  • [中等] 188. 买卖股票的最佳时机 IV
  • [中等] 309. 买卖股票的最佳时机含冷冻期
  • [中等] 714. 买卖股票的最佳时机含手续费
  • [中等] 300. 最长递增子序列
  • [简单] 674. 最长连续递增序列
  • [中等] 718. 最长重复子数组
  • [中等] 1143. 最长公共子序列

简介

记录一下自己刷题的历程以及代码。写题过程中参考了 代码随想录的刷题路线。会附上一些个人的思路,如果有错误,可以在评论区提醒一下。

[简单] 509. 斐波那契数

原题链接

简单的递归

public int fib(int n) {if(n == 0) return 0;else if(n == 1) return 1;else return fib(n - 1) + fib(n - 2);
}

动态规划

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

[简单] 70. 爬楼梯

原题链接

同斐波那契数一样

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

[简单] 746. 使用最小花费爬楼梯

原题链接

class Solution {public int minCostClimbingStairs(int[] cost) {int length = cost.length;int[] dp = new int[length + 1];dp[0] = 0;dp[1] = 0;for(int i = 2; i <= length; i++){dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);}return dp[length];}
}

[中等] 62. 不同路径

原题链接

先确定dp数组表示是每个网格有多少路径
机器人只能向下或者向右,这样的话推导式就是dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
第一行和第一列,因为只能选择一个方向,路径都是1,初始化dp时进行赋值。

class Solution {public int uniquePaths(int m, int n) {int[][] dp = new int[m][n];dp[0][0] = 0;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];}}return dp[m - 1][n - 1];}
}

[中等] 63. 不同路径 II

原题链接

在上一题的基础中,做障碍判断
初始网格需要做判断,有可能在起点出现障碍而不可达
把第一行和第一列的逻辑改为dp[i][0] = dp[i - 1][0];,因为当出现障碍的时候,第一行或者第一列的网格是不可达的,

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int[][] dp = new int[m][n];if(obstacleGrid[0][0] == 1) dp[0][0] = 0;else dp[0][0] = 1;for(int i = 1; i < m; i++){if(obstacleGrid[i][0] == 0)dp[i][0] = dp[i - 1][0];}for(int i = 1; i < n; i++){if(obstacleGrid[0][i] == 0)dp[0][i] = dp[0][i - 1];}for(int i = 1; i < m; i++){for(int j = 1; j < n; j++){if(obstacleGrid[i][j] == 1){dp[i][j] = 0;continue;}dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}
}

[中等] 343. 整数拆分

原题链接

dp数组保存的值是:正整数i拆分后的最大乘积
递推公式就是在循环中 将i拆解为两个数的和,找出不同组合中乘积最大的情况。

初始化的时候注意,dp[2] 以及 dp[3] 用自身值放入数组。

从下图可以知道,下标i == 2 或者 i == 3 的情况下,拆分成两个数以上的乘积值比本身小,意味着之后的数字中如果拆出了3,由于3继续拆分只会比本身更小,所以没有拆分必要。举个例子,手推这个过程的时候,比如10 = 3 + 7,理论上7 = 3 + 4,3与4的乘积大于7,所以最后10 = 3 + 3 + 4,但是3则没有继续拆分的必要。

手推一下这个过程比较容易理解
在这里插入图片描述

注意:如果想保证使用dp数组就能完成所有返回值,可以把 dp[j] 和 j 的大小判断逻辑加入到max的取值中进行判断。但我推导下来,会出现这种情况的其实只有2和3,所以我进行了单独设置。

class Solution {public int integerBreak(int n) {// 每个正整数可以化成的最大乘积int[] dp = new int[n + 1];if(n ==2) return 1;else if(n == 3) return 2;dp[2] = 2;if(n >= 3) dp[3] = 3;for(int i = 4; i <= n; i++){int max = Integer.MIN_VALUE;for(int j = i/2; j > 1; j--){int num = dp[j] * dp[i - j];max = num > max ? num : max;}dp[i] = max;}return dp[n];}
}

[中等] 96. 不同的二叉搜索树

原题链接

dp数组表示的是 i 个结点有dp[ i ] 种摆放方法摆出二叉搜索树

递推式就是取 j 作为根节点,左边有 j - 1 个结点, 右边 有 i - j 个结点,二者的摆放方法数相乘即为:以j为根节点,节点[1, i] 的摆放方法数。

dp[ 0 ] 取值 为1主要是为了后续相乘时正确处理,也可以理解为, 0 个节点只有一种摆放方式,就是没得摆。

class Solution {public int numTrees(int n) {int[] dp = new int[n + 1];dp[0] = 1;dp[1] = 1;if(n >= 2) dp[2] = 2;for(int i = 3; i <= n; i++) {for(int j = 1; j <= i; j++){dp[i] += dp[i - j] * dp[j - 1];}}return dp[n];}
}

背包问题

01背包

[中等] 416. 分割等和子集

原题链接

动态规划:01背包解法
数组总和 sum / 2 就是背包的最大容量,nums数组看做物品,重量和价值都是nums[ i ],只要最后dp[dp.length - 1] == dp.length - 1就说明能够取到总和一半的组合

可以看一看代码随想录:一维背包中一维背包的设计思想,自己递推的时候脑袋里按照二维背包的思路去规划

dp数组表示:背包限制为i 的 情况下可以取到的最大总和
递推公式:不取当前数字,且限制为i 的最大总和 dp[ j ]
取当前数字,限制为 i 的最大总和dp[j - nums[i]] + nums[i],二者取最大值

二重循环中第一层循环是遍历[0 ,i]的物品允许取的情况,二层循环是背包总和限制为 j 的情况
内层循环需要保证倒序,因为使用一维数组的情况下,正序遍历会把上一层物品状态覆盖。

for(int i = 1; i < nums.length; i++){for(int j = dp.length - 1; j >= nums[i]; j--){dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);}}
class Solution {public boolean canPartition(int[] nums) {int sum = 0;for(int i = 0; i < nums.length; i++)sum += nums[i];if(sum % 2 == 1) return false;int[] dp = new int[sum / 2 + 1];for(int i = 1; i < dp.length; i++){if(i >= nums[0]) dp[i] = nums[0];}//倒序遍历是保证每个数字只取一次for(int i = 1; i < nums.length; i++){for(int j = dp.length - 1; j >= nums[i]; j--){dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);}}if(dp[dp.length - 1] == dp.length - 1) return true;return false;}
}

[中等] 1049. 最后一块石头的重量 II

原题链接

和上一题 [中等] 416. 分割等和子集 相像,其实就是找出两堆重量尽量相近的石头。

如果给出一个背包,最大容量为j,dp[j] 就是他能取到的最大石头重量和,dp数组最大下标为sum / 2,也就是一个石头堆:dp[dp.length - 1]就是这一半石头堆能够取到的最大重量。sum - dp[dp.length - 1]就是另一个石头堆,且若两个石头堆重量无法相等,后者一定比前者大,所以最后的返回值就是二者之差

class Solution {public int lastStoneWeightII(int[] stones) {int sum = 0;for(int i = 0; i < stones.length; i++)sum += stones[i];int[] dp = new int[sum / 2 + 1];for(int i = 1; i < dp.length; i++){if(i >= stones[0]) dp[i] = stones[0];}//倒序遍历是保证每个数字只取一次for(int i = 1; i < stones.length; i++){for(int j = dp.length - 1; j >= stones[i]; j--){dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);}}return sum - dp[dp.length - 1] - dp[dp.length - 1];}
}

[中等] 494. 目标和

原题链接
在这里插入图片描述

class Solution {public int findTargetSumWays(int[] nums, int target) {int sum = 0;for(int i = 0; i < nums.length; i++){sum += nums[i];}//表示加法集合不是整数,也就是无法取到if((sum + target) % 2 != 0) return 0;//target 绝对值 大于 sum 同样无法取到if((Math.abs(target) > sum)) return 0;//表示数字总和取到 j 有dp[j]种方案int[] dp = new int[(sum +target) / 2 + 1];//target == 0,一个数都不取 为一种方案dp[0] = 1;//倒序遍历是保证每个数字只取一次for(int i = 0; i < nums.length; i++){for(int j = dp.length - 1; j >= nums[i]; j--){dp[j] += dp[j - nums[i]];}}return dp[dp.length - 1];}
}

[中等] 474. 一和零

原题链接

标准的01背包,只是对物品重量的考虑变成二维的。
dp表示:限制 i个0 和 j个1 情况下的最大子集长度
递推公式从二者取最大值:dp[ i ] [ j ] 表示不取当前物品,dp[i - zeroNum][j - oneNum] + 1表示取当前物品
对dp的遍历都是从后往前,因为都是对背包容量的遍历,不能覆盖先前值,把二维遍历对应到普通01背包的一维遍历即可

可以在考虑每个字符串时对dp数组做输出观察状态,更易于理解:

class Solution {public int findMaxForm(String[] strs, int m, int n) {// 限制 i个0 和 j个1 情况下的最大子集长度int[][] dp = new int[m + 1][n + 1];for(String str : strs){char[] chars = str.toCharArray();int zeroNum = 0;int oneNum = 0;for(char c : chars){if(c == '0')zeroNum++;else oneNum++;}for(int i = m; i >= zeroNum; i--){for(int j = n; j >= oneNum; j--){dp[i][j] = Math.max(dp[i - zeroNum][j - oneNum] + 1, dp[i][j]);}}System.out.println("当前考虑字符串:" + str);for(int i = 0; i <= m; i++){for(int j = 0; j <=n ;j++){System.out.print(dp[i][j] + " ");}System.out.println();}System.out.println();}return dp[m][n];}
}
public class main {public static void main(String[] args) {Solution solution = new Solution();System.out.println(solution.findMaxForm(new String[]{"10","0001","111001","1","0"}, 5, 3));}
}

完全背包

  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
    外层遍历物品,考虑过的物品不会再回头考虑,就不用考虑排序问题,每个组合都是唯一的。

  • 如果求排列数就是外层for遍历背包,内层for循环遍历物品。

[中等] 518. 零钱兑换 II

原题链接

突然觉得和回溯法的题目很像,可以看看LeetCode——回溯算法(Java) 中39题,把里面的代码的返回值List<List<Integer>> ans输出size也能通过测试,但是在正式提交时回出现爆内存的情况,因为本题中是不需要具体方案,只需要给出方案数,这或许就是碰到一个题目是使用回溯还是动态规划的一个判断角度。

本题是一个完全背包,跟01背包的差距就是完全背包的物品不限制选取次数

dp数组表示:背包容量为 j 时,最多有dp[ j ] 种方案
递归公式:第二层对背包容量的遍历采用正序,因为区别于01背包,物品可以选择多次。

初始化时:
如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选择,可以理解为amount == 0 时选择0个物品为1种方案。

class Solution {public int change(int amount, int[] coins) {int[]dp = new int[amount + 1];dp[0] = 1;for (int i = 0; i < coins.length; i++){for(int j = coins[i]; j <= amount; j++){dp[j] += dp[j - coins[i]];}}return dp[amount];}
}

[中等] 377. 组合总和 Ⅳ

同上一题一样,可以和回溯法的题目一起思考,如果本题要把排列都列出来的话,只能使用回溯算法爆搜。本题就是求排列问题,所以外层循环为背包容量。

class Solution {public int combinationSum4(int[] nums, int target) {int[]dp = new int[target + 1];dp[0] = 1;for(int i = 1; i <= target; i++){for(int j = 0; j < nums.length; j++){if(i >= nums[j]){dp[i] += dp[i - nums[j]];}}}return dp[target];}
}

[中等] 322. 零钱兑换

原题链接

dp[j]:凑足总额为j所需钱币的最少个数为dp[j]

递推公式:dp[j]表示不考虑本次循环到的钱币,dp[j - coins[i]] + 1表示考虑一个coins[i]

初始化dp[i] = Integer.MAX_VALUE表示没有方法能够凑到总额j,也用于后续比较最小值被有方法的情况覆盖。

在这里插入图片描述

class Solution {public int coinChange(int[] coins, int amount) {int[]dp = new int[amount + 1];dp[0] = 0;for(int i = 1; i < dp.length; i++) dp[i] = Integer.MAX_VALUE;for(int i = 0; i < coins.length; 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);}}}if(dp[amount] == Integer.MAX_VALUE) return -1;return dp[amount];}
}

[中等] 279. 完全平方数

原题链接

dp数组:和为 j 的完全平方数的最少数量为 dp[j]

递推公式:dp[ j ] 表示不考虑选当前数字 i * i 的取值数量,dp[j - i * i] + 1表示选取一个当前数字i * i 的取值数量,因为是完全背包问题,二层循环从前往后遍历,同一个数字可以多次选取

初始化,除了dp[ 0 ] 用于做最小值比较,并且本身题目 n 范围并不包括0,其余设置Integer.MAX_VALUE,用于被覆盖,因为 1 也属于完全平方数,所以最后的数组每个元素必定是有最小方案数的,不存在还有Integer.MAX_VALUE的情况

class Solution {public int numSquares(int n) {// 和为 j 的完全平方数的最少数量为 dp[j]int[] dp = new int[n + 1];dp[0] = 0;for(int j = 1; j <= n; j++){dp[j] = Integer.MAX_VALUE;}for(int i = 1; i <= 100; i++){for(int j = i * i; j <= n; j++){dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}return dp[n];}
}

[中等] 139. 单词拆分

原题链接

只是在基础的完全背包上添加了一些字符串操作

dp数组:从左到右长度为 j 的字符串的是否能够被表示 ,dp[j]为布尔值

递推公式:每一层容量考虑时都需要考虑wordDict中的所有字符串,所以,容量为外层循环,字符串集为内层循环,以 j 为末端的子串如果在wordDict集中,那么它是否能够被表示取决于[j - length]length为子串长度。
并且如果当前层 dp[ j ] 已经为true,找到方案的情况下,就可以跳出本次循环。题目中并没有需要提供方案数。

初始化:dp[0]true用于后续做判断

class Solution {public boolean wordBreak(String s, List<String> wordDict) {boolean[] dp = new boolean[s.length() + 1];dp[0] = true;for(int j = 1; j <= s.length() ; j++){for(int i = 0; i < wordDict.size(); i++){int length = wordDict.get(i).length();if(j < length) continue;String str = s.substring(j - length, j);if(str.equals(wordDict.get(i))){dp[j] = dp[j - length];if(dp[j]) break;}}}return dp[s.length()];}
}

[中等] 198. 打家劫舍

原题链接

class Solution {public int rob(int[] nums) {// [0,j] 的 最大收益为 dp[j]int[] dp = new int[nums.length];dp[0] = nums[0];if(nums.length > 1) dp[1] = Math.max(nums[1], dp[0]);for(int i = 2; i < nums.length; i++){dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[nums.length - 1];}
}

[中等] 213. 打家劫舍 II

原题链接

有点讨巧的方法,调用函数保留线性打家劫舍的方案,调用时两种情况,不考虑头或者不考虑尾,强制把环形问题拆分为线性考虑。

class Solution {public int rob(int[] nums) {if(nums.length == 1) return nums[0];int result1 = rob(nums, 0, nums.length - 1);int result2 = rob(nums, 1, nums.length);return Math.max(result1, result2);} public int rob(int[] nums, int start, int end) {int length = end - start;// [0,j] 的 最大收益为 dp[j]int[] dp = new int[nums.length];dp[start] = nums[start];if(length > 1) dp[start + 1] = Math.max(nums[start + 1], dp[start]);for(int i = start + 2; i < end; i++){dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);}return dp[end - 1];}
}

树形DP

[中等] 337. 打家劫舍 III

原题链接

可以设置二维dp数组保存遍历过的节点状态,但是由于本题只需要子树的状态,所以可以直接使用返回值,int[0] 表示偷窃当前节点, int[1]表示没有盗窃当前节点

class Solution {public int rob(TreeNode root) {int[] nums = recursion(root);return Math.max(nums[0], nums[1]);}// int[0] 表示偷窃当前节点, int[1]表示没有盗窃当前节点public int[] recursion(TreeNode root) {if(root == null) return new int[]{0 ,0};else if(root.left == null && root.right == null){return new int[]{root.val, 0};}int[] left = recursion(root.left);int[] right = recursion(root.right);// 偷窃当前节点int value0 = root.val + left[1] + right[1];// 不偷窃当前节点int value1 = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);return new int[]{value0, value1};}
}

[简单] 121. 买卖股票的最佳时机

原题链接

class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[prices.length][2];//dp[j][0] 表示第j天持有股票的最大现金,dp[j][1]表示第j天不持有股票的最大现金;int result = 0;dp[0][0] = -prices[0];dp[0][1] = 0;for(int i = 1; i < prices.length; i++){dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);}return dp[prices.length - 1][1];}
}

[中等] 122. 买卖股票的最佳时机 II

原题链接

class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[prices.length][2];//dp[0] 表示第i - 1不持有股票的最大现金,dp[1]表示第i- 1天持有股票的最大现金;dp[0][0] = 0;dp[0][1] = -prices[0];for(int i = 1; i < prices.length; 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[prices.length - 1][0];}
}

[中等] 123. 买卖股票的最佳时机 III

原题链接

题目中有五种状态,用dp二维下标 [0, 4]表示,0 未买入,1 第一次买入, 2 第一次卖出, 3 第二次买入,4 第二次卖出。

初始化的时候,其实是代入了当天买入卖出,收益为0 这样的情况。最大的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出。如果第一次卖出已经是最大值了,那么我们可以在当天立刻买入再立刻卖出。所以dp[prices.length - 1][4]已经包含了dp[prices.length - 1][2]的情况。也就是说第二次卖出手里所剩的钱一定是最多的。

其实通过观察动态转移方程可以发现,dp数组是可以压缩为一维的,不过那样写会比较绕,空间上会更节省资源

class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[prices.length][5];// 0 未买入,1 第一次买入, 2 第一次卖出, 3 第二次买入,4 第二次卖出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.length; i++){dp[i][0] = dp[i - 1][0];dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);dp[i][3] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][3]);dp[i][4] = Math.max(dp[i - 1][3] + prices[i], dp[i - 1][4]);}return dp[prices.length - 1][4];}
}

[中等] 188. 买卖股票的最佳时机 IV

原题链接

在 123. 买卖股票的最佳时机 III 的基础上做扩充就可以了,观察买卖两次的代码发现无非就是对奇数和偶数做区分

class Solution {public int maxProfit(int k, int[] prices) {int[][] dp = new int[prices.length][2 * k + 1];// 0 未买入,1 第一次买入, 2 第一次卖出, 3 第二次买入,4 第二次卖出for(int i = 0; i <= 2 * k; i++){if(i % 2 != 0) dp[0][i] = -prices[0];}for(int i = 1; i < prices.length; i++){for(int j = 1; j <= 2 * k; j++) {if(j % 2 == 0){dp[i][j] = Math.max(dp[i - 1][j - 1] + prices[i], dp[i - 1][j]);}else{dp[i][j] = Math.max(dp[i - 1][j - 1] - prices[i], dp[i - 1][j]);}}}return dp[prices.length - 1][2 * k];}
}

[中等] 309. 买卖股票的最佳时机含冷冻期

原题链接

四种状态:0 未持有股票, 1 买入, 2 卖出, 3表冷冻期

注意0表示的是非冷冻期可操作情况下选择不操作
0:今天不持有股票,昨天也不持有,或者昨天是冷冻期
1:今天买入,昨天不会是卖出,但是需要比较一下是今天买入好还是之前的买入好
2:今天卖出,昨天不能是冷冻期,也不能是卖出
3:今天冷冻期,昨天就是卖出股票

最后得到的最大值,有可能最后一天是没做任何操作,或者最后一天是冷冻期,也有可能最后一天出售的股票,所以需要进行比较

class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[prices.length][4];//0 未持有股票, 1 买入, 2 卖出, 3表冷冻期dp[0][0] = 0;dp[0][1] = -prices[0];dp[0][2] = 0;dp[0][3] = 0;for(int i = 1; i < prices.length; i++){// 今天不持有股票, 昨天也不持有,或者昨天冷冻期(冷冻期另外算)dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][3]);// 今天买入,昨天不会是卖出int max = Math.max(dp[i - 1][3], dp[i - 1][0]) - prices[i];dp[i][1] = Math.max(dp[i - 1][1], max);// 今天卖出,昨天不能是冷冻期,也不能是卖出dp[i][2] = dp[i - 1][1] + prices[i];// 今天冷冻期,昨天卖出dp[i][3] = dp[i - 1][2];}int max = Math.max(dp[prices.length - 1][0], dp[prices.length - 1][2]);return Math.max(dp[prices.length - 1][3], max);}
}

[中等] 714. 买卖股票的最佳时机含手续费

原题链接

和 [中等] 122. 买卖股票的最佳时机 II没什么区别,卖出的时候把手续费算上即可

class Solution {public int maxProfit(int[] prices, int fee) {int[][] dp = new int[prices.length][2];//dp[0] 表示第i - 1不持有股票的最大现金,dp[1]表示第i- 1天持有股票的最大现金;dp[0][0] = 0;dp[0][1] = -prices[0];for(int i = 1; i < prices.length; i++){//当天不持有股票 = 昨天不持有股票 || 昨天持有股票 + 今天股票收益dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);//当天持有股票 = 昨天持有股票 || 昨天不持有股票 - 今天股票成本dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}return dp[prices.length - 1][0];}
}

[中等] 300. 最长递增子序列

原题链接

dp[i] 表示以 nums[i] 为结尾的最大递增子序列个数

class Solution {public int lengthOfLIS(int[] nums) {// 以 nums[i] 为结尾的最大递增子序列个数int[] dp = new int[nums.length];for(int i = 0; i < nums.length; i++)dp[i] = 1;for(int i = 1; i <nums.length; i++){for(int j = 0; j < i; j++){if(nums[i] > nums[j])dp[i] = Math.max(dp[i], dp[j] + 1);}}int max = Integer.MIN_VALUE;for(int i = 0; i < nums.length; i++){max = Math.max(dp[i], max);}return max;}
}

[简单] 674. 最长连续递增序列

原题链接

dp[i] 表示以 nums[i] 为结尾的最大连续递增子序列个数,在遍历nums时,就不需要开二重循环,因为递增序列要求连续,只需要跟前一个数字做比较即可

观察代码会发现,每一次dp[i] = Math.max(dp[i], dp[i - 1] + 1)只用到了前一位的dp状态,可以考虑将数组压缩

class Solution {public int findLengthOfLCIS(int[] nums) {// 以 nums[i] 为结尾的最大连续递增子序列个数int[] dp = new int[nums.length];for(int i = 0; i < nums.length; i++)dp[i] = 1;int max = dp[0];for(int i = 1; i <nums.length; i++){if(nums[i] > nums[i- 1])dp[i] = Math.max(dp[i], dp[i - 1] + 1);max = Math.max(dp[i], max);}return max;}
}

[中等] 718. 最长重复子数组

原题链接

dp[i][j] 表示:以 nums1[i] 为结尾,以nums2[j] 为结尾的最大重复子串长度 为 dp[i][j]

dp[i][j] = dp[i - 1][j - 1] + 1;相当于回退去找i 和 j 的前一位,看是否相等,并得到最大重复子串长度,如果nums1[i - 1] != nums2[j - 1]dp[i - 1][j - 1] == 0,dp[i][j]即为1

class Solution {public int findLength(int[] nums1, int[] nums2) {// 以 nums1[i] 为结尾,以nums2[j] 为结尾的最大重复子串长度 为 dp[i][j]int[][] dp = new int[nums1.length][nums2.length];int max = Integer.MIN_VALUE;for(int i = 0; i < nums1.length; i++){if(nums1[i] == nums2[0]) dp[i][0] = 1;if(max < dp[i][0]) max = dp[i][0];}for(int i = 0; i < nums2.length; i++){if(nums2[i] == nums1[0]) dp[0][i] = 1;if(max < dp[0][i]) max = dp[0][i];}for(int i = 1; i < nums1.length; i++){for(int j = 1; j < nums2.length; j++){if(nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j - 1] + 1;if(max < dp[i][j]) max = dp[i][j];}}return max;}
}

[中等] 1143. 最长公共子序列

原题链接
dp[i][j] 表示: char1[0, i]char2[0, j] 的最大重复子串长度

class Solution {public int longestCommonSubsequence(String text1, String text2) {char[] char1 = text1.toCharArray();char[] char2 = text2.toCharArray();// 以 char1[i] 为结尾,以char2[j] 为结尾的最大重复子串长度 为 dp[i][j]int[][] dp = new int[char1.length][char2.length];if(char1[0] == char2[0]) dp[0][0] = 1;int max = dp[0][0];for(int i = 1; i < char1.length; i++){if(char1[i] == char2[0]) dp[i][0] = 1;else dp[i][0] = dp[i - 1][0];if(max < dp[i][0]) max = dp[i][0];}for(int i = 1; i < char2.length; i++){if(char2[i] == char1[0]) dp[0][i] = 1;else dp[0][i] = dp[0][i - 1];if(max < dp[0][i]) max = dp[0][i];}for(int i = 1; i < char1.length; i++){for(int j = 1; j < char2.length; j++){if(char1[i] == char2[j]) {dp[i][j] = dp[i - 1][j - 1] + 1;}else{dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);}if(max < dp[i][j]) max = dp[i][j];}}return max;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/589373.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于搭建电商独立站跨境电商接入主流电商平台API商品接口对于商品功能模块的巨大应用

功能设计 首先我们来看下mall项目中商品功能的设计&#xff0c;主要包括商品管理、添加\编辑商品、商品分类、商品类型、品牌管理等功能&#xff0c;这里的功能同时涉及前台商城和后台管理系统。 商品管理【接入主流电商平台商品API接口丰富自建商城商品】 在mall项目的后台管…

什么牌子的骨传导耳机值得入手?盘点五大业界公认靠谱品牌推荐!

骨传导耳机作为新型蓝牙耳机款式&#xff0c;凭借不入耳佩戴更健康等特点&#xff0c;赢得了消费者的喜欢&#xff0c;逐渐成为热门款式&#xff0c;但随着热度增加&#xff0c;骨传导耳机行业开始出现各种品牌&#xff0c;它们没有自己的核心技术&#xff0c;大部分都是来个韭…

算法沉淀——动态规划篇(子数组系列问题(上))

算法沉淀——动态规划篇&#xff08;子数组系列问题&#xff08;上&#xff09;&#xff09; 前言一、最大子数组和二、环形子数组的最大和三、乘积最大子数组四、乘积为正数的最长子数组长度 前言 几乎所有的动态规划问题大致可分为以下5个步骤&#xff0c;后续所有问题分析都…

书生·浦语大模型实战营(第二期):书生·浦语大模型趣味Demo

目录 部署InternLM2-Chat-1.8B模型进行对话环境配置下载InternLM2-Chat-1.8B模型运行cli_demo基础作业&#xff1a;使用 InternLM2-Chat-1.8B 模型生成 300 字的小故事&#xff08;需截图&#xff09; 部署实战营优秀作品 八戒-Chat-1.8B 模型下载运行Chat-八戒 Demo 使用 Lage…

【热门话题】文言一心与ChatGPT-4:一场跨时代智能对话系统的深度比较

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 文言一心与ChatGPT-4&#xff1a;一场跨时代智能对话系统的深度比较一、技术背景…

Shell脚本介绍及基本功能

目录 一、什么是Shell 二、什么是Shell脚本 三、echo 四、Hello World 五、Bash的基本功能 1.别名 2.常用快捷键 3.输入输出 4.输出重定向 5.多命令执行 6.管道符 7.通配符和特殊符合 一、什么是Shell Shell是一种命令行解释器&#xff0c;它是操作系统的一部分&a…

【数据处理包Pandas】分组及相关操作

目录 一、初步认识分组并查看分组信息&#xff08;一&#xff09;通过聚合函数查看分组信息&#xff08;二&#xff09;转换成列表查看所有组的信息&#xff08;三&#xff09;通过循环查看各组的名称和组中的数据信息&#xff08;四&#xff09;通过get_group()方法直接获得一…

​理想汽车:智能化之路,道阻且长后发先至

‍‍‍ 写下这个标题时&#xff0c;我心情非常复杂&#xff0c;理想汽车在 2023 年前&#xff0c;给大众的印象几乎是冰箱彩电大沙发&#xff0c;智能化宣传着墨极少。 在小鹏公开宣布将自动驾驶作为自己的核心能力&#xff0c;蔚来也大举投入智能化研发时&#xff0c;理想汽…

阿里云数据库服务器价格表,2024年最新报价

阿里云数据库服务器价格表&#xff0c;优惠99元一年起&#xff0c;ECS云服务器2核2G、3M固定带宽、40G ESSD Entry云盘&#xff0c;优惠价格99元一年&#xff1b;阿里云数据库MySQL版2核2G基础系列经济版99元1年、2核4GB 227.99元1年&#xff0c;云数据库PostgreSQL、SQL Serve…

Cesium 仿高德导航线

最近在开发无人机平台中&#xff0c;做一个自主航线规划时&#xff0c;为了达到一个好的航线展示效果&#xff0c;决定仿照高德地图中的线路导航来做。高德地图导航线效果如下&#xff1a; 开始的时候想通过传统的贴图方式来进行处理&#xff0c;发现使用此种方式时&#xff0c…

深入C语言:探究static关键字的奥秘

文章目录 一、链接属性二、static变量1、定义静态局部变量2、在函数内部使用静态变量3、函数中静态局部变量与递归 三、static变量与全局变量的区别1、存储期与生命周期2、可见性与作用域3、使用场景4、静态与动态内存分配 注意事项 当用于不同的上下文环境时&#xff0c; sta…

手搓 Docker Image Creator(DIC)工具(02):预备知识

此节主要简单介绍一下 Docker、Dockerfile 的基本概念&#xff0c;Dockerfile 对的基本语法&#xff0c;Windows 和 macOS 下 Docker 桌面的安装&#xff0c;Docker 镜像的创建和运行测试等。 1 关于 Docker Docker 是一个开源的应用容器引擎&#xff0c;它允许开发者打包应用…