1 基础问题

// 509. 斐波那契数
// 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。// dp[i]的定义为:第i个数的斐波那契数值是dp[i]
// dp[i] = dp[i - 1] + dp[i - 2]
class Solution {public int fib(int n) {if (n < 2) return n;int[] dp = new int[n + 1];dp[0] = 0;dp[1] = 1;for (int i = 2; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}
}
// 70. 爬楼梯
// 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。// dp[i]: 爬到第i层楼梯,有dp[i]种方法
// dp[i] = dp[i - 1] + dp[i - 2]
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];}
}
// 746. 使用最小花费爬楼梯
// 给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。// dp[i]的定义:到达第i个台阶所花费的最少体力为dp[i]
// dp[i] = Math.min(dp[i - 1] + cost[i - 1],dp[i - 2] + cost[i - 2])
class Solution {public int minCostClimbingStairs(int[] cost) {int[] dp = new int[cost.length + 1];dp[0] = 0;dp[1] = 0;for (int i = 2; i <= cost.length; i++) {dp[i] = Math.min(dp[i - 1] + cost[i - 1],dp[i - 2] + cost[i - 2]);}return dp[cost.length];}
}
// 62. 不同路径
// 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?// dp[i][j] :表示从(0 ,0)出发,到(i, j) 有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];}
}
// 63. 不同路径 II
// 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?// dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
// dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)
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; i++) {if (obstacleGrid[i][0] == 1) break;dp[i][0] = 1;}for (int i = 0; i < n; i++) {if (obstacleGrid[0][i] == 1) break;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];}
}
// 343. 整数拆分
// 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。// dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。
// dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
// 其实可以从1遍历j,然后有两种渠道得到dp[i].
// 一个是j * (i - j) 直接相乘。
// 一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
class Solution {public int integerBreak(int n) {int[] dp = new int[n + 1];dp[2] = 1;for (int i = 3; i <= n; i++) {for (int j = 1; j <= i - 2; j++) {dp[i] = Math.max(dp[i],Math.max(j * (i - j),j * dp[i - j]));}}return dp[n];}
}
// 96. 不同的二叉搜索树
// 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。// dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]。
// dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以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 = 1; j <=i ; j++) {dp[i] += dp[j - 1] * dp[i - j];}}return dp[n];}
}

2 背包问题

2.1 01背包

