class Solution {
public int fib(int n) {
int[] f = new int[n+1];
if(n <= 1) return n;
f[0] = 0;f[1] = 1;
for(int i = 2;i <= n;i++){
f[i] = f[i-1] + f[i-2];
}
return f[n];
}
}
进
行
一
个
分
割
我们来分析一下,动规五部曲:
定义一个一维数组来记录不同楼层的状态
确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
确定递推公式
如何可以推出dp[i]呢?
从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。
首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!
所以dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性!
dp数组如何初始化
再回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]种方法。
需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。
所以本题其实就不应该讨论dp[0]的初始化!
我相信dp[1] = 1,dp[2] = 2,这个初始化大家应该都没有争议的。
所以我的原则是:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。
确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
举例推导dp数组
举例当n为5的时候,dp table(dp数组)应该是这样的
class Solution {
public int climbStairs(int n) {
if(n <= 2) return n;
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];
}
}
//背包版本
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
int m = 2;
dp[0] = 1;
for (int i = 1; i <= n; i++) { // 遍历背包
for (int j = 1; j <= m; j++) { //遍历物品
if (i >= j) dp[i] += dp[i - j];
}
}
return dp[n];
}
}
进
行
一
个
分
割
class Solution {
public int minCostClimbingStairs(int[] cost) {
int len = cost.length;
int[] dp = new int[len + 1];
// 从下标为 0 或下标为 1 的台阶开始,因此支付费用为0
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];
}
}
进
行
一
个
分
割
按照动规五部曲来分析:
1.确定dp数组(dp table)以及下标的含义
dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
2.确定递推公式
想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。
此时在回顾一下 dp[i - 1][j] 表示啥,是从(0, 0)的位置到(i - 1, j)有几条路径,dp[i][j - 1]同理。
那么很自然,dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。
3.dp数组的初始化
如何初始化呢,首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。
4.确定遍历顺序
这里要看一下递推公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1],dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。
这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。
class Solution {
public int uniquePaths(int m, int n) {
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];
}
}
return dp[m-1][n-1];
}
}
进
行
一
个
分
割
思路同上,在赋值时判定是否有障碍。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++){
dp[i][0] = 1;
}
for(int i = 0; i < n && obstacleGrid[0][i] == 0; i++){
dp[0][i] = 1;
}
for(int i = 1;i < m;i++){
for(int j = 1;j < n;j++){
if (obstacleGrid[i][j] == 0){
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[m-1][n-1];
}
}
进
行
一
个
分
割
j*dp[i-j]同样是多个拆分,dp[i-j]的值就是多个拆分之后的结果。
拆分成一系列同样的数,乘积是最大的。
class Solution {
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++) {
// 这里的 j 其实最大值为 i-j,再大只不过是重复而已,
//并且,在本题中,我们分析 dp[0], dp[1]都是无意义的,
//j 最大到 i-j,就不会用到 dp[0]与dp[1]
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
// j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
//拆分成两个数的原因是:数很小的时候只能拆分成两个。
//而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘。
}
}
return dp[n];
}
}
进
行
一
个
分
割
当1为头结点的时候,其右子树有两个节点,看这两个节点的布局,是不是和 n 为2的时候两棵树的布局是一样的啊!
(我们就是求不同树的数量,并不用把搜索树都列出来,所以不用关心其具体数值的差异)
当3为头结点的时候,其左子树有两个节点,看这两个节点的布局,是不是和n为2的时候两棵树的布局也是一样的啊!
当2为头结点的时候,其左右子树都只有一个节点,布局是不是和n为1的时候只有一棵树的布局也是一样的啊!
发现到这里,其实我们就找到了重叠子问题了,其实也就是发现可以通过dp[1] 和 dp[2] 来推导出来dp[3]的某种方式。
思考到这里,这道题目就有眉目了。
dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量
元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量
元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量
元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量
有2个元素的搜索树数量就是dp[2]。
有1个元素的搜索树数量就是dp[1]。
有0个元素的搜索树数量就是dp[0]。
所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
递推公式:dp[i] += dp[j - 1] * dp[i - j];
class Solution {
public int numTrees(int n) {
int[] dp = new int[n+1];
dp[0] =1;
for(int i = 1;i <= n;i++){
for(int j = 0;j <=i-1;j++){
dp[i] += dp[j] * dp[i-j-1];
}
}
return dp[n];
}
}
进
行
一
个
分
割
public class BagProblem {
public static void main(String[] args) {
int[] weight = {1,3,4};
int[] value = {15,20,30};
int bagSize = 4;
testWeightBagProblem(weight,value,bagSize);
}
/**
* 动态规划获得结果
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){
// 创建dp数组
int goods = weight.length; // 获取物品的数量
int[][] dp = new int[goods][bagSize + 1];
//i是物品的数量,j是背包的容量。
// 初始化dp数组
// 创建数组后,其中默认的值就是0
for (int j = weight[0]; j <= bagSize; j++) {
dp[0][j] = value[0];
}
// 填充dp数组
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i]) {
/**
* 当前背包的容量都没有当前物品i大的时候,是不放物品i的
* 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
*/
dp[i][j] = dp[i-1][j];
} else {
/**
* 当前背包的容量可以放下物品i
* 那么此时分两种情况:
* 1、不放物品i
* 2、放物品i
* 比较这两种情况下,哪种背包中物品的最大价值最大
*/
dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
}
}
}
// 打印dp数组
for (int i = 0; i < goods; i++) {
for (int j = 0; j <= bagSize; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println("\n");
}
}
}
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}
进
行
一
个
分
割
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++) {
for(int j = target; j >= nums[i]; j--) {
//物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
//二维版本
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int len = nums.length;
int sum = 0;
for(int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[][] dp = new int[len][target+1];
for(int i = 0;i <= target;i++){
if(i >= nums[0])
dp[0][i] = nums[0];
}
for(int i = 1; i < len; i++) {
for(int j = 1; 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;
}
}
进
行
一
个
分
割
解法同上一题,关键:把石头分成重量近似相等的两堆,这样差值就是最后的结果,等价于两个大石头相撞,留下的小石头,这两个大石头的重量近似相等。
class Solution {
public int lastStoneWeightII(int[] stones) {
int sum = 0;
for (int i : stones) {
sum += i;
}
int target = sum / 2;
//初始化dp数组
int[] dp = new int[target + 1];
for (int i = 0; i < stones.length; i++) {
//采用倒序
for (int j = target; j >= stones[i]; j--) {
//两种情况,要么放,要么不放
dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
}
}
return sum - 2 * dp[target];
}
}
进
行
一
个
分
割
class Solution {
public int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int i = 0; i < nums.length; i++) sum += nums[i];
//如果target过大 sum将无法满足
if ( target < 0 && sum < -target) return 0;
if ((target + sum) % 2 != 0) return 0;
//left - right = target left = target+right
//left是正数部分
int left = (target + sum) / 2;
if(left < 0) left = -left;
int[] dp = new int[left + 1];
dp[0] = 1;
for (int i = 0; i < nums.length; i++) {
for (int j = left; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[left];
}
}
进
行
一
个
分
割
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//可以由前一个字符串推出来这个字符串是否可以添加进去
//dp[i][j]表示i个0和j个1时的最大子集
int[][] dp = new int[m + 1][n + 1];
int oneNum, zeroNum;
for (String str : strs) {
oneNum = 0;
zeroNum = 0;
for (char ch : str.toCharArray()) {
if (ch == '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][j], dp[i - zeroNum][j - oneNum] + 1);
}
}
}
return dp[m][n];
}
}
进
行
一
个
分
割
//先遍历物品,再遍历背包
private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
for (int j = 0; j < weight.length; j++){ // 遍历物品
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
进
行
一
个
分
割
跟494.目标和递推公式一样
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];
}
}
进
行
一
个
分
割
先遍历背包,再遍历物品就是排列数,反过来遍历是组合数。
组合例:先遍历物品再遍历背包,以[1,2,3] 3为例,物品2从背包开始遍历到背包容量为3时:dp[3] + dp[3-2]
排列例:遍历背包容量到3时,dp[3] + dp[3-1] ,dp[3] + dp[3-2] 背包容量为3,里面的for会从头遍历物品,就会把两种排列都算上,一种是价值为1,需要dp[2]方法,一种是价值为2,需要dp[1]种方法。
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];
}
}
进
行
一
个
分
割
class Solution {
public int coinChange(int[] coins, int amount){
int[] dp = new int[amount+1];
//这次是用min函数,需要初始成最大值,否则全会取0。
//dp[0]初始化为0。
for(int i = 1;i<=amount;i++){
dp[i] = Integer.MAX_VALUE;
}
for(int i = 0;i < coins.length;i++ ){
for(int j = coins[i]; j <= amount; j++){
//只有在dp[j-coins[i]]不为最大值的时候才去相加,否则会出现越界,然后变成Integer.最小值;
if(dp[j-coins[i]] != Integer.MAX_VALUE)
dp[j] =Math.min(dp[j-coins[i]] + 1,dp[j]);
}
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
进
行
一
个
分
割
思路同上一题
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];
for(int i = 1;i<=n;i++){
dp[i] = Integer.MAX_VALUE;
}
for(int i = 1; i <= Math.sqrt(n);i++){
for(int j = i*i;j<= n;j++){
if(dp[j-i*i] != Integer.MAX_VALUE)
dp[j] = Math.min(dp[j],dp[j-i*i] + 1);
}
}
return dp[n];
}
}
进
行
一
个
分
割
递推公式要依赖于前一个单词是否为true。
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (String word : wordDict) {
int len = word.length();
if(i >= len && dp[i - len] && word.equals(s.substring(i - len, i))){
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
进
行
一
个
分
割
public void testMultiPack1(){
// 版本一:改变物品数量为01背包格式
List<Integer> weight = new ArrayList<>(Arrays.asList(1, 3, 4));
List<Integer> value = new ArrayList<>(Arrays.asList(15, 20, 30));
List<Integer> nums = new ArrayList<>(Arrays.asList(2, 3, 2));
int bagWeight = 10;
for (int i = 0; i < nums.size(); i++) {
while (nums.get(i) > 1) { // 把物品展开为i
weight.add(weight.get(i));
value.add(value.get(i));
nums.set(i, nums.get(i) - 1);
}
}
int[] dp = new int[bagWeight + 1];
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight.get(i); j--) { // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight.get(i)] + value.get(i));
}
System.out.println(Arrays.toString(dp));
}
}
public void testMultiPack2(){
// 版本二:改变遍历个数
int[] weight = new int[] {1, 3, 4};
int[] value = new int[] {15, 20, 30};
int[] nums = new int[] {2, 3, 2};
int bagWeight = 10;
int[] dp = new int[bagWeight + 1];
for(int i = 0; i < weight.length; i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
// 以上为01背包,然后加一个遍历个数
for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
dp[j] = Math.max(dp[j], dp[j - k * weight[i]] + k * value[i]);
}
System.out.println(Arrays.toString(dp));
}
}
}
进
行
一
个
分
割
class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
if(nums.length == 1) return nums[0];
dp[0] = nums[0];
dp[1] = Math.max(nums[0],nums[1]);
for(int j = 2;j < nums.length;j++){
dp[j] = Math.max(dp[j-1],dp[j-2]+nums[j]);
}
return dp[nums.length-1];
}
}
进
行
一
个
分
割
考虑两种情况,一种是包含第一个节点,一种是包含最后一个节点。
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int len = nums.length;
if (len == 1)
return nums[0];
return Math.max(robAction(nums, 0, len - 1), robAction(nums, 1, len));
}
int robAction(int[] nums, int start, int end) {
int x = 0, y = 0, z = 0;
for (int i = start; i < end; i++) {
y = z;
z = Math.max(y, x + nums[i]);
x = y;
}
return z;
}
}
进
行
一
个
分
割
class Solution {
public int rob(TreeNode root) {
int[] res = robAction1(root);
return Math.max(res[0], res[1]);
}
int[] robAction1(TreeNode root) {
int res[] = new int[2];
if (root == null)
return res;
int[] left = robAction1(root.left);
int[] right = robAction1(root.right);
res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[1] = root.val + left[0] + right[0];
return res;
}
}
进
行
一
个
分
割
//贪心,左面取最小值,右面取最大值。
class Solution {
public int maxProfit(int[] prices) {
int low = Integer.MAX_VALUE;
int res = 0;
for(int i = 0;i < prices.length;i++){
low = Math.min(low,prices[i]);
res = Math.max(res,prices[i] - low);
}
return res;
}
}
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
for(int i = 1;i < prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],prices[i] + dp[i-1][0]);
}
return dp[prices.length - 1][1];
}
}
进
行
一
个
分
割
动规的解法
dp[i][0] 表示第i天持有股票所得现金。
dp[i][1] 表示第i天不持有股票所得最多现金
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -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],prices[i] + dp[i-1][0]);
}
return dp[prices.length - 1][1];
}
}
进
行
一
个
分
割
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][4];
dp[0][0] = -prices[0];
dp[0][2] = -prices[0];
for(int i = 1;i < prices.length;i++){
dp[i][0] = Math.max(dp[i-1][0],-prices[i]);
dp[i][1] = Math.max(dp[i-1][1],prices[i] + dp[i-1][0]);
dp[i][2] = Math.max(dp[i-1][2],dp[i-1][1]-prices[i]);
dp[i][3] = Math.max(dp[i-1][3],prices[i] + dp[i-1][2]);
}
return dp[prices.length-1][3];
}
}
进
行
一
个
分
割
原理和上面一样
class Solution {
public int maxProfit(int k, int[] prices) {
int[][] dp = new int[prices.length][2*k + 1];
for(int i = 1; i < k*2; i += 2){
dp[0][i] = -prices[0];
}
for(int i = 1;i < prices.length;i++){
for(int j = 0; j < 2*k;j+=2){
dp[i][j + 1] = Math.max(dp[i-1][j+1], dp[i-1][j]-prices[i]);
dp[i][j + 2] = Math.max(dp[i-1][j+2], dp[i-1][j+1]+prices[i]);
}
}
return dp[prices.length-1][2*k];
}
}
进
行
一
个
分
割
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][4];
dp[0][0] = -prices[0];
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][3] - prices[i], dp[i - 1][1] - 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[n - 1][3], Math.max(dp[n - 1][1], dp[n - 1][2]));
}
}
进
行
一
个
分
割
通买卖股票最佳时机2,只需要加上手续费就可。
class Solution {
public int maxProfit(int[] prices, int fee) {
int[][] dp = new int[prices.length][2];
dp[0][0] = -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],prices[i] + dp[i-1][0] - fee);
}
return dp[prices.length - 1][1];
}
}
进
行
一
个
分
割
这道题力扣的示例代码很有乐,直接把所有例子的结果整成静态数组直接输出。
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int res = 1;
int[] dp = new int[n];
Arrays.fill(dp, 1);
for(int i = 1;i < n;i++){
for(int j = 0;j < i;j++){
if(nums[j] < nums[i]){
dp[i] = Math.max(dp[j] + 1,dp[i]);
if(dp[i] > res) res = dp[i];
}
}
}
return res;
}
}
进
行
一
个
分
割
贪心和动规两种方法。
class Solution {
public int findLengthOfLCIS(int[] nums) {
//int n = nums.length;
int res = 1;
int count = 1;
//int[] dp = new int[n];
//Arrays.fill(dp, 1);
for(int i = 1;i < nums.length;i++){
if(nums[i] > nums[i-1]){
//dp[i] = dp[i-1] + 1;
count++;
//if(res < dp[i]) res = dp[i];
if(res < count) res = count;
}
else{
count = 1;
}
}
return res;
}
}
进
行
一
个
分
割
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length+1][nums2.length+1];
int res = 0;
for(int i = 1;i <= nums1.length;i++){
for(int j = 1;j <= nums2.length;j++){
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
if(res < dp[i][j]) res = dp[i][j];
}
}
}
return res;
}
}
进
行
一
个
分
割
类上一道题,递推公式在不相等的时候,取值为之前推出的最长公共子序列。
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int[][] dp = new int[text1.length()+1][text2.length()+1];
for(int i = 1;i <= text1.length();i++){
for(int j = 1;j <= text2.length();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][j-1],dp[i-1][j]);
}
}
}
return dp[text1.length()][text2.length()];
}
}
进
行
一
个
分
割
本质和上一题相同,求最长公共子序列。例:abcde 和 ace、aec,有顺序要求,ace就是3,aec就是2。
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length+1][nums2.length+1];
for(int i = 1;i <= nums1.length;i++){
for(int j = 1;j <= nums2.length;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][j-1],dp[i-1][j]);
}
}
}
return dp[nums1.length][nums2.length];
}
}
进
行
一
个
分
割
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
int res = nums[0];
dp[0] = nums[0];
for(int i = 1;i < nums.length;i++){
dp[i] = Math.max(nums[i],dp[i-1] + nums[i]);
res = res > dp[i] ? res : dp[i];
}
return res;
}
}
进
行
一
个
分
割
class Solution {
public boolean isSubsequence(String s, String t) {
int[][] dp = new int[s.length()+1][t.length()+1];
for(int i = 1;i <= s.length();i++){
for(int j = 1;j <= t.length();j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}
else{
dp[i][j] = dp[i][j-1];
}
}
}
return dp[s.length()][t.length()] == s.length() ? true : false;
}
}
进
行
一
个
分
割
class Solution {
public int numDistinct(String s, String t) {
int[][] dp = new int[s.length() + 1][t.length() + 1];
for (int i = 0; i < s.length() + 1; i++){
dp[i][0] = 1;
}
for (int i = 1; i < s.length() + 1; i++){
for (int j = 1; j < t.length() + 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[s.length()][t.length()];
}
}
进
行
一
个
分
割
方法一:求最长公共子序列,然后用每个字符串的长度减去最长公共子序列就是删除操作的最小步数。
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 1;i <= word1.length();i++){
for(int j = 1;j <= word2.length();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][j-1],dp[i-1][j]);
}
}
}
return word1.length()-dp[word1.length()][word2.length()] + word2.length()-dp[word1.length()][word2.length()];
}
}
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 1;i<=word1.length();i++) dp[i][0] = i;
for(int i = 1;i<=word2.length();i++) dp[0][i] = i;
for(int i = 1;i <= word1.length();i++){
for(int j = 1;j <= word2.length();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][j-1]+1,dp[i-1][j]+1));
}
}
}
return dp[word1.length()][word2.length()];
}
}
进
行
一
个
分
割
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i = 1;i<=word1.length();i++) dp[i][0] = i;
for(int i = 1;i<=word2.length();i++) dp[0][i] = i;
for(int i = 1;i <= word1.length();i++){
for(int j = 1;j <= word2.length();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][j-1]+1,dp[i-1][j]+1);
//替换
dp[i][j] = Math.min(dp[i][j],dp[i-1][j-1]+1);
}
}
}
return dp[word1.length()][word2.length()];
}
}
进
行
一
个
分
割
class Solution {
public int countSubstrings(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
boolean[][] dp = new boolean[len][len];
int result = 0;
for (int i = len - 1; i >= 0; i--) {
for (int j = i; j < len; j++) {
if (chars[i] == chars[j]) {
if (j - i <= 1) { // 情况一 和 情况二
result++;
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { //情况三
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
}
进
行
一
个
分
割
public class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len][len];
for(int i = len-1;i >= 0;i--){
dp[i][i] = 1;
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];
}
}
进
行
一
个
分
割
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int lens=temperatures.length;
int []res=new int[lens];
Deque<Integer> stack=new LinkedList<>();
for(int i=0;i<lens;i++){
while(!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
res[stack.peek()]=i-stack.peek();
stack.pop();
}
stack.push(i);
}
return res;
}
}
进
行
一
个
分
割
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer,Integer> map = new HashMap<>();
int[] res = new int[nums1.length];
for(int i = 0;i<nums2.length;i++){
map.put(nums2[i],i);
}
for(int i = 0;i<nums1.length;i++){
int a = map.get(nums1[i]);
for(int j = a + 1;j<nums2.length;j++){
if(nums2[j] > nums1[i]){
res[i] = nums2[j];
break;
}
}
if(res[i] == 0) res[i] = -1;
}
return res;
}
}
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
Map<Integer,Integer> map = new HashMap<>();
int[] res = new int[nums1.length];
Stack<Integer> stack = new Stack<>();
Arrays.fill(res, -1);
for(int i = 0;i<nums1.length;i++){
map.put(nums1[i],i);
}
for(int i = 0;i<nums2.length;i++){
while (!stack.isEmpty() && nums2[stack.peek()] < nums2[i]) {
int pre = nums2[stack.pop()];
if (map.containsKey(pre)) {
res[map.get(pre)] = nums2[i];
}
}
stack.push(i);
}
return res;
}
}
进
行
一
个
分
割
最开始的想法是两个数组拼到一起,然后遍历,浪费空间和时间,通过i % nums.size()来模拟遍历两遍的效果。
class Solution {
public int[] nextGreaterElements(int[] nums) {
int size = nums.length;
int[] result = new int[size];//存放结果
Arrays.fill(result,-1);//默认全部初始化为-1
Stack<Integer> st= new Stack<>();//栈中存放的是nums中的元素下标
for(int i = 0; i < 2*size; i++) {
while(!st.empty() && nums[i % size] > nums[st.peek()]) {
result[st.peek()] = nums[i % size];//更新result
st.pop();//弹出栈顶
}
st.push(i % size);
}
return result;
}
}
进
行
一
个
分
割
暴力解法
class Solution {
public int trap(int[] height) {
int sum = 0;
for (int i = 0; i < height.length; i++) {
// 第一个柱子和最后一个柱子不接雨水
if (i==0 || i== height.length - 1) continue;
int rHeight = height[i]; // 记录右边柱子的最高高度
int lHeight = height[i]; // 记录左边柱子的最高高度
for (int r = i+1; r < height.length; r++) {
if (height[r] > rHeight) rHeight = height[r];
}
for (int l = i-1; l >= 0; l--) {
if(height[l] > lHeight) lHeight = height[l];
}
int h = Math.min(lHeight, rHeight) - height[i];
if (h > 0) sum += h;
}
return sum;
}
}
双指针优化
class Solution {
public int trap(int[] height) {
int len = height.length;
int[] maxLeft = new int[len];
int[] maxRight = new int[len];
maxLeft[0] = height[0];
for(int i = 1;i < len;i++){
maxLeft[i] = Math.max(height[i],maxLeft[i-1]);
}
maxRight[len-1] = height[len-1];
for(int i = len-2;i >= 0;i--){
maxRight[i] = Math.max(height[i],maxRight[i+1]);
}
int sum = 0;
for(int i = 1;i < len-1;i++){
sum += Math.min(maxLeft[i],maxRight[i]) - height[i];
}
return sum;
}
}
单调栈解法
class Solution {
public int trap(int[] height) {
Stack<Integer> st= new Stack<>();//栈中存放的是nums中的元素下标
int len = height.length;
int sum = 0;
for(int i = 0;i < len;i++){
while(!st.isEmpty() && height[i] > height[st.peek()]){
int mid = st.pop();
if(st.isEmpty()) break;
int h = Math.min(height[i],height[st.peek()]) - height[mid];
int w = i - st.peek() - 1;
sum += h*w;
}
st.push(i);
}
return sum;
}
}
进
行
一
个
分
割
双指针优化
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
int[] leftMin = new int[len];
int[] rightMin = new int[len];
// 记录每个柱子 左边第一个小于该柱子的下标
leftMin[0] = -1;
for(int i = 1;i < len;i++){
int j = i-1;
//while循环不断找左侧第一个小于他的下标
//不可以用递归去找第一个小的下标,相当于双重for循环,超时。
while(j>=0 && heights[j] >= heights[i]){
j = leftMin[j];
}
leftMin[i] = j;
}
rightMin[len - 1] = len;
for(int i = len - 2;i >= 0;i--){
int j = i+1;
while(j <= len -1 && heights[j] >= heights[i]){
j = rightMin[j];
}
rightMin[i] = j;
}
int sum = 0;
for(int i = 0; i < len; i++){
int w = rightMin[i] - leftMin[i] - 1;
int h = heights[i];
sum = Math.max(sum,w * h);
}
return sum;
}
}
单调栈解法
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
int[] newHeight = new int[len + 2];
System.arraycopy(heights, 0, newHeight, 1, len);
Stack<Integer> stack= new Stack<>();//栈中存放的是nums中的元素下标
int res = 0;
stack.push(0);
for(int i = 1;i < len+2;i++){
while(newHeight[i] < newHeight[stack.peek()]){
int mid = stack.pop();
int w = i - stack.peek() - 1;
int h = newHeight[mid];
res = Math.max(res, w * h);
}
stack.push(i);
}
return res;
}
}
进
行
一
个
分
割