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.mzph.cn/news/785576.shtml

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

相关文章

【游戏分析】FPS游戏狩猎百发百中

某某游戏狩猎玩法及其类似于FPS游戏 即3D射击 所以同样拥有 自动瞄准功能和爆头功能 想达到百发百中我们就要精准的计算出3D朝向值 读取人物坐标 遍历怪物,读取怪物坐标比较简单,不过多陈诉 朝向自然而然一定是我们和敌人的坐标计算出来的 那么怎么计算的呢&#xff1f; 我…

JAVA学习笔记21(访问修饰符)

1.访问修饰符 ​ *基本介绍 ​ java提供四种访问控制修饰符号&#xff0c;用于控制方法和属性(成员变量)的访问权限(范围) 1.公开级别&#xff1a;用public修饰&#xff0c;对外公开 2.受保护级别&#xff1a;用protected修饰&#xff0c;对子类和同一个包中的类公开 3.默…

基础知识系列

文章目录 一、linux passwd文件 一、linux passwd文件 linux中&#xff0c;用户名存放在/etc/passwd中&#xff0c;密码存放在/etc/shadow中。/etc/passwd只允许root用户可读可写&#xff0c;root组的用户可读&#xff0c;其他用户不可读不可写不可执行。/etc/shadow只允许roo…

手写简易操作系统(二十)--实现堆内存管理

前情提要 前面我们实现了 0x80 中断&#xff0c;并实现了两个中断调用&#xff0c;getpid 和 write&#xff0c;其中 write 还由于没有实现文件系统&#xff0c;是个残血版&#xff0c;这一节我们实现堆内存管理。 一、arena 在计算机科学中&#xff0c;“arena” 内存管理通…

DC-9靶场

一.环境搭建 1.下载地址 靶机下载地址&#xff1a;https://download.vulnhub.com/dc/DC-9.zip 2.虚拟机配置 设置虚拟机为nat&#xff0c;遇到错误点重试和是 开启虚拟机如下图所示 二.开始渗透 1. 信息收集 查找靶机的ip地址 arp-scan -l 发现靶机的ip地址为192.168.11…

如何使用单片机 pwm 控制 mos 管?

目录 选择适合的硬件 连接电路 编写代码 参考示例 程序一 程序二 测试与调试 注意事项 使用单片机&#xff08;如常见的Arduino、STM32等&#xff09;通过PWM&#xff08;脉冲宽度调制&#xff09;控制MOS管&#xff08;金属氧化物半导体场效应管&#xff09;是一种常见…

Linux使用Docker部署RStudio Server结合内网穿透实现公网访问本地服务

文章目录 前言1. 安装RStudio Server2. 本地访问3. Linux 安装cpolar4. 配置RStudio server公网访问地址5. 公网远程访问RStudio6. 固定RStudio公网地址 前言 RStudio Server 使你能够在 Linux 服务器上运行你所熟悉和喜爱的 RStudio IDE&#xff0c;并通过 Web 浏览器进行访问…

标准版IP地址证书

IP地址证书是一种网络安全工具&#xff0c;用于确保互联网通信中IP地址的所有权和真实性。它类似于为网站颁发的SSL/TLS证书&#xff0c;但专门针对IP地址。这种证书由受信任的第三方机构&#xff08;如证书颁发机构&#xff09;签发&#xff0c;包含公钥、所有者信息和有效期。…

【Linux】开始掌握进程控制吧!

送给大家一句话&#xff1a; 我并不期待人生可以一直过得很顺利&#xff0c;但我希望碰到人生难关的时候&#xff0c;自己可以是它的对手。—— 加缪 开始学习进程控制 1 前言2 进程创建2.1 fork函数初识2.2 fork函数返回值2.3 写时拷贝2.4 fork常规用法2.5 fork调用失败的原因…

Free RTOS day2

1.思维导图 2.使用PWMADC光敏电阻完成光控灯的实验 int adc_val0;//用于保存ADC采样得到的数值 float volt0;//用于保存电压值 int main(void) {MX_GPIO_Init();MX_DMA_Init();MX_TIM1_Init();MX_USART1_UART_Init();MX_ADC_Init();MX_TIM3_Init();HAL_TIM_PWM_Start(&hti…

【linux】基础IO(一)

文件只有站在系统层面才能彻底理解 简单回顾一下文件&#xff1a; 首先我们要明确一点&#xff0c;我们说的打开文件不是写下fopen就打开文件&#xff0c;而是当我们的进程运行起来&#xff0c;进程打开的文件。 我们在C语言一般都会使用过如下的代码进行向文件中写入 但是除…

GUID测试程序

全局唯一标识符&#xff08;GUID&#xff0c;Globally Unique Identifier&#xff09;是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下&#xff0c;任何计算机和计算机集群都不会生成两个相同的GUID。G…

Clickhouse-表引擎探索之MergeTree

引言 前文曾说过&#xff0c;Clickhouse是一个强大的数据库Clickhouse-一个潜力无限的大数据分析数据库系统 其中一个强大的点就在于支持各类表引擎以用于不同的业务场景。 MergeTree MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一…

手写SpringBoot(二)之动态切换Servlet容器

系列文章目录 手写SpringBoot&#xff08;一&#xff09;之简易版SpringBoot 手写SpringBoot&#xff08;二&#xff09;之动态切换Servlet容器 手写SpringBoot&#xff08;三&#xff09;之自动配置 手写SpringBoot&#xff08;四&#xff09;之bean动态加载 手写SpringBoot&…

衍生品交易概况

场内 场外 交易台架构 报价、交易、研究、程序个股、股指Flow、Exotic线性、非线性 对冲管理 管理风险敞口 做好情景分析 尊重市场选择 及时调整策略 理解头寸 善于学习 场外衍生品交易员的一天 盘前 回顾市场、决定今天总体方向处理隔夜敞口 盘中 处理客户询价…

C语言中入门到实战————动态内存管理

目录 前言 一、为什么要有动态内存分配 二、 malloc和free 2.1 malloc 2.2 free 三、calloc和realloc 3.1 calloc 3.2 realloc 四. 常见的动态内存的错误 4.1 对NULL指针的解引用操作 4.2 对动态开辟空间的越界访问 4.3 对非动态开辟内存使用free释放 4.4 使…

【算法】01背包问题(代码+详解+练习题)

题目&#xff1a; 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一行两个整…

视频素材库有哪些网站?八大平台视频素材库创作推荐

视频创作的小达人们&#xff0c;是不是经常在想&#xff0c;视频素材库有哪些网站能提供高质量的素材呢&#xff1f;别担心&#xff0c;今天我要为你们揭秘八个超棒的视频素材网站&#xff0c;让你的视频制作更加轻松在创作的路上如鱼得水&#xff01; 蛙学网&#xff1a;海量…

深入探索Yarn:安装与使用指南

Yarn 是一个由 Facebook 开发的 JavaScript 包管理器&#xff0c;旨在提供更快、更可靠的包管理体验。它与 npm 类似&#xff0c;但在某些方面更加高效和可靠。本文将介绍如何安装 Yarn&#xff0c;并展示如何使用它来管理 JavaScript 项目的依赖。 1. 安装 Yarn Yarn 可以通…

三步提升IEDA下载速度——修改IDEA中镜像地址

找到IDEA的本地安装地址 D:\tool\IntelliJ IDEA 2022.2.4\plugins\maven\lib\maven3\conf 搜索阿里云maven仓库 复制https://developer.aliyun.com/mvn/guide中红框部分代码 这里也是一样的&#xff1a; <mirror><id>aliyunmaven</id><mirrorOf>*&…