// 01背包(二维版本)// dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
// dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
// 1.不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。
// 2.放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值
public class Test {public static void main(String[] args) {int[] weight = new int[] {1,3,4};int[] value = new int[] {15,20,30};int bagWeight = 4;int[][] dp = new int[weight.length][bagWeight + 1];for (int j = weight[0]; j <= bagWeight; j++) {dp[0][j] = value[0];}// 先遍历物品还是先遍历背包重量均可for (int i = 1; i < weight.length; i++) {for (int j = 0; j <= bagWeight; j++) {if (j < weight[i]) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - weight[i]] + value[i]);}}}for (int i = 0; i < dp.length; i++) {for (int j = 0; j < dp[0].length; j++) {System.out.print(dp[i][j] + " ");}System.out.println();}System.out.println(dp[weight.length - 1][bagWeight]);}
}
// result
0 15 15 15 15
0 15 15 20 35
0 15 15 20 35
35
// 01背包(一维版本)// dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
// dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
// 1.一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i
// 2.一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值
// 先遍历物品再遍历背包,逆序遍历
public class Test {public static void main(String[] args) {int[] weight = new int[] {1,3,4};int[] value = new int[] {15,20,30};int bagWeight = 4;int[] dp = new int[bagWeight + 1];for (int i = 0; i < weight.length; i++) {for (int j = bagWeight; j >= weight[i]; j--) {dp[j] = Math.max(dp[j],dp[j - weight[i]] + value[i]);}for (int j = 0; j <= bagWeight; j++) {System.out.print(dp[j] + " ");}System.out.println();}System.out.println(dp[bagWeight]);}
}
// result
0 15 15 15 15
0 15 15 20 35
0 15 15 20 35
35// 错误
// 先遍历背包容量再遍历物品,每个dp[j]就只会放入一个物品
public class Test {public static void main(String[] args) {int[] weight = new int[] {1,3,4};int[] value = new int[] {15,20,30};int bagWeight = 4;int[] dp = new int[bagWeight + 1];//意思解读:在i这个容量的背包里,选一个价值最高的物品放入for (int i = bagWeight; i >= 0; i--) {for (int j = 0; j < weight.length; j++) {if (i >= weight[j]) {dp[i] = Math.max(dp[i],dp[i - weight[j]] + value[j]);}}for (int j = 0; j <= bagWeight; j++) {System.out.print(dp[j] + " ");}System.out.println();}System.out.println(dp[bagWeight]);}
}
// result
0 0 0 0 30
0 0 0 20 30
0 0 15 20 30
0 15 15 20 30
0 15 15 20 30
30
// 416. 分割等和子集
// 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。// dp[j]表示 背包总容量是j,最大可以凑成j的子集总和为dp[j]。
// 相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
class Solution {public boolean canPartition(int[] nums) {if (nums == null || nums.length == 1) return false;int sum = 0;for (int num : nums) {sum += num;}if (sum % 2 == 1) return false;int target = sum / 2;int[] dp = new int[target + 1];for (int i = 0; i < nums.length; i++) {for (int j = target; j >= nums[i]; j--) {dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);}}return dp[target] == target;}
}
// 1049. 最后一块石头的重量 II
// 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:如果 x == y,那么两块石头都会被完全粉碎;如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。// dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背dp[j]这么重的石头。
// dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
class Solution {public int lastStoneWeightII(int[] stones) {int sum = 0;for (int stone : stones) {sum += stone;}int target = sum / 2;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 - dp[target] - dp[target];}
}
// 494. 目标和
// 给你一个整数数组 nums 和一个整数 target 。向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。// dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
// dp[j] += dp[j - nums[i]]
class Solution {public int findTargetSumWays(int[] nums, int target) {int sum = 0;for (int num : nums) {sum += num;}if (Math.abs(target) > sum) return 0;if ((sum + target) % 2 == 1) return 0;int bagSize = (sum + target) / 2;int[] dp = new int[bagSize + 1];dp[0] = 1;for (int i = 0; i < nums.length; i++) {for (int j = bagSize; j >= nums[i]; j--) {dp[j] += dp[j - nums[i]];}}return dp[bagSize];}
}
// 474. 一和零
// 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。// dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
// dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1)
class Solution {public int findMaxForm(String[] strs, int m, int n) {int[][] dp = new int[m + 1][n + 1];for (String str : strs) {int zeroNum = 0,oneNum = 0;for (char ch : str.toCharArray()) {if (ch == '1') {oneNum++;} else {zeroNum++;}}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];}
}

2.2 完全背包

// 完全背包
// 先遍历物品还是先遍历背包容量都可以// 1.先遍历物品再遍历背包容量
// 完全背包的物品是可以添加多次的,所以要从小到大去遍历
public class Test {public static void main(String[] args) {int[] weight = new int[] {1,3,4};int[] value = new int[] {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 j = 0; j <= bagWeight; j++) {System.out.print(dp[j] + " ");}System.out.println();}System.out.println(dp[bagWeight]);}
}
// result
0 15 30 45 60
0 15 30 45 60
0 15 30 45 60
60// 2.先遍历背包容量再遍历物品
public class Test {public static void main(String[] args) {int[] weight = new int[] {1,3,4};int[] value = new int[] {15,20,30};int bagWeight = 4;int[] dp = new int[bagWeight + 1];for (int i = 0; i <= bagWeight; i++) {for (int j = 0; j < weight.length; j++) {if (weight[j] <= i) {dp[i] = Math.max(dp[i],dp[i - weight[j]] + value[j]);}}for (int j = 0; j <= bagWeight; j++) {System.out.print(dp[j] + " ");}System.out.println();}System.out.println(dp[bagWeight]);}
}
// result
0 15 0 0 0
0 15 30 0 0
0 15 30 45 0
0 15 30 45 60
60
// 518. 零钱兑换 II
// 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。// dp[j]:凑成总金额j的货币组合数为dp[j]
// dp[j] += dp[j - coins[i]];
// 如果求组合数就是外层for循环遍历物品(元素之间要求没有顺序),内层for遍历背包。如果求排列数就是外层for遍历背包,内层for循环遍历物品((元素之间要求有顺序))。
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. 组合总和 Ⅳ
// 给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。题目数据保证答案符合 32 位整数范围。// dp[i]: 凑成目标正整数为i的排列个数为dp[i]
// dp[i] += dp[i - nums[j]]
// 排序问题,target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
class Solution {public int combinationSum4(int[] nums, int target) {int[] dp = new int[target + 1];dp[0] = 1;for (int i = 0; i <= target; i++) {for (int j = 0; j < nums.length; j++) {if (i >= nums[j]) {dp[i] += dp[i - nums[j]];}}}return dp[target];}
}
// 70. 爬楼梯
// 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。
// 改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?// dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
// dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]那么递推公式为:dp[i] += dp[i - j]
// 排列问题,将target放在外循环,将nums放在内循环。
class Solution {public int climbStairs(int n) {int[] dp = new int[n + 1];int[] weight = {1,2};dp[0] = 1;for (int i = 0; i <= n; i++) {for (int j = 0; j < weight.length; j++) {if (i >= weight[j]) dp[i] += dp[i - weight[j]];}}return dp[n];}
}
// 322. 零钱兑换
// 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。你可以认为每种硬币的数量是无限的。// dp[j]:凑足总额为j所需钱币的最少个数为dp[j]
// dp[j] = min(dp[j - coins[i]] + 1, dp[j])
// 求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数
class Solution {public int coinChange(int[] coins, int amount) {int[] dp = new int[amount + 1];for (int i = 0; i < dp.length; i++) {dp[i] = Integer.MAX_VALUE;}dp[0] = 0;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);}}}return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];}
}
// 279. 完全平方数
// 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。// dp[i]:和为i的完全平方数的最少数量为dp[i]
// dp[j] = min(dp[j - i * i] + 1, dp[j])
class Solution {public int numSquares(int n) {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 * i <= 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];}
}
// 139. 单词拆分
// 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。// dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
// if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
class Solution {public boolean wordBreak(String s, List<String> wordDict) {boolean[] dp = new boolean[s.length() + 1];dp[0] = true;for (int i = 0; i <= s.length(); i++) {for (int j = 0; j < i; j++) {if (dp[j] == true && wordDict.contains(s.substring(j,i))) {dp[i] = true;}}}return dp[s.length()];}
}

