刷题记录|目标101题--动态规划
写在前面
开个贴记录一下 刷题的过程,目前参照 leetcode101,目标101题,由于太久没写java了,也会涉及到一些java的基础用法,希望能和大家共勉吧~
已刷链接:
- 贪心算法:https://www.nowcoder.com/discuss/1051877
- 双指针:https://www.nowcoder.com/discuss/1057237
- 二分查找:https://www.nowcoder.com/discuss/1059986
- 排序:链接
- 搜索:链接
动态规划
一维动态规划
No.1 Climbing Stairs
链接
解题思路:
dp[i] = dp[i-1] + dp[i-2]
class Solution { public int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; int[] dp = new int[n]; dp[0] = 1; dp[1] = 2; for (int i = 2; i < n; i ++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n - 1]; } }
No.2 House Robber
链接
class Solution { public int rob(int[] nums) { if (nums.length == 1) return nums[0]; if (nums.length == 2) return Math.max(nums[0], nums[1]); int[] dp = new int[nums.length]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); 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]; } }
No.3 Arithmetic Slices
题目:链接
解题思路:
本题需要注意两点:
- 成为一个Arithmetic Slices 的条件可以抽象为nums[i] - nums[i-1] == nums[i-1] - nums[i-2]
- 每个位置一共有多少种可能性很难用dp计算出,但是我们可以dp出以nums[i]结尾的Arithmetic Slices数量为 dp[i] = dp[i-] + 1,因为当nums[i] - nums[i-1] == nums[i-1] - nums[i-2]时,每个以i-1结尾的Arithmetic Slices都可以加上 nums[i]构成Arithmetic Slices,同时还有一个nums[i-2],num[i-1],nums[i]的组合,所以一共会比以i-1结尾的dp多一,最后求和即可。
class Solution { public int numberOfArithmeticSlices(int[] nums) { int n = nums.length; if (n < 3) return 0; int[] dp = new int[n]; int result = 0; for (int i = 2; i < n; i++) { if (nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]) { dp[i] = dp[i - 1] + 1; result += dp[i]; } } return result; } }
No.4 House Robber II
题目:链接
解题思路:
这里的问题是nums首尾相连了,所以为了防止成环,我们选用从第1个开始开始偷到n-1个和从第2个开始偷到第n个,分别取最大值,最后两者比较
class Solution { public int rob(int[] nums) { int n = nums.length; if (n == 1) return nums[0]; if (n == 2) return Math.max(nums[0],nums[1]); return Math.max(dpSolution(nums,0,n - 2,n),dpSolution(nums,1,n - 1,n)); } private int dpSolution(int[] nums, int l ,int r, int n) { int[] dp = new int[n]; dp[l] = nums[l]; dp[l + 1] = Math.max(nums[l], nums[l + 1]); for (int i = l + 2; i <= r; i++) { dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); } return dp[r]; } }
No.5 Maximum Subarray
题目:链接
解题思路:
class Solution { public int maxSubArray(int[] nums) { int n = nums.length; int dp = nums[0]; int sum = dp; for (int i = 1; i < n; i++) { dp = Math.max(dp + nums[i],nums[i]); sum = Math.max(sum, dp); } return sum; } }
二维动态规划
No.1 Minimum Path Sum
题目:链接
解题思路:
位置i,j的值等于其上和左的两个dp取较小值加上他自己的取值,既dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
class Solution { public int minPathSum(int[][] grid) { int m = grid.length, n = grid[0].length; int[][] dp = new int[m][n]; dp[0][0] = grid[0][0]; for (int i = 1; i < m; i++) { dp[i][0] = grid[i][0] + dp[i - 1][0]; } for (int j = 1; j < n; j++) { dp[0][j] = grid[0][j] + dp[0][j - 1]; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = Math.min(dp[i - 1][j],dp[i][j -1]) + grid[i][j]; } } return dp[m - 1][n - 1]; } }
No.2 01 Matrix
题目:链接
解题思路:
本题的dp[i][j]其实等于上下左右四个方位的最小值+1,但是由于我们顺序遍历的时候无法获取下和右的值,所有我们先从左到右从上到下遍历一遍赋值,再从右到左从下到上反着走一遍取最小值
class Solution { public int[][] updateMatrix(int[][] mat) { int m = mat.length, n = mat[0].length; int[][] dp = new int[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (mat[i][j] == 0) { dp[i][j] = 0; } else { if (i == 0 && j == 0) { dp[0][0] = 100000; } else if (j == 0) { dp[i][j] = dp[i - 1][j] + 1; } else if (i == 0) { dp[i][j] = dp[i][j - 1] + 1; } else { dp[i][j] = Math.min(dp[i][j - 1],dp[i - 1][j]) + 1; } } } } for (int i = m -1; i >= 0; i--) { for (int j = n -1; j >= 0; j--) { if (mat[i][j] == 1) { if (i == m -1 && j == n -1) { continue; } else if (i == m - 1) { dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1); } else if (j == n - 1) { dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1); } else { dp[i][j] = Math.min(dp[i][j],Math.min(dp[i][j + 1],dp[i + 1][j]) + 1); } } } } return dp; } }
No.3 Maximal Square
题目:链接
解题思路:
这样的求正方形的题需要一个二维dp数组,但是怎么求dp[i][j]的值呢?
如果nums[i][j] == 0,则 dp[i][j] = 0,
如果nums[i][j]==1
- 我们假设dp[i][j] = k2,则他的上元素,左元素和上左元素都需要不小于 (k - 1)2,
- nums[i-1][j] >= (k - 1)2 && nums[i][j-1] >= (k - 1)2 && nums[i-1][j-1] >= (k - 1)2,即下图所示
class Solution { public int maximalSquare(char[][] matrix) { int m = matrix.length, n = matrix[0].length; int[][] dp = new int[m][n]; int result = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j ++) { if (matrix[i][j] == '0') { dp[i][j] = 0; } else { if (i == 0 || j ==0) { dp[i][j] = 1; } else { int min = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; dp [i][j] = min; } } if (dp[i][j] > result) { result = dp[i][j]; } } } return result * result; } }
分割类题型
No.1 Perfect Squares
题目:链接
解题思路:
dp[i] = Math.min(dp[i-1*1],dp[i-2*2],dp[i-3*3]...) + 1,这里注意dp常需要增加dp[0]的值便于回归
class Solution { public int numSquares(int n) { int[] dp = new int[n + 1]; Arrays.fill(dp,Integer.MAX_VALUE); dp[0] = 0; for (int i = 1;i <= n; i++) { for (int j = 1; j * j <= i; j++) { dp[i] = Math.min(dp[i - j * j],dp[i]); } dp[i]++; } return dp[n]; } }
No.2 Decode Ways
题目:链接
解题思路:
本题要将情况细分:
- 如果是第一位是0,直接return 0
- 如果第一位不是0,dp[1]赋值为1
- 后续遇到0
- 如果和前面一位形成20,10,则dp[i] = dp[i-2];
- 如果不是10,20,则可以直接return 0,这样的字符串找不到
- 如果后续不是0
- 如果和前面一位形成11到26之内,则这位即可以自己成为一个字母,也可以和前一位结合形成字母,即dp[i] = dp[i - 1] + dp[i-2]
- 反之则要单独为一个字母,即dp[i] = d[i-1]
class Solution { public int numDecodings(String s) { int n = s.length(); int[] dp = new int[n + 1]; dp[0] = 1; if (s.charAt(0) == '0') return 0; dp[1] = 1; for (int i = 1; i < n; i++) { char c = s.charAt(i); char pre = s.charAt(i - 1); if (c == '0' && pre >= '1' && pre <= '2') { dp[i + 1] = dp[i - 1]; dp[i] = dp[i - 1]; } else if (c == '0') { return 0; } else if (pre == '1' || (pre == '2' && c >= '1' && c <= '6')) { dp[i + 1] = dp[i] + dp[i - 1]; } else { dp[i + 1] = dp[i]; } } return dp[n]; } }
No.3 Word Break
题目:链接
解题思路:
此题与分割完美平方数字类似,都是通过分割原来的input来做,不同的是完美平方数字是通过减去完美平方数字来获得上一个dp,而本题是获取到以当前i位置结尾的字符串,然后将其剪去获取上一dp,本质都是类似的。注意这里为了防止dict里的字符串互相包含的情况,需要每次都遍历dict中所有的字符串,根据其长度取上一个dp。
class Solution { public boolean wordBreak(String s, List<String> wordDict) { int n = s.length(); boolean[] dp = new boolean[n + 1]; dp[0] = true; for (int i = 1; i <= n; i++) { for (String word : wordDict) { int len = word.length(); if (i >= len && s.substring(i - len,i).equals(word)) { dp[i] = dp[i-len]; if (dp[i] == true) break; } } } return dp[n]; } }
No.4 Integer Break
题目:链接
思路解析:
第i个位置可以将其分解为两部分,其中每个部分再各自分为两部分,求dp。需要注意的是有可能i会>dp[i],此时需要求max值,如果i>dp[i]就不分解了
class Solution { public int integerBreak(int n) { //dp[i] means output when input = i, e.g. dp[4] = 4 (2*2),dp[8] = 18 (2*2*3)... int[] dp = new int[n + 1]; dp[1] = 1; // fill the entire dp array for (int i = 2; i <= n; i++) { //let's say i = 8, we are trying to fill dp[8]:if 8 can only be broken into 2 parts, the answer could be among 1 * 7, 2 * 6, 3 * 5, 4 * 4... but these numbers can be further broken. so we have to compare 1 with dp[1], 7 with dp[7], 2 with dp[2], 6 with dp[6]...etc for (int j = 1; j <= i / 2; j++) { // use Math.max(dp[i],....) so dp[i] maintain the greatest value dp[i] = Math.max(dp[i],Math.max(j, dp[j]) * Math.max(i - j, dp[i - j])); } } return dp[n]; } }
子序列问题
No.1 Longest Increasing Subsequence
题目:链接
解题思路:
这题还是要采用以i为结尾的数字的递增序列,不过要记录j再从后往前走,时间复杂度会是n2,有些大。这只是基本思路
class Solution { public int lengthOfLIS(int[] nums) { int n = nums.length; int[] dp = new int[n]; Arrays.fill(dp,1); int result = 1; for (int i = 1; i < n; i++) { for (int j = i - 1; j >= 0; j--) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[j] + 1,dp[i]); } } result = Math.max(result,dp[i]); } return result; } }
进阶处理可以利用二分查找优化时间复杂度为n*logn,方法如下:
我们开创一个数组dp,将其视为一个stack,其每个位置dp[i]表示的是长度为i+1的子序列的最小结尾值,每次取nums[i]的时候更新dp数组,如果nums[i]更大,则直接入栈,如果nums[i]小,则向前二分查找到对应的dp赋值
class Solution { public int lengthOfLIS(int[] nums) { int n = nums.length; int[] dp = new int[n]; int j = 0; dp[0] = nums[0]; for (int i = 1; i < n; i++) { if (nums[i] > dp[j]) { dp[++j] = nums[i]; } else if (nums[i] < dp[j]) { int l = 0, r = j; while (l <= r) { int m = l + (r - l) / 2; if (dp[m] == nums[i]) { break; } if (dp[m] < nums[i]) { l = m + 1; } else { r = m - 1; } } if (l > r) { dp[l] = nums[i]; } } } return j + 1; } }
No.2 Longest Common Subsequence
题目:链接
解题思路:
本题创建一个二维数组dp,其中i表示第一个字符串到i位置为止,j表示第二个字符串到j位置为止的最长的公共子序列长度。
即如果在位置ij,两个字符串对应的char相等,则dp[i][j]=dp[i-1][j-1] + 1,否则将等于dp[i-1[[j]和dp[i][j-1]的max
class Solution { public int longestCommonSubsequence(String text1, String text2) { int n = text1.length(); int m = text2.length(); int[][] dp = new int[n + 1][m + 1]; int x = 0, y = 0; for (int i = 1; i <= n; i++) { for (int j = 1;j <= m; j++) { char a = text1.charAt(i - 1); char b = text2.charAt(j - 1); if (a == b) { 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[n][m]; } }
No.3 Delete Operation for Two Strings
题目链接:链接
解题思路:
和求最大common subseque一样,只要求出common subseque然后进行减法就可以
class Solution { public int minDistance(String word1, String word2) { int m = word1.length(), n = word2.length(); int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { char a = word1.charAt(i - 1); char b = word2.charAt(j - 1); if (a == b) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return m + n - dp[m][n] * 2; } }
No.4 Maximum Length of Pair Chain
题目:链接
解题思路:
设置一个dp数组,dp[i]表示第i个位置能链接的最长值,每次从已经链接的头开始找起来,如果可以链接则dp[j]+1,
dp[i] = Math.max(dp[i],dp[j]+1),可用二分加速
class Solution { public int findLongestChain(int[][] pairs) { int n = pairs.length; int[] dp = new int[n]; Arrays.sort(pairs,(a,b)->{return a[0] - b[0];}); Arrays.fill(dp,1); for (int i = 1; i < n; i++) { for(int j = 0; j < i; j++) { if (pairs[j][1] < pairs[i][0]) { dp[i] = Math.max(dp[i],dp[j] + 1); } } } return dp[n - 1]; } }
No.5 Wiggle Subsequence
题目:链接
解题思路:
考虑每种边界,如果是单调关系则覆盖,wiggle的时候就放在下一位
class Solution { public int wiggleMaxLength(int[] nums) { int n = nums.length; if (n == 1) return 1; int[] stack = new int[n]; int j = 0; stack[0] = nums[0]; int isPositive = 0; for (int i = 1; i < n; i++) { if (isPositive == 0) { if (nums[i] > stack[j]) { isPositive = 1; stack[++j] = nums[i]; } else if (nums[i] < stack[j]) { isPositive = 2; stack[++j] = nums[i]; } continue; } if (isPositive == 1) { if (nums[i] > stack[j]) { stack[j] = nums[i]; } else if (nums[i] < stack[j]) { stack[++j] = nums[i]; isPositive = 2; } continue; } if (isPositive == 2) { if (nums[i] < stack[j]) { stack[j] = nums[i]; } else if (nums[i] > stack[j]) { stack[++j] = nums[i]; isPositive = 1; } continue; } } return j + 1; } }
背包问题
背包分为0-1背包问题和完全背包问题
有 N 个物品和容量为 W 的背包,每个物品都有自己的价值 v 和体积 w,求拿那些物品可以使背包放下的价值最大
在0-1背包问题中公式为 dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w]+v)
,
因为i行的值只依赖于i-1行的值,所以可以去除i维度的dp数组,将其优化为一维数组,则公式为dp[j] = Math.max(dp[j],dp[j-w]+v)
注意这时因为dp[j]受到dp[j-w]的影响,所以要从后往前计算,以免dp[j-w]更新导致dp[j]受影响
在完全背包问题中公式为dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-w*1]+v*1,dp[i-1][j-w*2]+v*2,...)
,此时时间复杂度将很大,所以将其进行简化
可以想到dp[i][j] 在迭代时dp[i-1][j] 和dp[i][j-w]都已经得出结果,所以dp[i][j] = Math.max(dp[i-1][j],dp[i][j-w] +v)
同样的我们可以对dp[i][j]进行空间压缩,压缩后的公式为dp[j] = Math.max(dp[j],dp[j-w]+v)
,注意此时因为j-w比j早一行,所以要正向遍历,确保j-w早于j位置更新
No.1 416. Partition Equal Subset Sum
题目:链接
解题思路:
class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 != 0) return false; int target = sum / 2; int n = nums.length; boolean[] dp = new boolean[target + 1]; dp[0] = true; for (int i = 1; i < n; i++) { for (int j = target; j >= nums[i - 1]; j--) { dp[j] = dp[j - nums[i - 1]] || dp[j]; } } return dp[target]; } }
No.2 Ones and Zeroes
题目链接:链接
解题思路:
本题是一个三维数组,原三维代码如下:
class Solution { public int findMaxForm(String[] strs, int m, int n) { int len = strs.length; int[][][] dp = new int[len + 1][m + 1][n + 1]; dp[0][0][0] = 0; for (int i = 1; i <= len; i++) { int zeroCount = 0; int oneCount = 0; for (int j = 0; j < strs[i - 1].length(); j ++) { char c = strs[i - 1].charAt(j); if (c == '0') { zeroCount++; } else { oneCount++; } } for (int j = 0; j <= m; j++) { for (int k = 0; k <= n; k ++) { if (j < zeroCount || k < oneCount) { dp[i][j][k] = dp[i - 1][j][k]; } else { dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zeroCount][k - oneCount] + 1); } } } } return dp[len][m][n]; } }
将i维度去掉后,需要从后往前迭代,代码如下
class Solution { public int findMaxForm(String[] strs, int m, int n) { int len = strs.length; int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= len; i++) { int zeroCount = 0; int oneCount = 0; for (int j = 0; j < strs[i - 1].length(); j ++) { char c = strs[i - 1].charAt(j); if (c == '0') { zeroCount++; } else { oneCount++; } } for (int j = m; j >= zeroCount; j--) { for (int k = n; k >= oneCount; k --) { dp[j][k] = Math.max(dp[j][k], dp[j - zeroCount][k - oneCount] + 1); } } } return dp[m][n]; } }
No.3 Coin Change
题目链接:链接
解题思路:
完全背包问题,二维数据如下:其中之所以初始化dp的所有值为 amount+1 是因为我们在取min,如果还是默认值就一直为0,设置为amount+1,最后如果遍历完还是amount+1则说明没找到返回-1。注意amount为0的时候要额外设置为0
class Solution { public int coinChange(int[] coins, int amount) { if (amount == 0) return 0; int n = coins.length; int[][] dp = new int[n + 1][amount + 1]; for (int i = 0; i <= n; i++) { Arrays.fill(dp[i], amount + 1); dp[i][0] = 0; } for (int i = 1; i <= n; i ++) { for (int j = 1; j <= amount; j ++) { if (j < coins[i - 1]) { dp[i][j] = dp[i - 1][j]; } else { dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - coins[i - 1]] + 1); } } } return dp[n][amount] == amount + 1 ? -1 : dp[n][amount]; } }
简化为一维数组,可以理解为我们对amount进行遍历,取min(dp[i],dp[i-coins[0]],dp[i-coins[1]....)
class Solution { public int coinChange(int[] coins, int amount) { if (amount == 0) return 0; int n = coins.length; int[] dp = new int[amount + 1]; Arrays.fill(dp,amount + 1); dp[0] = 0; for (int i = 1; i <= amount; i ++) { for (int coin : coins) { if (i >= coin) { dp[i] = Math.min(dp[i],dp[i - coin] + 1); } } } return dp[amount] == amount + 1 ? -1 : dp[amount]; } }
No.4 Target Sum
题目:https://leetcode.com/problems/target-sum/
解题思路:
这题可以化为一个0-1背包问题,target即为我们的目标值,每次选择是+还是-,用dp[i][j]表示在第i个位置,j表示当前计算出的值,dp[i][j]代表当前有多少种可能性。
可以得出公示为dp[i][j] = dp[i -1][j - nums[i]] + d[i-1][j+nums[i]]。
i的取值范围为0-len,j为从-sum到sum
class Solution { public int findTargetSumWays(int[] nums, int target) { int sum = 0; for (int n : nums) { sum += n; } if (Math.abs(target) > sum) return 0; int len = nums.length; int[][] dp = new int[len][2*sum + 1]; for (int i = 0; i < len; i++) { for (int j = 0; j < 2*sum + 1; j++) { if (i == 0) { dp[i][j] = nums[i] == Math.abs(j - sum) ? (nums[i] == 0 ? 2 : 1 ) : 0; } else { if (j - nums[i] >= 0){ dp[i][j] += dp[i - 1][j - nums[i]]; } if (j + nums[i] < 2 * sum + 1) { dp[i][j] += dp[i - 1][j + nums[i]]; } } } } return dp[len - 1][target + sum]; } }
字符串编辑
No.1 Edit Distance
题目:链接
解题思路:
dp[i][j]为string1在i位置和string2在j位置情况下的最小操作数。
- 当 i == j时,dp[i][j] = dp[i-1][j-1]
- i!=j
- 修改:dp[i][j] = dp[i-1][j-1]+1
- 插入i/删除j:dp[i][j] = dp[i][j-1] + 1
- 插入j/删除i:dp[i][j] = dp[i-1][j] + 1
注意在i = 0时,所有的dp[i][j]都为j,j=0时同理
class Solution { public int minDistance(String word1, String word2) { int n = word1.length(), m = word2.length(); if (n == 0) return m; if (m == 0) return n; int[][] dp = new int[n + 1][m + 1]; for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j ++) { if (i == 0) { dp[i][j] = j; } else if (j == 0) { dp[i][j] = i; } else { char a = word1.charAt(i - 1); char b = word2.charAt(j - 1); if (a == b) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)); } } } } return dp[n][m]; } }
No.2 2 Keys Keyboard
题目:链接
解题思路:
将第i位数字拆解为n*j,
class Solution { public int minSteps(int n) { if (n == 1) return 0; int[] dp = new int[n + 1]; Arrays.fill(dp,n + 1); dp[0] = 0; dp[1] = 0; for (int i = 2; i <= n; i++) { for (int j = 1; j < i; j++) { if (i % j == 0) { dp[i] = Math.min(dp[i], dp[j] + i/j); } } } return dp[n]; } }
No.3 Regular Expression Matching
题目:链接
解题思路:
本题主要是在*号时比较难匹配,a*会匹配到"","a"和"aaaaaaa*n"几种情况,分在字符串中几种又会有下面几种情况:
- s为空的时候,此时只有*代表""才能匹配
- s不为空的时候,如果遇到*
- p[j - 1] == '.' || p[j -1] == s[i]
- 则说明此时 * 表示的是空,一个和n个都有可能,那么
- 对应*表示为空:dp[i][j] = dp[i][j - 2]
- 对应*表示一个:dp[i][j] = dp[i][j - 1]
- 对应*表示多个:dp[i][j] = dp[i -1][j]
- p[j-1] != s[i]
- 说明此时*表示的一定为空
- 对应*表示为空:dp[i][j] = dp[i][j - 2]
代码如下:
class Solution { public boolean isMatch(String s, String p) { int m = s.length(); int n = p.length(); boolean[][] dp = new boolean[m + 1][n + 1]; dp[0][0] = true; for (int i = 2; i <= n; i++) { if (p.charAt(i - 1) == '*') { dp[0][i] = dp[0][i - 2]; } } for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { char pp = p.charAt(j - 1); char ss = s.charAt(i - 1); if (pp == '.' || pp == ss) { dp[i][j] = dp[i - 1][j - 1]; } else if (pp == '*') { char ppp = p.charAt(j - 2); if (ppp == '.' || ppp == ss) { dp[i][j] = dp[i - 1][j] || dp[i][j - 1] || dp[i][j - 2]; } else { dp[i][j] = dp[i][j - 2]; } } } } return dp[m][n]; } }
股票交易问题
冷却时间和交易费用等可以用dp的状态机实现
No.1 Best Time to Buy and Sell Stock
解题思路:
class Solution { public int maxProfit(int[] prices) { int n = prices.length; if (n < 2) return 0; int[] dp = new int[n]; int result = 0; for (int i = 1; i < n; i++) { dp[i] = prices[i] - prices[i - 1] + dp[i - 1] > 0 ? prices[i] - prices[i - 1] + dp[i - 1] : 0; result = Math.max(dp[i],result); } return result; } }
No.2 Best Time to Buy and Sell Stock IV
解题思路:
- 如果k * 2>= n,则随意买卖
- 反之, 建立俩动态规划数组,buy和sell,遍历每一天,探寻在第i天买卖第j次的收益,取max
class Solution { public int maxProfit(int k, int[] prices) { int n = prices.length; int[] buy = new int[k + 1]; int[] sell = new int[k + 1]; Arrays.fill(buy, Integer.MIN_VALUE); Arrays.fill(sell, 0); if (k * 2 >= n) { int result = 0; for (int i = 1; i < n; i++) { if (prices[i] > prices[i - 1]) { result += prices[i] - prices[i - 1]; } } return result; } for (int i = 0; i < n; i ++) { for (int j = 1; j <= k; j++) { buy[j] = Math.max(buy[j],sell[j - 1] - prices[i]); sell[j] = Math.max(sell[j], buy[j] + prices[i]); } } return sell[k]; } }
No.3 Best Time to Buy and Sell Stock with Cooldown
题目链接:链接
解题思路:
这里制造三个状态节点:buy,sell,cooldown,遍历每天的股价,通过建立节点之间的方程来获得结果。
buy[i] = Math.max(buy[i-1],cool[i -1] - price[i]) // 本日不买和cooldown以后买入会让利润更高
sell[i] = buy[i - 1] + prices[i] //只有从昨日售出一条路
cool[i] = Math.max(cool[i - 1], sell[i - 1] //是维持昨日的cool不售出还是售出以后利润更高
class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[] buy = new int[n], sell = new int[n], cool = new int[n]; buy[0] = -prices[0]; sell[0] = cool[0] = 0; for (int i = 1; i < n; i ++) { buy[i] = Math.max(cool[i - 1] - prices[i], buy[i - 1]); sell[i] = buy[i - 1] + prices[i]; cool[i] = Math.max(cool[i - 1], sell[i - 1]); } return Math.max(cool[n - 1], sell[n - 1]); } }
No.4 Best Time to Buy and Sell Stock with Transaction Fee
题目:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
解题思路:、
创建状态机,分别有sell节点和buy节点
buy[i] = Math.max(buy[i -1], sell[i - 1] - prices[i]); // 当天不买入和当天买入收益
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i] - fee); // 当天sell和当天不sell比收益
class Solution { public int maxProfit(int[] prices, int fee) { int n = prices.length; int[] buy = new int[n], sell = new int[n]; buy[0] = -prices[0]; sell[0] = 0; for (int i = 1; i < n; i++) { buy[i] = Math.max(buy[i - 1], sell[i - 1] - prices[i]); sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i] - fee); } return sell[n - 1]; } }#刷题#