3 打劫问题

// 198. 打家劫舍
// 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。// dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
// dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
class Solution {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]);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];}
}
// 213. 打家劫舍 II
// 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。// 情况1:考虑包含首元素,不包含尾元素
// 情况2:考虑包含尾元素,不包含首元素
class Solution {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]);return Math.max(traversal(nums,0,nums.length - 2),traversal(nums,1,nums.length - 1));}private int traversal(int[] nums,int left,int right) {int[] dp = new int[nums.length];dp[left] = nums[left];dp[left + 1] = Math.max(nums[left],nums[left + 1]);for (int i = left + 2; i <= right; i++) {dp[i] = Math.max(dp[i - 1],dp[i - 2] + nums[i]);}return dp[right];}
}
// 337. 打家劫舍 III
// 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。// dp数组(dp table)以及下标的含义:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
// 如果是偷当前节点,那么左右孩子就不能偷,val1 = cur->val + left[0] + right[0];
// 如果不偷当前节点,那么左右孩子就可以偷,至于到底偷不偷一定是选一个最大的,所以:val2 = max(left[0], left[1]) + max(right[0], right[1]);
class Solution {public int rob(TreeNode root) {int[] res = new int[2];res = traversal(root);return Math.max(res[0],res[1]);}private int[] traversal(TreeNode root) {if (root == null) return new int[] {0,0};int[] leftArr = traversal(root.left);int[] rightArr = traversal(root.right);int unRob = Math.max(leftArr[0],leftArr[1]) + Math.max(rightArr[0],rightArr[1]);int rob = root.val + leftArr[0] + rightArr[0];return new int[] {unRob,rob};}
}

4 股票问题

// 121. 买卖股票的最佳时机
// 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。// dp[i][0] 表示第i天持有股票所得最多现金,dp[i][1] 表示第i天不持有股票所得最多现金
// 1.如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来。第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]。第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
// dp[i][0] = max(dp[i - 1][0], -prices[i])
// 2.如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来。第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]。第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
// dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0])
class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[2][2];dp[0][0] = -prices[0];dp[0][1] = 0;for (int i = 1; i < prices.length; i++) {dp[i % 2][0] = Math.max(dp[(i - 1) % 2][0],-prices[i]);dp[i % 2][1] = Math.max(dp[(i - 1) % 2][1],prices[i] + dp[(i - 1) % 2][0]);}return dp[(prices.length - 1) % 2][1];}
}
// 122. 买卖股票的最佳时机 II
// 给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。// dp[i][0] 表示第i天持有股票所得现金。dp[i][1] 表示第i天不持有股票所得最多现金
// 1.如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来。第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]。第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
// 2.如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来。第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]。第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[2][2];dp[0][0] = -prices[0];dp[0][1] = 0;for (int i = 1; i < prices.length; i++) {dp[i % 2][0] = Math.max(dp[(i - 1) % 2][0],dp[(i - 1) % 2][1] - prices[i]);dp[i % 2][1] = Math.max(dp[(i - 1) % 2][1],dp[(i - 1) % 2][0] + prices[i]);}return dp[(prices.length - 1) % 2][1];}
}
// 123. 买卖股票的最佳时机 III
// 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。// 一天一共就有五个状态, 0,没有操作。1,第一次买入。2,第一次卖出。3,第二次买入。4,第二次卖出
class Solution {public int maxProfit(int[] prices) {int[][] dp = new int[prices.length][5];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][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[prices.length - 1][4];}
}
// 188. 买卖股票的最佳时机 IV
// 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。// 二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j] j的范围就定义为 2 * k + 1
class Solution {public int maxProfit(int k, int[] prices) {if (prices.length == 0) return 0;int[][] dp = new int[prices.length][2 * k + 1];for (int i = 1; i < 2 * k; i += 2) {dp[0][i] = -prices[0];}for (int i = 1; i < prices.length; i++) {for (int j = 1; j <= 2 * k; j++) {if (j % 2 == 1) {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[prices.length - 1][2 * k];}
}
// 309. 最佳买卖股票时机含冷冻期
// 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。// dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。
// 状态一:买入股票状态(今天买入股票,或者是之前就买入了股票然后没有操作)卖出股票状态,这里就有两种卖出股票状态
// 状态二:两天前就卖出了股票,度过了冷冻期,一直没操作,今天保持卖出股票状态
// 状态三:今天卖出了股票
// 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!
class Solution {public int maxProfit(int[] prices) {if (prices.length == 0) return 0;int[][] dp = new int[prices.length][4];dp[0][0] = -prices[0];for (int i = 1; i < prices.length; 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[prices.length - 1][1],Math.max(dp[prices.length - 1][2],dp[prices.length - 1][3]));}
}
// 714. 买卖股票的最佳时机含手续费
// 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。// dp[i][0] 表示第i天持有股票所省最多现金。 dp[i][1] 表示第i天不持有股票所得最多现金
// dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i])
// dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee)
class Solution {public int maxProfit(int[] prices, int fee) {int[][] dp = new int[prices.length][2];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][1],dp[i - 1][0] + prices[i] - fee);}return dp[prices.length - 1][1];}
}

5 子序列问题

5.1 不连续子序列

// 300. 最长递增子序列
// 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。// dp[i]表示i之前包括i的最长上升子序列的长度。
// 位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
// if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
class Solution {public int lengthOfLIS(int[] nums) {int[] dp = new int[nums.length];Arrays.fill(dp,1);for (int i = 0; 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 res = 0;for (int item : dp) {if (item > res) {res = item;}}return res;}
}
// 1143. 最长公共子序列
// 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。// dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
// 如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;
// 如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
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++) {char ch1 = text1.charAt(i - 1);for (int j = 1; j <= text2.length(); j++) {char ch2 = text2.charAt(j - 1);if (ch1 == ch2) {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[text1.length()][text2.length()];}
}
// 1035. 不相交的线
// 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:nums1[i] == nums2[j]且绘制的直线不与任何其他连线(非水平线)相交。请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。以这种方法绘制线条,并返回可以绘制的最大连线数。// 思路同最长公共子序列
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++) {int num1 = nums1[i - 1];for (int j = 1; j <= nums2.length; j++) {int num2 = nums2[j - 1];if (num1 == num2) {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[nums1.length][nums2.length];}
}

5.2 连续子序列

// 674. 最长连续递增序列
// 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。// dp[i]:以下标i为结尾的数组的连续递增的子序列长度为dp[i]。
// 如果 nums[i + 1] > nums[i],那么以 i+1 为结尾的数组的连续递增的子序列长度 一定等于 以i为结尾的数组的连续递增的子序列长度 + 1 。即:dp[i + 1] = dp[i] + 1;
class Solution {public int findLengthOfLCIS(int[] nums) {int[] dp = new int[nums.length];Arrays.fill(dp,1);for (int i = 1; i < nums.length; i++) {if (nums[i] > nums[i - 1]) {dp[i] = dp[i - 1] + 1;}}int res = 0;for (int item : dp) {if (item > res) {res = item;}}return res;}
}
// 718. 最长重复子数组
// 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。// dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
// 当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
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++) {int num1 = nums1[i - 1];for (int j = 1; j <= nums2.length; j++) {int num2 = nums2[j - 1];if (num1 == num2) {dp[i][j] = dp[i - 1][j - 1] + 1;if (dp[i][j] > res) res = dp[i][j];}}}return res;}
}
// 53. 最大子数组和
// 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。// dp[i]:包括下标i之前的最大连续子序列和为dp[i]。
// dp[i]只有两个方向可以推出来:
// 1.dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
// 2.nums[i],即:从头开始计算当前连续子序列和
class Solution {public int maxSubArray(int[] nums) {int[] dp = new int[nums.length];dp[0] = nums[0];for (int i = 1; i < nums.length; i++) {dp[i] = Math.max(nums[i],dp[i - 1] + nums[i]);}int res = Integer.MIN_VALUE;for (int item : dp) {if (item > res) {res = item;}}return res;}
}

5.3 编辑距离

// 392. 判断子序列
// 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。// dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
// 1.if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1
// 2.if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];
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++) {char ch1 = s.charAt(i - 1);for (int j = 1; j <= t.length(); j++) {char ch2 = t.charAt(j - 1);if (ch1 == ch2) {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;}
}
// 115. 不同的子序列
// 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)题目数据保证答案符合 32 位带符号整数范围。// dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
// 1.当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
// 2.当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
class Solution {public int numDistinct(String s, String t) {int[][] dp = new int[s.length() + 1][t.length() + 1];for (int i = 0; i < dp.length; i++) {dp[i][0] = 1;}for (int i = 1; i <= s.length(); i++) {char ch1 = s.charAt(i - 1);for (int j = 1; j <= t.length(); j++) {char ch2 = t.charAt(j - 1);if (ch1 == ch2) {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()];}
}
// 583. 两个字符串的删除操作
// 给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。// dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
// 1.当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
// 2.当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1。情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1。情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
class Solution {public int minDistance(String word1, String word2) {int[][] dp = new int[word1.length() + 1][word2.length() + 1];for (int i = 0; i < dp.length; i++) dp[i][0] = i;for (int i = 0; i < dp[0].length; i++) dp[0][i] = i;for (int i = 1; i <= word1.length(); i++) {char ch1 = word1.charAt(i - 1);for (int j = 1; j <= word2.length(); j++) {char ch2 = word2.charAt(j - 1);if (ch1 == ch2) {dp[i][j] = dp[i - 1][j - 1];} else {dp[i][j] = Math.min(dp[i - 1][j] + 1,Math.min(dp[i][j - 1]+ 1,dp[i - 1][j - 1] + 2));}}}return dp[word1.length()][word2.length()];}
}
// 72. 编辑距离
// 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:插入一个字符。删除一个字符。替换一个字符// dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。
// 1.if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
// 2.if (word1[i - 1] != word2[j - 1]),此时就需要编辑。操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i - 1][j] + 1;操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i][j - 1] + 1;操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1 与 j-2为结尾的word2的最近编辑距离 加上一个替换元素的操作。即 dp[i][j] = dp[i - 1][j - 1] + 1;
class Solution {public int minDistance(String word1, String word2) {int[][] dp = new int[word1.length() + 1][word2.length() + 1];for (int i = 0; i < dp.length; i++) dp[i][0] = i;for (int i = 0; i < dp[0].length; i++) dp[0][i] = i;for (int i = 1; i <= word1.length(); i++) {char ch1 = word1.charAt(i - 1);for (int j = 1; j <= word2.length(); j++) {char ch2 = word2.charAt(j - 1);if (ch1 == ch2) {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[word1.length()][word2.length()];}
}

5.4 回文

// 647. 回文子串
// 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。子字符串 是字符串中的由连续字符组成的一个序列。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。// 布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
// 1.当s[i]与s[j]不相等,那没啥好说的了,dp[i][j]一定是false。
// 2.当s[i]与s[j]相等时,这就复杂一些了,有如下三种情况
// 情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
// 情况二:下标i 与 j相差为1,例如aa,也是回文子串
// 情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。
class Solution {public int countSubstrings(String s) {boolean[][] dp = new boolean[s.length()][s.length()];int res = 0;for (int i = s.length() - 1; i >=0; i--) {for (int j = i; j < s.length(); j++) {if (s.charAt(i) == s.charAt(j)) {if (j - i <= 1) {res++;dp[i][j] = true;} else if (dp[i + 1][j - 1] == true) {res++;dp[i][j] = true;}}}} return res;}
}
// 516.最长回文子序列
// 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。// 回文子串是要连续的,回文子序列不是连续的
// dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
// 1.如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
// 2.如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。加入s[j]的回文子序列长度为dp[i + 1][j]。加入s[i]的回文子序列长度为dp[i][j - 1]。
// 从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 i 和j相同时候的情况。所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1的
class Solution {public int longestPalindromeSubseq(String s) {int[][] dp = new int[s.length()][s.length()];for (int i = 0; i < s.length(); i++) dp[i][i] = 1;for (int i = s.length() - 1; i >= 0; i--) {for (int j = i + 1; j < s.length(); 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 + 1][j],dp[i][j - 1]);}}}return dp[0][s.length() - 1];}
}

http://www.taodudu.cc/news/show-3311071.html

相关文章:

  • 动态规划: dp+递推——确定动态矩阵dp含义,确定每个状态下面临的选择和对结果值影响,选择符合题意的作为结果存储在dp中
  • Leetcode学习之动态规划
  • 相关算法---动态规划
  • 【LeetCode】动态规划专题
  • 力扣刷题笔记
  • 刷题学习—算法思想(动态规划下)
  • 一个完整的人工神经网络,人工神经网络搭建步骤
  • leecode-试水
  • xyz回归分析 matlab,第5讲-回归分析-Matlabppt课件
  • 十一、动态规划题目相关
  • 代码随想录一刷个人记录
  • 动态规划二
  • LeetCode部分习题解答记录-动态规划
  • 动态规划归纳
  • 国际航运管理简答题-题库
  • 动态规化刷题
  • LeetCode动态规划基础题-总结(超级长文)
  • 计算机的发展经历了四代,代的划分是根据计算机的,计算机的发展经历了四代,\代\的划分是根据计算机的运算速度来划分....
  • 股票量化指标库:stockstats
  • axios的实例拦截器和全局拦截器关系。
  • axios请求拦截器
  • axios请求拦截器在请求头中添加token
  • axios请求拦截器的配置
  • axios的请求拦截和响应拦截
  • axios再次封装,axios拦截器
  • axios拦截设置和错误处理
  • axios拦截器里终止请求
  • axios拦截器设置及其实现原理剖析
  • vue3.0-axios拦截器、proxy跨域代理
  • 全局配置axios、axios 拦截器 和 proxy 跨域代理

算法-动态规划算法总结相关推荐

  1. 漫画说算法--动态规划算法三(绝对通俗易懂,非常棒)

    在前两集漫画中,我们通过一个算法问题的完整解题过程,讲述了动态规划的基本概念和思想.没看过前两集的朋友可以点击下面的链接: 漫画说算法–动态规划算法一(绝对通俗易懂,非常棒) 漫画说算法–动态规划算法 ...

  2. 矩阵相乘的strassen算法_矩阵乘法的Strassen算法+动态规划算法(矩阵链相乘和硬币问题)...

    矩阵乘法的Strassen 这个算法就是在矩阵乘法中采用分治法,能够有效的提高算法的效率. 先来看看咱们在高等代数中学的普通矩阵的乘法 两个矩阵相乘 上边这种普通求解方法的复杂度为: O(n3) 也称 ...

  3. 五大常用算法——动态规划算法详解及经典例题

    一.基本概念 动态规划是运筹学中用于求解决策过程中的最优化数学方法.当然,我们在这里关注的是作为一种算法设计技术,作为一种使用多阶段决策过程最优的通用方法. 动态规划过程是:每次决策依赖于当前状态,又 ...

  4. 算法-动态规划算法(详解)

    动态规划算法介绍 1)动态规划(Dynamic Programming)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法 2)动态规划算法与分治算法类似,其基本思想也是将 ...

  5. 漫画说算法--动态规划算法二(绝对通俗易懂,非常棒)

    在上一篇漫画中,我们分析了一道动态规划相关的算法问题,并归纳出了问题的状态转移方程式.没看过上一篇的朋友可以点击下面的链接: 漫画说算法–动态规划算法一(绝对通俗易懂,非常棒) 首先,让我们简单回顾一 ...

  6. 算法——动态规划算法

    动态规划法基本思想:将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解. 著名的应用实例有:求解最短路径问题,背包问题,项目管理,网络流优化等. 个人对动态规划的理解,主要就是避免 ...

  7. 算法——动态规划算法求解字符串的编辑距离

    当有人让你用递归算法求解斐波那契数列以及字符串的编辑距离时,所设的陷阱都是一致的(递归调用时的重复计算),解决方案也是一致的(引入备忘录概念).观察树形的层级调用关系,我们可以发现动态规划隐式地嵌入了 ...

  8. 贪心、递归、递推以及动态规划算法的分析与对比

    PS:   头一次规规矩矩的按照论文的格式写文章,呵呵.虽然是小儿科的不能再小儿科的东西了..不过..也忽悠了6000多字~~嘿嘿..肯定写的不好,第一次嘛..所以..接受大家一切批评哈!...文章N ...

  9. 以空间换时间——动态规划算法及其应用:矩阵链相乘

    动态规划算法是5大算法基础中最重要的一个,它专门用来解决平面世界下的应用,即会多次使用二维数组. 当然动态规划算法是空间换时间的算法,也就是说:我们可以利用空间资源来使某算法问题的时间复杂度降到最低. ...

最新文章

  1. 中国电子学会图形化四级编程题:解密
  2. SAP QM中阶执行事务代码QDB1,报错- Inspection severity 001 AQL 0.650 not in sampling schema A01-
  3. asp mysql datetime_ASP中SQL和Access数据库中时间/日期字段内容查询的方法说明
  4. 微信开发系列之五 - 将SAP UI5应用嵌入到微信中
  5. phpcms扁平化简约工作室团队介绍网站模板
  6. 巨杉mysql支持临时表_sql server用变量动态命名临时表表名
  7. 睡前小故事之MySQL起源
  8. 是男人就下100层【第五层】——2048游戏从源码到发布市场
  9. sm总线控制器找不到驱动程序_细说嵌入式系统下的驱动程序设计
  10. 智能交通|智慧产业园区管理系统集成搭建
  11. An Add-in Bookmark for visual studio 2005
  12. cscope的使用(2)
  13. eclipse SWT 中实现工程图标最小化到托盘,并只能右键托盘图标选择关闭
  14. syn重发_SYN***原理及处理
  15. Unity Camera Filed Of View
  16. 计算机教师师徒结对师傅总结,师徒结对师傅小结
  17. php 代替each()函数写法
  18. linux中'.'的作用
  19. Oracle Exadata一体机与云计算应用(四)
  20. 青龙面板之KS普通版、极速版【详细教程】

热门文章

  1. 2022人民网算法赛:微博话题识别任务(ERNIE文本分类)_副本
  2. 36 句话看完锤子科技 2018 夏季新品发布会
  3. D2DEngine V1.0.1 全函数解析
  4. 最新校招备战攻略,AI知识要点+面试笔记,GitHub超2万星
  5. 梦里又见清明雨!你最思念的人是谁?
  6. CAD室内设计图,CAD图纸素材
  7. 应用系统AD-及AD认证
  8. matlab simulink欠驱动水面船舶航迹自抗扰控制研究
  9. 十五分钟逻辑学系列之一——逻辑学概要
  10. FP算法挖掘疾病的关联规则