概述
刷题地址:https://leetcode-cn.com/study-plan/dynamic-programming/?progress=8e97f6
动态规划常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法。
动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归,自底向上就是递推。
使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。
推荐阅读
一套框架解决「背包问题」
「代码随想录」带你学透完全背包!【139. 单词拆分】
知识点
子序列就是可以不连续,子串要连续
判断两个整数异号,用异或运算
动态规划填表格的在线网站:https://alchemist-al.com/algorithms/edit-distance
关于排列和组合的一些思考
类似下题,把1,3和和3,1看做是两种不同的组合,我们就可以吧这个题看做是青蛙跳台阶,先跳一步,再跳3步,先跳3步,再跳1步是两种不同的跳法。
但是实际上,组合是不可以重复的,1,3和3,1应该是相同的组合是两种不同的排列
那么我们求排列问题和组合问题的时候,就转化为背包问题的先遍历背包还是先遍历物品的问题了
参考:「代码随想录」带你搞定背包问题!518. 零钱兑换 II【附背包完整攻略】
文章目录
- 推荐阅读
- 知识点
- 2021.07.13(第1天)
- [1 509. 斐波那契数](https://leetcode-cn.com/problems/fibonacci-number/)
- [2 1137. 第 N 个泰波那契数](https://leetcode-cn.com/problems/n-th-tribonacci-number/)
- 2021.07.14(第2天)
- [3 70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/)
- [4 746. 使用最小花费爬楼梯](https://leetcode-cn.com/problems/min-cost-climbing-stairs/)
- 2021.07.15(第3天)
- [5 198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/)
- [6 213. 打家劫舍 II](https://leetcode-cn.com/problems/house-robber-ii/)
- [7 740. 删除并获得点数](https://leetcode-cn.com/problems/delete-and-earn/)
- 2021.07.16(第4天)
- [8 55. 跳跃游戏](https://leetcode-cn.com/problems/jump-game/)
- [9 45. 跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/)
- 2021.07.17(第5天)
- [10 53. 最大子序和](https://leetcode-cn.com/problems/maximum-subarray/)
- 贪心算法
- 动态规划
- [11 918. 环形子数组的最大和](https://leetcode-cn.com/problems/maximum-sum-circular-subarray/)
- 2021.07.18 (第6天)
- [12 152. 乘积最大子数组](https://leetcode-cn.com/problems/maximum-product-subarray/)
- [13 1567. 乘积为正数的最长子数组长度](https://leetcode-cn.com/problems/maximum-length-of-subarray-with-positive-product/)
- 2021.07.19 (第7天)
- [14 1014. 最佳观光组合](https://leetcode-cn.com/problems/best-sightseeing-pair/)
- [15 121. 买卖股票的最佳时机](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/)
- [16 122. 买卖股票的最佳时机 II](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/)
- 动态规划
- 贪心算法
- 2021.07.20 (第8天)
- [17 309. 最佳买卖股票时机含冷冻期](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)
- [18 714. 买卖股票的最佳时机含手续费](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)
- 2021.07.21(第9天)
- [19 139. 单词拆分](https://leetcode-cn.com/problems/word-break/)
- [20 42. 接雨水](https://leetcode-cn.com/problems/trapping-rain-water/)
- 2021.07.22(第10天)
- [21 413. 等差数列划分](https://leetcode-cn.com/problems/arithmetic-slices/)
- [22 91. 解码方法](https://leetcode-cn.com/problems/decode-ways/)
- 2021.07.23(第11天)
- [23 264. 丑数 II](https://leetcode-cn.com/problems/ugly-number-ii/)
- [24 96. 不同的二叉搜索树](https://leetcode-cn.com/problems/unique-binary-search-trees/)
- 2021.07.24(第12天)
- [25 118. 杨辉三角](https://leetcode-cn.com/problems/pascals-triangle/)
- [26 119. 杨辉三角 II](https://leetcode-cn.com/problems/pascals-triangle-ii/)
- 2021.07.25 (第13天)
- [27 931. 下降路径最小和](https://leetcode-cn.com/problems/minimum-falling-path-sum/)
- [28 120. 三角形最小路径和](https://leetcode-cn.com/problems/triangle/)
- 2021.07.26 (第14天)
- [29 1314. 矩阵区域和](https://leetcode-cn.com/problems/matrix-block-sum/)
- [30 304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/)
- 2021.07.27(第15天)
- [31 62. 不同路径](https://leetcode-cn.com/problems/unique-paths/)
- [32 63. 不同路径 II](https://leetcode-cn.com/problems/unique-paths-ii/)
- 2021.07.28 (第16天)
- [33 64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/)
- [34 221. 最大正方形](https://leetcode-cn.com/problems/maximal-square/)
- 2021.07.29 (第17天)
- [35 5. 最长回文子串](https://leetcode-cn.com/problems/longest-palindromic-substring/)
- [36 516. 最长回文子序列](https://leetcode-cn.com/problems/longest-palindromic-subsequence/)
- 2021.07.30 (第18天)
- [37 300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
- [38 376. 摆动序列](https://leetcode-cn.com/problems/wiggle-subsequence/)
- 2021.07.31 (第19天)
- [39 392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/)
- [40 1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/)
- [41 72. 编辑距离](https://leetcode-cn.com/problems/edit-distance/)
- 2021.08.01(第20天)
- [42 322. 零钱兑换](https://leetcode-cn.com/problems/coin-change/)
- [43 518. 零钱兑换 II](https://leetcode-cn.com/problems/coin-change-2/)
- 2021.08.02 (第21天)
- [44 377. 组合总和 Ⅳ](https://leetcode-cn.com/problems/combination-sum-iv/)
- [45 343. 整数拆分](https://leetcode-cn.com/problems/integer-break/)
- [46 279. 完全平方数](https://leetcode-cn.com/problems/perfect-squares/)
2021.07.13(第1天)
1 509. 斐波那契数
class Solution {
public int fib(int n) {
int dp[]=new int[n+1];
dp[0]=0;
if(n<1) return dp[0];
dp[1]=1;
for(int i=2;i<n+1;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
2 1137. 第 N 个泰波那契数
class Solution {
public int tribonacci(int n) {
int dp[]=new int[n+1];
if(n==0) return 0;
if(n==1) return 1;
if(n==2) return 1;
dp[0]=0; dp[1]=1; dp[2]=1;
for(int i=3;i<n+1;i++)
{
dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
}
return dp[n];
}
}
空间优化
class Solution {
public int tribonacci(int n) {
if (n < 3) return n == 0 ? 0 : 1;
int tmp, x = 0, y = 1, z = 1;
for (int i = 3; i <= n; ++i) {
tmp = x + y + z;
x = y;
y = z;
z = tmp;
}
return z;
}
}
2021.07.14(第2天)
3 70. 爬楼梯
class Solution {
public int climbStairs(int n) {
int dp[]=new int [n];
if(n==1) return 1;
if(n==2) return 2;
dp[0]=1;dp[1]=2;
for(int i=2;i<n;i++)
{
dp[i]=dp[i-1]+dp[i-2];//先爬1级的方法数加上先爬2级的方法数
}
return dp[n-1];
}
}
递归解法,会超时
class Solution {
public int climbStairs(int n) {
if(n==1) return 1;
if(n==2) return 2;
return climbStairs(n-1)+climbStairs(n-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;
if(dp[n-1]==0)
{
return climbStairs(n-1)+climbStairs(n-2);
}
return dp[n-1];
}
}
记忆化递归2,还是超时
class Solution {
public int climbStairs(int n) {
if(n==1||n==0) return 1;
int dp[]=new int[n+1];
dp[0]=1; dp[1]=1;
if(dp[n]==0)
{
return climbStairs(n-1)+climbStairs(n-2);
}
return dp[n];
}
}
超时原因分析:dp数组是定义在递归里面的,发现了吗,,,,应该把递归数组定义在递归外面
下图这样?不行的,n都还没定义,不能给数组初始化大小
于是有聪明的小伙伴想到了重新构造一个函数,如下
class Solution {
public int climbStairs(int n) {
int dp[]=new int[n+1];
dp[0]=1; dp[1]=1;
helper(dp,n);
return dp[n];
}
public int helper(int []dp,int n)
{
if(n==1||n==0) return 1;
if(dp[n]==0)
{
dp[n]= helper(dp,n-1)+helper(dp,n-2);
}
return dp[n];
}
}
用 Map做记忆化递归,可以的
class Solution {
Map<Integer,Integer> cache = new HashMap();
public int climbStairs(int n){
if(n==0 || n==1){
return 1;
}
if(cache.containsKey(n)){
return cache.get(n);
}
int total = climbStairs(n-1)+climbStairs(n-2);
cache.put(n,total);
return total;
}
}
节约空间的解法,用变量代替数组
class Solution {
public int climbStairs(int n) {
if(n==1) return 1;
if(n==2) return 2;
int a=1,b=2,c=0;
for(int i=2;i<n;i++)
{
c=a+b;
a=b;
b=c;
}
return c;
}
}
4 746. 使用最小花费爬楼梯
这个题单是理解题就用了快半小时。。。就是注意cost[n]代表的是阶梯顶的前一个阶梯,也就是说你跳到cost[n]是需要支付cost[n]的体力值的,然后再一步登顶,你直接跳到cost[n-1],再两步登顶,就不用支付cost[n]的体力值,但是要支付cost[n-1]
我们就求每一步的最小花费就好了。
思考过程
假设有2级台阶 [15,20] 那么对应的体力值就是最小的15了
假设有3级台阶 [15,20,10] 那么对应的体力值就是20了,因为15+10=25>20
假设有3级台阶 [15,20,3] 那么对应的体力值就是18了,因为15+3=18<20
那么就不能用每一级对应的最小花费来推出下一级的最小体力花费了
我想的是再用一个数组来存放到达每一级的最大花费,但是问题是不能对应跳的是一级还是2级
那我们就比较先跳一级的最小花费和先跳2级的最小花费?
比如有7级,我们可以比较6和5+cost[6]的大小
…
…
比如有3级 我们可以比较2和1+cost[2]的大小
dp[0]=cost[0]只有第0级台阶的时候,只有一种方法可以上楼
dp[1]=cost[1]有2级台阶的时候,最小花费是cost[1]有谁不服?一步登天的事没必要分两步走啊,这不是增加体力花费嘛。比如[1,100],要到达天花板,你直接从100上去就好了。。。先从1走,再走两步是到不了天花板的,只会被卡在天花板上,因为一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯 ,你从1开始走,只能先走100在上天花板。就要花费101了
class Solution {
public int minCostClimbingStairs(int[] cost) {
int n = cost.length;
int[] dp = new int[n ];
dp[0] =cost[0];
dp[1] = cost[1];
for (int i = 2; i < n; i++) {
dp[i] = Math.min(dp[i - 1] , dp[i - 2] )+ cost[i];
}
return Math.min(dp[n - 1] , dp[n - 2] );
}
}
我又理解了一下题目,咱们可以构造一个dp数组存放到达每一级阶梯的最小花费,咱们先不管天花板,到达每一级的最小花费求出来了,则到达天花板的最小花费就是天花板前一级和天花板前两级的花费中最小的那个
dp[0]=cost[0];//到达第一级台阶的最小花费
dp[1]=cost[1];//到达第二级台阶的最小花费
第三级,可以从第一级跳上去,也可以从第二级跳上去,那么到达第三级的最小花费就是min(dp[0],dp[1])+cost[2],依次类推
参考「代码随想录」动态规划五步曲详细分析!746. 使用最小花费爬楼梯
一步一步推导动态规划的多种解法
2021.07.15(第3天)
5 198. 打家劫舍
个人分析:小偷每一次可以走多少步,可以走2,3,4,,,,n步,
可以从任意地方开始偷,
可以从第一家开始偷,也可以从第二家开始偷,但没必要从第三家开始偷吧,因为偷了第三家完全可以把第一家也偷了啊。。
现在分析[1,5,1,1,10]这种情况,小偷从第二家开始偷,直接走3步,偷第5家获得的利润将是最大
再分析[1,5,1,1,10,100]这种情况,小偷从第二家开始偷,先走2步,再走2步获得的利润将会最大
那么现在问题变成了每次走2步还是走3步的问题,没必要一次走4步,因为4可以拆分成先走2步再走2步,同理5=2+3,,,,,,,
理解拐了的嘛
class Solution {
public int rob(int[] nums) {
int N= nums.length;
int dp[]=new int[N];
if(N==1) return nums[0];
if(N==2) return Math.max(nums[0],nums[1]);
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for(int i=2;i<N;i++)
{
dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[N-1];
}
}
空间优化
class Solution {
public int rob(int[] nums) {
int N= nums.length;
int dp[]=new int[N];
if(N==1) return nums[0];
if(N==2) return Math.max(nums[0],nums[1]);
int a=nums[0];
int b=Math.max(nums[0],nums[1]);
int c=0;
for(int i=2;i<N;i++)
{
c=Math.max(a+nums[i],b);
a=b;
b=c;
}
return c;
}
}
6 213. 打家劫舍 II
这个题跟上一题的区别就是打劫了第一家,就不能打劫最后一家,因为是环壮的嘛,所以可以分为打劫第一家和不打劫第一家两种情况进行讨论
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]);
int a=nums[0];
int b=Math.max(nums[0],nums[1]);
int sum1=0,sum2=0;
sum1=b;
for(int i=2;i<N-1;i++)//打劫第一家
{
sum1=Math.max(a+nums[i],b);
a=b;
b=sum1;
}
a=nums[1];b=Math.max(nums[1],nums[2]);
sum2=b;
for(int i=3;i<N;i++)//不打劫第一家
{
sum2=Math.max(a+nums[i],b);
a=b;
b=sum2;
}
return Math.max(sum1,sum2);
}
}
把动态规划的代码合并到一个函数中
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(robHelper(nums,0, N-1),robHelper(nums,1, N));
}
public int robHelper(int nums[],int begin,int end)
{
int a=nums[begin];
int b=Math.max(nums[begin],nums[begin+1]);
int sum=b;
for(int i=begin+2;i<end;i++)
{
sum=Math.max(a+nums[i],b);
a=b;
b=sum;
}
return sum;
}
}
7 740. 删除并获得点数
一开始思路出现问题,还以为是删掉所有这个等于nums[i]-1和nums[i]+1的数就行了,实际上不是,删掉这些数之后,剩下的数组里面也只能一个一个地取,还要接着删,题目给的例子太让人迷惑了,我的代码对于给的两个例子都能过,,,,,,,,,
class Solution {
public int deleteAndEarn(int[] nums) {
int N=nums.length;
if(N==1) return nums[0];
int dp[]=new int[N];
int max=0;
Map<Integer,Integer>map=new HashMap<>();
for(int i=0;i<N;i++){
if(map.containsKey(nums[i]))//避免重复元素重复计算
{
dp[i]=map.get(nums[i]);
continue;
}
for(int j=0;j<N;j++)
{
if((nums[j]!=nums[i]-1)&&(nums[j]!=nums[i]+1))
dp[i]+=nums[j];
}
max=Math.max(dp[i],max);
map.put(nums[i],dp[i]);
}
return max;
}
}
思路:转换成打家劫舍问题
class Solution {
public int deleteAndEarn(int[] nums) {
int maxVal = 0;
for (int val : nums) {
maxVal = Math.max(maxVal, val);
}
int[] sum = new int[maxVal + 1];
for (int val : nums) {
sum[val] += val;
}
return rob(sum);
}
public int rob(int[] nums) {
int size = nums.length;
int first = nums[0], second = Math.max(nums[0], nums[1]);
for (int i = 2; i < size; i++) {
int temp = second;
second = Math.max(first + nums[i], second);
first = temp;
}
return second;
}
}
返回的sum数组就可以转成打家劫舍的数组了,即不能大打劫相邻的元素。
2021.07.16(第4天)
8 55. 跳跃游戏
如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点。
可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新。
如果可以一直跳到最后,就成功了。
作者:ikaruga
链接:https://leetcode-cn.com/problems/jump-game/solution/55-by-ikaruga/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这种方法所依据的核心特性:如果一个位置能够到达,那么这个位置左侧所有位置都能到达。 想到这一点,解法就呼之欲出了~
解释:i>k,即当前最大值k已经小于i了,说明不能到达第i个位置,也就不能到达后面的位置了
class Solution {
public boolean canJump(int[] nums) {
int N=nums.length;
int k=0;//当前能达到的最大值
for(int i=0;i<N;i++)
{
if(i>k) return false;//不能到达i
k=Math.max(k,i+nums[i]);
}
return true;
}
}
9 45. 跳跃游戏 II
移动下标达到了当前覆盖的最远距离下标时,步数就要加一,来增加覆盖距离。最后的步数就是最少步数。
class Solution {
public int jump(int[] nums) {
if (nums == null || nums.length == 0 || nums.length == 1) {
return 0;
}
//记录跳跃的次数
int count=0;
//当前的覆盖最大区域
int curDistance = 0;
//最大的覆盖区域
int maxDistance = 0;
for (int i = 0; i < nums.length; i++) {
//在可覆盖区域内更新最大的覆盖区域
maxDistance = Math.max(maxDistance,i+nums[i]);
//说明当前一步,再跳一步就到达了末尾
if (maxDistance>=nums.length-1){
count++;
break;
}
//走到当前覆盖的最大区域时,更新下一步可达的最大区域
if (i==curDistance){
curDistance = maxDistance;
count++;
}
}
return count;
}
}
7月17自己理解编写
class Solution {
public int jump(int[] nums) {
int maxCoverArea=0;
int nextPos=0;
int count=0;//跳跃次数
if(nums.length==1) return 0;
for(int i=0;i<nums.length;i++)
{
maxCoverArea=Math.max(i+nums[i], maxCoverArea);
//当前能覆盖的最大区域等于或大于数组最后一个下标,再跳一步,然后就可以返回count了
if(maxCoverArea>=nums.length-1)
{
count++;
break;
}
//当前能覆盖的最大区域小于数组最后一个下标,继续i遍历
if(i==nextPos)
{
count++;
nextPos=maxCoverArea;
}
}
return count;
}
}
2021.07.17(第5天)
10 53. 最大子序和
贪心算法
当前指针所指元素之前的和小于0,则丢弃当前元素之前的序列
贪心算法
class Solution {
public int maxSubArray(int[] nums) {
//解题思路,当前指针之前的序列和小于0就丢弃,就是已经小于0的连续数列就不要参与后面的计算了
int max=Integer.MIN_VALUE;//先让max取整数的最小值,避免数组中全是负数的情况
if(nums.length==1) return nums[0];
int curSum=nums[0];
max=nums[0];//比如[-1,-2]这种
for(int i=1;i<nums.length;i++)
{
if(curSum<0)//当前指针之前的序列和都小于0了,重置 curSum
{
curSum=nums[i];
}
else//当前指针之前的序列和大于等于0,直接加上 nums[i]
{
curSum=curSum+ nums[i];
}
max=Math.max(max,curSum);
}
return max;
}
}
简化一下,就是下面的代码
class Solution {
public int maxSubArray(int[] nums) {
//解题思路,当前指针之前的序列和小于0就丢弃,就是已经小于0的连续数列就不要参与后面的计算了
int max=Integer.MIN_VALUE;//先让max取整数的最小值,避免数组中全是负数的情况
if(nums.length==1) return nums[0];
int curSum=nums[0];
max=nums[0];//比如[-1,-2]这种
for(int i=1;i<nums.length;i++)
{
curSum=Math.max(curSum+ nums[i],nums[i]);
max=Math.max(max,curSum);
}
return max;
}
}
动态规划
若前一个元素大于0,就将它加到当前元素上面
class Solution {
public int maxSubArray(int[] nums) {
//解题思路,当前指针之前的序列和小于0就丢弃,就是已经小于0的连续数列就不要参与后面的计算了,大于0就加到nums[i]上面
int curSum=nums[0];
int dp[]=new int[nums.length];
dp[0]=nums[0];
int max=nums[0];
for(int i=1;i<nums.length;i++)
{
if(dp[i-1]>0) dp[i]= dp[i-1]+nums[i];
else dp[i]=nums[i];
max=Math.max(dp[i],max);
}
return max;
}
}
其实想想,这个跟贪心算法的代码是一样的,不过可以省略dp数组,直接在原数组上面操作,节约空间,代码如下
class Solution {
public int maxSubArray(int[] nums) {
//解题思路,当前指针之前的序列和小于0就丢弃,就是已经小于0的连续数列就不要参与后面的计算了,大于0就加到nums[i]上面
int curSum=nums[0];
int max=nums[0];
for(int i=1;i<nums.length;i++)
{
if(nums[i-1]>0) nums[i]=nums[i-1]+nums[i];
max=Math.max(nums[i],max);
}
return max;
}
}
11 918. 环形子数组的最大和
来不及了,直接抄的代码
class Solution {
public int maxSubarraySumCircular(int[] A){
// 情况一
int maxSum = A[0];
int sum = 0;
for(int a : A){
sum = a + Math.max(sum, 0);
maxSum = Math.max(maxSum, sum);
}
// 情况二
if(A.length > 2){
int minSum = Integer.MAX_VALUE;
sum = 0;
for(int i = 1; i < A.length - 1; i++){
sum = A[i] + Math.min(sum, 0);
minSum = Math.min(minSum, sum);
}
int S = 0;
for(int i = 0; i < A.length; i++){
S += A[i];
}
maxSum = Math.max(maxSum, S - minSum);
}
return maxSum;
}
}
2021.07.18 (第6天)
12 152. 乘积最大子数组
定义一个最大值数组和最小值数组dp,分别表示数组中以当前元素结尾连续子数组乘积的最大值和最小值,因为负数遇到负数会反转,正数遇到负数也会反转,所以一旦遇到负数,maxSum和minSum中的值是需要相互用到的,maxSum[i]每次取乘积最大的, minSum[i]每次取乘积最小的。最后遍历maxSum数组就好了
class Solution {
public int maxProduct(int[] nums) {
//我的想法,遇到负数就先记着,累积2个就再乘起来
//构造成绩最大和最小的数组
int N=nums.length;
if(N==1) return nums[0];
int maxSum[]=new int[N];
int minSum[]=new int[N];
maxSum[0]=nums[0];
minSum[0]=nums[0];
for(int i=1;i<N;i++)
{
maxSum[i]=Math.max(maxSum[i-1]*nums[i],Math.max(nums[i],minSum[i-1]*nums[i]));
minSum[i]=Math.min(minSum[i-1]*nums[i],Math.min(nums[i],maxSum[i-1]*nums[i]));
}
int max=Integer.MIN_VALUE;
for(int i=0;i<N;i++)
{
max=Math.max(max, maxSum[i]);
}
return max;
}
}
其实我有疑问就是遇到0应该怎么办
这是我以[2,3,-2,4,-5,0,2,200]测试出来的最后的结果
简化代码一直有错
经过Debug发现,是max取值有问题,以数组第一个元素结尾的子数组的最大乘积没有取到,应该 int max=nums[0];
class Solution {
public int maxProduct(int[] nums) {
//我的想法,遇到负数就先记着,累积2个就再乘起来
//构造成绩最大和最小的数组
int N=nums.length;
if(N==1) return nums[0];
// int maxSum[]=new int[N];
// int minSum[]=new int[N];
int a=nums[0];
int b=nums[0];
int max=nums[0];
int tmp=a;
for(int i=1;i<N;i++)
{
a=Math.max(a*nums[i],Math.max(nums[i],b*nums[i]));
max=Math.max(max, a);
b=Math.min(b*nums[i],Math.min(nums[i],tmp*nums[i]));
tmp=a;//暂存a给b用
}
return max;
}
}
13 1567. 乘积为正数的最长子数组长度
直接抄的代码提交的,看看思路,明早上去实验室复现
class Solution {
public int getMaxLen(int[] nums) {
int length = nums.length;
int positive = nums[0] > 0 ? 1 : 0;
int negative = nums[0] < 0 ? 1 : 0;
int maxLength = positive;
for (int i = 1; i < length; i++) {
if (nums[i] > 0) {
positive++;
negative = negative > 0 ? negative + 1 : 0;
} else if (nums[i] < 0) {
int newPositive = negative > 0 ? negative + 1 : 0;
int newNegative = positive + 1;
positive = newPositive;
negative = newNegative;
} else {
positive = 0;
negative = 0;
}
maxLength = Math.max(maxLength, positive);
}
return maxLength;
}
}
自己来悟的
class Solution {
public int getMaxLen(int[] nums) {
//我们用两个数组保存以当前数结尾乘积为正数的最长长度和乘积为负数的最长长度
//以当前数结尾的乘积为正数的最长长度可以由positive[i-1]+1得到(nums[i]>0),
//也可以由negative[i-1]+1得到(nums[i]<0)
//同理以当前数结尾的乘积为负数的最长长度
int length=nums.length;
int positive[]=new int[length];
int negative[]=new int[length];
if (nums[0] > 0) {
positive[0] = 1;
} else if (nums[0] < 0) {
negative[0] = 1;
}
int maxLength = positive[0];
for(int i=1;i<length;i++)
{
if(nums[i]>0)
{
positive[i]=positive[i-1]+1; //正正得正
if( negative[i-1]!=0)//就是前一个negative[i-1]为0,那么 negative[i]也应该为0,不应该加1
negative[i]=negative[i-1]+1;//负正得负
}
else if(nums[i]<0)
{
if(negative[i-1]!=0)//就是前一个negative[i-1]为0,那么positive[i]也应该为0,不应该加1
positive[i]=negative[i-1]+1; //负负得正
// if(positive[i-1]!=0)
negative[i]=positive[i-1]+1;//负正得负
}
else
{
positive[i]=0; //负负得正
negative[i]=0;//负正得负
}
}
for(int i=0;i<length;i++)
{
maxLength=Math.max(maxLength,positive[i]);
}
return maxLength;
}
}
2021.07.19 (第7天)
14 1014. 最佳观光组合
暴力解决
class Solution {
public int maxScoreSightseeingPair(int[] values) {
int max=0;
for(int i=0;i<values.length;i++)
{
for(int j=0;j<i;j++)
{
if(j!=i)
{
max=Math.max(max, countValues(values, j,i));
}
}
}
return max;
}
int countValues(int [] values,int i,int j)
{
return values[i] + values[j] + i - j;
}
}
遍历优化
class Solution {
public int maxScoreSightseeingPair(int[] values) {
int max=0;
int maxValues=values[0]+0;//最大的values[i]+i的值
for(int i=1;i<values.length;i++)
{
maxValues=Math.max(maxValues,values[i-1]+i-1);//当前i之前的最大 maxValues
max=Math.max(maxValues+values[i]-i,max);//当前i的maxValues+values[i]-i与max相比
}
return max;
}
}
class Solution {
public int maxScoreSightseeingPair(int[] values) {
int ans = 0, mx = values[0] + 0;
for (int j = 1; j < values.length; ++j) {
ans = Math.max(ans, mx + values[j] - j);
// 边遍历边维护
mx = Math.max(mx, values[j] + j);
}
return ans;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-sightseeing-pair/solution/zui-jia-guan-guang-zu-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
15 121. 买卖股票的最佳时机
就是要考虑到利润为负数的时候不应该卖出股票,利润为0,
return maxprofit>0?maxprofit:0;
class Solution {
public int maxProfit(int[] prices) {
int min=prices[0];//最低价格
int maxprofit=Integer.MIN_VALUE;//最大利润
for(int i=1;i<prices.length;i++)
{
maxprofit=Math.max(maxprofit,prices[i]-min);
min=Math.min(prices[i],min);
}
return maxprofit>0?maxprofit:0;
}
}
16 122. 买卖股票的最佳时机 II
class Solution {
public int maxProfit(int[] prices) {
//搜集所有的上坡
int maxprofit=0;
for(int i=1;i<prices.length;i++)
{
if(prices[i]-prices[i-1]>0)
{
maxprofit+=prices[i]-prices[i-1];
}
}
return maxprofit;
}
}
动态规划
就是分为当天结束手里有股票或者没有股票
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++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[n - 1][0];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-ii-by-leetcode-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
贪心算法
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
int n = prices.length;
for (int i = 1; i < n; ++i) {
ans += Math.max(0, prices[i] - prices[i - 1]);
}
return ans;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-ii-by-leetcode-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.20 (第8天)
17 309. 最佳买卖股票时机含冷冻期
class Solution {
public int maxProfit(int[] prices) {
//分为3种情况
//1 当天结束手里有股票 2 当天结束手里没有股票,当天不是冷冻期 3 当天结束手里没有股票,当天为冷冻期
int length=prices.length;
int a[]=new int[length];//当天结束手里有股票
int b[]=new int[length];//当天结束手里没有股票,当天结束不是冷冻期
int c[]=new int[length];//当天结束后处于为冷冻期的最大收益
a[0]=-prices[0]; b[0]=0; c[0]=0;
//这里的「处于冷冻期」指的是在第 i 天结束之后的状态。也就是说:如果第 i 天结束之后处于冷冻期,那么第 i+1 天无法买入股票。
for(int i=1;i<length;i++)
{
int a1=Math.max(a[i-1],b[i-1]-prices[i]);//手里有一只股票可能情况为前一天手里就有一只股票,一直没卖,
//也可能是前一天结束后不是冷冻期,今天买入
int b1=Math.max(b[i-1],c[i-1]);//手里没有股票且不是冷冻期,说明第i天没有任何操作,可能情况为前一天结束后为冷冻期,也可能是前一天结束后手里没有股票,当天结束不是冷冻期
int c1=a[i-1]+prices[i];//今天借宿后是冷冻期只可能是今天卖掉了一只股票,说明在第 i−1天时我们必须持有一支股票
a[i]=a1;b[i]=b1;c[i]=c1;
}
return Math.max(b[length-1],c[length-1] );
}
}
18 714. 买卖股票的最佳时机含手续费
class Solution {
public int maxProfit(int[] prices, int fee) {
//分为当天结束手里有股票和手里没有股票的情况 如果有交易,就扣一次手续费
int length=prices.length;
int a[]=new int[length];//当天结束手里有股票
int b[]=new int [length];//当天结束手里没有股票
a[0]=-prices[0];
b[0]=0;
for(int i=1;i<length;i++)
{
int a1=Math.max(a[i-1],b[i-1]-prices[i]);//当天结束手里有股票,可能是前一天手里有股票,也可能是今天买入了
int b1=Math.max(b[i-1],a[i-1]+prices[i]-fee);//当天结束手里没有股票,可能是前一天有股票,今天卖出了,也可能是前一天手里就没有股票
a[i]=a1;b[i]=b1;
}
return b[length-1];
}
}
空间优化
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int sell = 0, buy = -prices[0];
for (int i = 1; i < n; ++i) {
sell = Math.max(sell, buy + prices[i] - fee);
buy = Math.max(buy, sell - prices[i]);
}
return sell;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-han-sh-rzlz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
贪心算法
buy 表示在最大化收益的前提下,如果我们手上拥有一支股票,那么它的最低买入价格是多少。
即当我们卖出一支股票时,我们就立即获得了以相同价格并且免除手续费买入一支股票的权利。在遍历完整个数组 prices 之后,我们就得到了最大的总收益。
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int buy = prices[0] + fee;
int profit = 0;
for (int i = 1; i < n; ++i) {
if (prices[i] + fee < buy) {
buy = prices[i] + fee;
} else if (prices[i] > buy) {
profit += prices[i] - buy;
buy = prices[i];
}
}
return profit;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-han-sh-rzlz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.21(第9天)
19 139. 单词拆分
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String>set=new HashSet(wordDict);
int n=s.length();
int dp[]=new int[n+1];
//用dp[i]表示以i结尾的s的子串能否由wordDict中的单词组成
dp[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<i;j++)
{
if(dp[j]==1&&set.contains(s.substring(j,i)))
{
dp[i]=1;
break;
}
}
}
return dp[n]==1?true:false;
}
}
优化
对于检查一个字符串是否出现在给定的字符串列表里一般可以考虑哈希表来快速判断,同时也可以做一些简单的剪枝,枚举分割点的时候倒着枚举,如果分割点 j 到 i 的长度已经大于字典列表里最长的单词的长度,那么就结束枚举
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
参考
单词拆分
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int len = s.length(), maxw = 0;
boolean[] dp = new boolean[len + 1];
dp[0] = true;
Set<String> set = new HashSet();
for(String str : wordDict){
set.add(str);
maxw = Math.max(maxw, str.length());
}
for(int i = 1; i < len + 1; i++){
for(int j = i; j >= 0 && j >= i - maxw; j--){
if(dp[j] && set.contains(s.substring(j, i))){
dp[i] = true;
break;
}
}
}
return dp[len];
}
}
20 42. 接雨水
这个题是难题,有点来不及做了,也有点抗拒哈,先抄的答案过了,等会看一下视频,明天早上复现
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.22(第10天)
21 413. 等差数列划分
暴力破解
枚举每一个i j 看是否满足,时间复杂度为o(n的3次方)
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int count=0;boolean flag=true;
for(int i=0;i<nums.length-2;i++)//外层最多循环到倒数第二个元素,因为最少需要3个元素
{
int temp=nums[i+1]-nums[i];
for(int j=i+2;j<nums.length;j++)
{
flag=true;//尤其要注意这里,给每一个i对应的j都初始化一个 flag;
for(int k=j-i;k>1;k--)
{
if((nums[i+k]-nums[i+k-1])!=temp)
{
flag=false;
break;
}
}
if(flag)
{
count++;
}
}
}
return count;
}
}
暴力优化
加入i=3 j=5 如果满足条件 ,那么3到5是一个子序列,我们在判断6-5和5-4是否相等,如果相等,则3到6也是满足条件的一个子序列,这些序列都是以3开头的,不会和后面的重复
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int count=0;boolean flag=true;
for(int i=0;i<nums.length-2;i++)//外层最多循环到倒数第二个元素,因为最少需要3个元素
{
int temp=nums[i+1]-nums[i];
for(int j=i+2;j<nums.length;j++)
{
if(nums[j]-nums[j-1]==temp)
{
count++;
}
else break;
}
}
return count;
}
}
动态规划
首先创建一个大小为 n 的一维数组 dp。dp[i] 用来存储在区间 (k,i) 而不在区间 (k,j) 中等差数列的个数,其中 j<i
比如 1 3 5 7 9 对于dp的下标2,他可以构成一个等差数列 ,对于下标3,新增的等差数列都是以下标3结尾的,有1 3 5 7 , 3 5 7,即dp[i-1]+1,依次类推
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int count=0;
int dp[]=new int[nums.length];
if(nums.length<3) return 0;
dp[0]=0; dp[1]=0;
for(int i=2;i<nums.length;i++)
{
if(nums[i]-nums[i-1]==nums[i-1]-nums[i-2])
{
dp[i]=dp[i-1]+1;
count=count+dp[i];
}
}
return count;
}
}
动态规划空间优化
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int count=0;
//用dp数组表示每一个下标能构成的等差出列的最大个数
int dp=0;
if(nums.length<3) return 0;
for(int i=2;i<nums.length;i++)
{
if(nums[i]-nums[i-1]==nums[i-1]-nums[i-2])
{
dp=dp+1;
count=count+dp;
}
else
{
dp=0;
}
}
return count;
}
}
22 91. 解码方法
这个题也是抄的代码,明天复现
class Solution {
public int numDecodings(String s) {
if(s.charAt(0) == '0') {
//说明无解
return 0;
}
char[] charArray = s.toCharArray();
int last_2 = 1, last_1 = 1; //last_2 代表i-2 last_1 代表i-1 temp 代表当前
for(int i=1; i<s.length(); i++) {
int temp = last_1;
if(charArray[i] == '0') {
if(charArray[i-1] == '1' || charArray[i-1] == '2') {
temp = last_2;
}else {
return 0;
}
}else if( charArray[i-1] == '1' || (charArray[i-1] == '2' && charArray[i] - '0'>0 && charArray[i] - '0'<7)) {
temp += last_2;
}
last_2 = last_1;
last_1 = temp;
}
return last_1;
}
}
参考:C++ 我认为很简单直观的解法
复现
class Solution {
public int numDecodings(String s) {
//这个题跟爬楼梯差不多,对于最后一个字符,它可以单独解码,也可以跟前一个字符混合解码
//单独解码的时候,要求该字符不能为0,那么可解码总数就是前一个字符之前的可解码总数,即dp[n-1]
//跟前一个字符混合解码的时候,要求前一个字符不能是0,不能超过2,当前一个字符为2的时候,最后一个字符不能超过6
//跟前一个字符混合解码的时候,可解码的总数就是dp[n-2]
//那么最后一个字符的可解码总数就是dp[n-2]+dp[n-1]
//其他情况就不满足解码要求了,直接忽略
if(s.length()==1&&s.charAt(0)=='0') return 0;
if(s.length()==1&&s.charAt(0)!='0') return 1;
int dp[]=new int[s.length()+1];
dp[0]=1;
for(int i=1;i<=s.length();i++)
{
if(s.charAt(i-1)!='0') dp[i]=dp[i-1];
if (i >= 2 && (s.charAt(i - 2) == '1' || s.charAt(i - 2) == '2' && s.charAt(i - 1) <= '6'))
dp[i] += dp[i - 2];
}
return dp[s.length()];
}
}
class Solution {
public:
int numDecodings(string s) {
if(s[0] == '0') return 0;
int n = s.size();
vector<int> dp(n+1,1);
//dp[0]表示s[-1]的状态, dp[1] 表示 s[0]的状态
//dp[i] 表示 s[i-1]的状态
for(int i=2;i<=n;i++){
if(s[i-1] == '0'){
if(s[i-2] == '1' || s[i-2] == '2')//唯一译码,不增加情况
dp[i] = dp[i-2];
else//这里要好好理解一下,比如给定340, 输出可行的编码数为0, 因为0和40都无法转换
return 0;
}
else if(s[i-2] == '1' || s[i-2] == '2' && s[i-1] >= '1' && s[i-1] <= '6')
dp[i] = dp[i-1] + dp[i-2];
else//当上述条件都不满足,维持上一个状态
dp[i] = dp[i-1];
}
//for(auto c:dp) cout << c << ",";
return dp[n];//返回dp[n] 即最后 s[n-1] 的状态
}
};
2021.07.23(第11天)
23 264. 丑数 II
class Solution {
public int nthUglyNumber(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
int p2 = 1, p3 = 1, p5 = 1;
for (int i = 2; i <= n; i++) {
int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;
dp[i] = Math.min(Math.min(num2, num3), num5);
if (dp[i] == num2) {
p2++;
}
if (dp[i] == num3) {
p3++;
}
if (dp[i] == num5) {
p5++;
}
}
return dp[n];
}
}
24 96. 不同的二叉搜索树
class Solution {
public int numTrees(int n) {
int[] G = new int[n + 1];
G[0] = 1;
G[1] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
}
}
2021.07.24(第12天)
25 118. 杨辉三角
class Solution {
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
for (int i = 0; i < numRows; ++i) {
List<Integer> row = new ArrayList<Integer>();
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
row.add(1);
} else {
row.add(ret.get(i - 1).get(j - 1) + ret.get(i - 1).get(j));
}
}
ret.add(row);
}
return ret;
}
}
26 119. 杨辉三角 II
class Solution {
public List<Integer> getRow(int rowIndex) {
List<List<Integer>> C = new ArrayList<List<Integer>>();
for (int i = 0; i <= rowIndex; ++i) {
List<Integer> row = new ArrayList<Integer>();
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
row.add(1);
} else {
row.add(C.get(i - 1).get(j - 1) + C.get(i - 1).get(j));
}
}
C.add(row);
}
return C.get(rowIndex);
}
}
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> pre = new ArrayList<Integer>();
for (int i = 0; i <= rowIndex; ++i) {
List<Integer> cur = new ArrayList<Integer>();
for (int j = 0; j <= i; ++j) {
if (j == 0 || j == i) {
cur.add(1);
} else {
cur.add(pre.get(j - 1) + pre.get(j));
}
}
pre = cur;
}
return pre;
}
}
2021.07.25 (第13天)
27 931. 下降路径最小和
这是我的第一版代码,还没有考虑边界以及剪枝,通不过
class Solution {
public int minFallingPathSum(int[][] matrix) {
int min=Integer.MIN_VALUE;
for(int i=0;i<matrix[0].length;i++)
{
min=Math.min(dfs(matrix,0,i),min);
}
return min;
}
public int dfs(int [][]matrix,int i,int j)
{
if(i+1==matrix.length)
return 0;
return Math.min(dfs(matrix,i+1,j),Math.min(dfs(matrix,i+1,j+1),dfs(matrix,i+1,j-1)))+matrix[i][j];
}
}
这个能过
class Solution {
public int minFallingPathSum(int[][] A) {
int N = A.length;
for (int r = N-2; r >= 0; --r) {
for (int c = 0; c < N; ++c) {
// best = min(A[r+1][c-1], A[r+1][c], A[r+1][c+1])
int best = A[r+1][c];
if (c > 0)
best = Math.min(best, A[r+1][c-1]);
if (c+1 < N)
best = Math.min(best, A[r+1][c+1]);
A[r][c] += best;
}
}
int ans = Integer.MAX_VALUE;
for (int x: A[0])
ans = Math.min(ans, x);
return ans;
}
}
28 120. 三角形最小路径和
暴力dfs 超时
顶点到底边的最小距离,可以转化为顶点的值加上与顶点相邻的两个节点中,到底边的距离最小的一个的距离值,
而与顶点相邻的两个节点中,到底边的距离最小的一个的距离值可以把这两个节点分别当作顶点,按同样的方式求解
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
return dfs(triangle,0,0);
}
public int dfs(List<List<Integer>> triangle,int i,int j)
{
if(i==triangle.size())
{
return 0;
}
return Math.min(dfs(triangle,i+1,j),dfs(triangle,i+1,j+1))+triangle.get(i).get(j);
}
}
记忆化dfs
class Solution {
Integer[][] memo;
public int minimumTotal(List<List<Integer>> triangle) {
memo = new Integer[triangle.size()][triangle.size()];
return dfs(triangle,0,0);
}
public int dfs(List<List<Integer>> triangle,int i,int j)
{
if(i==triangle.size())
{
return 0;
}
if (memo[i][j] != null) {
return memo[i][j];
}
return memo[i][j] = Math.min(dfs(triangle, i + 1, j), dfs(triangle, i + 1, j + 1)) + triangle.get(i).get(j);
}
}
参考
递归 + 记忆化 + DP,????♀️ 必须秒懂!
自己写的动态规划
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int M=triangle.size();
int N=triangle.get(M-1).size();//注意这里,,,
//用二维dp数组代表点[i][j]到底边的最小距离
int dp[][]=new int [M][N];
dp[0][0]=triangle.get(0).get(0);
if(M==1) return dp[0][0];
for(int i=1;i<M;i++)
{
dp[i][0]=dp[i-1][0]+triangle.get(i).get(0);//每一行的最左一列只能从上一行的最左一列走到
for(int j=1;j<i;j++)
{
dp[i][j]=Math.min(dp[i-1][j],dp[i-1][j-1])+triangle.get(i).get(j);//每一行中间的分别可以由上一行的两个列得到
}
dp[i][i]=dp[i-1][i-1]+triangle.get(i).get(i);//每一行的最后一列只能从上一行的最后一列得到
}
int min=Integer.MAX_VALUE;
for(int i=0;i<N;i++)
{
min=Math.min(min,dp[M-1][i]) ;
}
return min;
}
}
官方的还是简洁很多,比如二维数组的列数的选取,比如最后求最小值的时候min初始值选为 f[n - 1][0]
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] f = new int[n][n];
f[0][0] = triangle.get(0).get(0);
for (int i = 1; i < n; ++i) {
f[i][0] = f[i - 1][0] + triangle.get(i).get(0);
for (int j = 1; j < i; ++j) {
f[i][j] = Math.min(f[i - 1][j - 1], f[i - 1][j]) + triangle.get(i).get(j);
}
f[i][i] = f[i - 1][i - 1] + triangle.get(i).get(i);
}
int minTotal = f[n - 1][0];
for (int i = 1; i < n; ++i) {
minTotal = Math.min(minTotal, f[n - 1][i]);
}
return minTotal;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/triangle/solution/san-jiao-xing-zui-xiao-lu-jing-he-by-leetcode-solu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这个从下往上递推就绝了呀
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int M=triangle.size();
int N=triangle.get(M-1).size();//注意这里,,,
//用二维dp数组代表点[i][j]到底边的最小距离
int dp[][]=new int [M+1][N+1];//用来避免最后一行算的时候越界
for (int i = M - 1; i >= 0; i--)//注意是从最后一行开始算的
{
for(int j=0;j<=i;j++)
{
dp[i][j]=Math.min(dp[i+1][j],dp[i+1][j+1])+triangle.get(i).get(j);
}
}
return dp[0][0];
}
}
优化,二维数组变一维数组(这个是确实没想通啊)
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[] dp = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
for (int j = 0; j <= i; j++) {
dp[j] = Math.min(dp[j], dp[j + 1]) + triangle.get(i).get(j);
}
}
return dp[0];
}
}
作者:sweetiee
链接:https://leetcode-cn.com/problems/triangle/solution/di-gui-ji-yi-hua-dp-bi-xu-miao-dong-by-sweetiee/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.26 (第14天)
这两个题感觉思路有点奇怪,题意也不好理解,先留着吧
29 1314. 矩阵区域和
30 304. 二维区域和检索 - 矩阵不可变
2021.07.27(第15天)
31 62. 不同路径
dfs超时
class Solution {
public int count=0;
public int uniquePaths(int m, int n) {
return dfs( m,n,1, 1);
// return count;
}
public int dfs(int m,int n,int i,int j)
{
if(i==m&&j==n)
{
count++;
return 1;
}
if(i>m||j>n) return 0;
return dfs(m,n,i+1,j)+ dfs(m,n,i,j+1);
}
}
动态规划
class Solution {
public int uniquePaths(int m, int n) {
int[][] f = new int[m][n];
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
return f[m - 1][n - 1];
}
}
32 63. 不同路径 II
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int n = obstacleGrid.length, m = obstacleGrid[0].length;
int[] f = new int[m];
f[0] = obstacleGrid[0][0] == 0 ? 1 : 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (obstacleGrid[i][j] == 1) {
f[j] = 0;
continue;
}
if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {
f[j] += f[j - 1];
}
}
}
return f[m - 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/unique-paths-ii/solution/bu-tong-lu-jing-ii-by-leetcode-solution-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.28 (第16天)
33 64. 最小路径和
日常超时 dfs
class Solution {
int m=0;
int n=0;
public int minPathSum(int[][] grid) {
m=grid.length;
n=grid[0].length;
return dfs(grid,0,0);
}
public int dfs(int[][]grid,int i,int j)
{
if(i==m-1&&j==n-1) return grid[i][j];
if(i==m-1) return dfs(grid,i,j+1)+grid[i][j];
if(j==n-1) return dfs(grid,i+1,j)+grid[i][j];
return Math.min(dfs(grid,i+1,j),dfs(grid,i,j+1))+grid[i][j];
}
}
记忆化dfs
自顶向下,分情况讨论,左上角的点到达右下角的最小距离为它向右和向下左的最小距离,我们需要再找它右边的点和下边的点到右下角点的最小距离
class Solution {
int m=0;
int n=0;
Integer memo[][];
public int minPathSum(int[][] grid) {
m=grid.length;
n=grid[0].length;
memo=new Integer[m][n];
return dfs(grid,0,0);
}
public int dfs(int[][]grid,int i,int j)
{
if(memo[i][j]!=null) return memo[i][j];
if(i==m-1&&j==n-1) return memo[i][j]=grid[i][j];
if(i==m-1) return memo[i][j]=dfs(grid,i,j+1)+grid[i][j];
if(j==n-1) return memo[i][j]=dfs(grid,i+1,j)+grid[i][j];
return memo[i][j]=Math.min(dfs(grid,i+1,j),dfs(grid,i,j+1))+grid[i][j];
}
}
动态规划
dp[i][j]代表到达[i][j]这个点的最短路径,分情况讨论,分为左上角点,左边界点和上边界点以及其他点讨论
class Solution {
int m=0;
int n=0;
public int minPathSum(int[][] grid) {
m=grid.length;
n=grid[0].length;
int dp[][]=new int [m][n];
dp[0][0]=grid[0][0];
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(i==0&&j!=0) dp[i][j]=dp[i][j-1]+grid[i][j];//上边界(只能从左边过来)
else if(j==0&&i!=0) dp[i][j]=dp[i-1][j]+grid[i][j]; //左边界(只能从上边下来)
else
{
if (i!=0&&j!=0)dp[i][j]=Math.min(dp[i][j-1],dp[i-1][j])+grid[i][j]; //去除原点
}
}
}
return dp[m-1][n-1];
}
}
简化动态规划
上面的例子用了太多的判断,我们考虑给grid加一条左边界和上边界,就不用考虑那么多条件了,这样所有的点都是可以从上边和左边两条路径到达
class Solution {
int m=0;
int n=0;
public int minPathSum(int[][] grid) {
m=grid.length;
n=grid[0].length;
int dp[][]=new int [m+1][n+1];
for(int i=2;i<m+1;i++) dp[i][0]=Integer.MAX_VALUE;//初始化左边界
for(int j=1;j<n+1;j++) dp[0][j]=Integer.MAX_VALUE;//初始化上边界
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=Math.min(dp[i][j-1],dp[i-1][j])+grid[i-1][j-1];
}
}
return dp[m][n];
}
}
官方题解
直接把左边界和上边界拿出来单独计算,并不需要增加边界,也是挺好的思路
class Solution {
public int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length, columns = grid[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/minimum-path-sum/solution/zui-xiao-lu-jing-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
这个解法对于i=0 j=0的处理比我巧妙,且是原地动态规划
class Solution {
public int minPathSum(int[][] grid) {
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(i == 0 && j == 0) continue;
else if(i == 0) grid[i][j] = grid[i][j - 1] + grid[i][j];
else if(j == 0) grid[i][j] = grid[i - 1][j] + grid[i][j];
else grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
return grid[grid.length - 1][grid[0].length - 1];
}
}
作者:jyd
链接:https://leetcode-cn.com/problems/minimum-path-sum/solution/zui-xiao-lu-jing-he-dong-tai-gui-hua-gui-fan-liu-c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
34 221. 最大正方形
class Solution {
public int maximalSquare(char[][] grid) {
int m=grid.length;
int n=grid[0].length;
int dp[][]=new int[m+1][n+1];
int maxLen=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(grid[i-1][j-1]=='1')
{
dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+1;
maxLen=Math.max(maxLen,dp[i][j]);
}
}
}
return maxLen *maxLen;
}
}
参考
理解 三者取最小+1
2021.07.29 (第17天)
35 5. 最长回文子串
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
public int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return right - left - 1;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-palindromic-substring/solution/zui-chang-hui-wen-zi-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
36 516. 最长回文子序列
子序列就是可以不连续,子串要连续
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] f = new int[n][n];
for (int i = n - 1; i >= 0; i--) {
f[i][i] = 1;
for (int j = i + 1; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
f[i][j] = f[i + 1][j - 1] + 2;
} else {
f[i][j] = Math.max(f[i + 1][j], f[i][j - 1]);
}
}
}
return f[0][n - 1];
}
}
作者:a380922457
链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence/solution/dong-tai-gui-hua-si-yao-su-by-a380922457-3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.30 (第18天)
37 300. 最长递增子序列
dp[i]表示当前元素结尾的最长子序列的长度,可以初始化为1,如果它前面的元素都大于它,那么只能是1,如果前面有元素小于它,dp[i]=max(dp[i],dp[j]+1)
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++) {
for(int j = 0; j < i; j++) {
if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
38 376. 摆动序列
class Solution {
public int wiggleMaxLength(int[] nums) {
//先把所有的差值放进一个数组,然后判断这个差值数组的正负关系,判断两个数是否异号可以用异或
//转化为求最长子序列
if(nums.length==1) return 1;
int tempArr[]=new int[nums.length-1];
int dp[]=new int[nums.length-1];
for(int i=0;i<nums.length-1;i++)
{
tempArr[i]=nums[i+1]-nums[i];
}
Arrays.fill(dp,1);//dp数组初始化为1
int maxLen=0;
for(int i=0;i<nums.length-1;i++ )
{
if (tempArr[i]==0) dp[i]=0;
for(int j=0;j<i;j++)
{
if((tempArr[i]^tempArr[j])<0&&tempArr[i]!=0)//说明异号
{
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
maxLen=Math.max(maxLen,dp[i]);
}
return maxLen+1;
}
}
动态规划绝了,分成
public int wiggleMaxLength(int[] nums) {
int down = 1, up = 1;
for (int i = 1; i < nums.length; i++) {
if (nums[i] > nums[i - 1])
up = down + 1;
else if (nums[i] < nums[i - 1])
down = up + 1;
}
return nums.length == 0 ? 0 : Math.max(down, up);
}
作者:lgh18
链接:https://leetcode-cn.com/problems/wiggle-subsequence/solution/tan-xin-si-lu-qing-xi-er-zheng-que-de-ti-jie-by-lg/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
2021.07.31 (第19天)
39 392. 判断子序列
思路:暴力求解,依次匹配在长字符串中匹配端字符串的每一个字符,匹配到就count+1,同时跳出内循环,最后比较count和短字符串的长度
也可以对每一个字符设置一个标志位flag,匹配到就设置为true,没有匹配到直接返回false;
class Solution {
public boolean isSubsequence(String s, String t) {
int start=0;int count=0;
for(int i=0;i<s.length();i++)
{
boolean flag=false;
for(int j=start;j<t.length();j++)
{
if(s.charAt(i)==t.charAt(j))
{
flag=true;
start=j+1;
// count++;
break;
}
}
if(!flag) return false;
}
// System.out.println(count);
// return count==s.length();
return true;
}
}
思路2 双指针yyds
class Solution {
public boolean isSubsequence(String s, String t) {
int i=0;int j=0;
while(i<s.length()&&j<t.length())
{
if(s.charAt(i)==t.charAt(j))
{
i++;//如果匹配,i,j后移
}
j++;//不匹配,j后移,继续匹配
}
return i==s.length();
}
}
40 1143. 最长公共子序列
注意 当两个子串末尾字符不相等时,比如 abcd 和adcb,那么dp[4][4]应该是 abcd和adc相比,以及abc和adcb中最长公共序列较大的那一个
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m=text1.length();int n=text2.length();
int dp[][]=new int[m+1][n+1];
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;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-1][j],dp[i][j-1]);
}
}
return dp[m][n];
}
}
41 72. 编辑距离
参考
视频图解 动态规划 编辑距离
动态规划填表格的在线网站:https://alchemist-al.com/algorithms/edit-distance
视频是真的好懂啊。
class Solution {
public int minDistance(String word1, String word2) {
int m=word1.length();int n=word2.length();
int dp[][]=new int [m+1][n+1];
for(int i=0;i<=m;i++) dp[i][0]=i;
for(int j=0;j<=n;j++) dp[0][j]=j;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;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],Math.min(dp[i][j-1],dp[i-1][j-1]))+1;
}
}
return dp[m][n];
}
}
2021.08.01(第20天)
42 322. 零钱兑换
写的动态规划超时了,这是我没有想到的。。。。
思路:先把硬币面额存进一个set,然后遍历amount,比如找11,就可以找set里面有没有11,有的话返回1,没有的话,就去找1+10,2+9,3+8,4+7,5+6找出其中最小的,1+10就需要去找10的最小值,就是1+9 2+8 3+7 4+6 5+5,后面的结果可以由前面的结果推导出来,用动态规划
public class Solution {
public int coinChange(int[] coins, int amount) {
if(amount==0) return 0;
int dp[]=new int [amount+1];
dp[0]=0;
Set<Integer>set=new HashSet();
for(int i=0;i<coins.length;i++) set.add(coins[i]);
for(int i=1;i<=amount;i++)
{
dp[i]=Integer.MAX_VALUE;//因为要用Math.min,所以先把dp[i]默认置为最大值
if(set.contains(i)) dp[i]=1; //本来就有这个amount,就直接返回1
else {
for(int j=1;j<=i/2;j++)//比如计算5,就可以计算1+4和2+3,加到一般就不用往后面加了,后面的都是重复的,比如4+1和3+2
{
if(dp[j]!=-1&&dp[i-j]!=-1)
dp[i]=Math.min(dp[i],dp[j]+dp[i-j]);
}
if(dp[i]==Integer.MAX_VALUE) dp[i]=-1;//计算完之后,仍然没有合适的组合,则置为-1
}
}
return dp[amount];
}
}
别人的解法也太优秀了吧
public class Solution {
public int coinChange(int[] coins, int amount) {
if(coins.length == 0) return -1;
int[] dp = new int[amount + 1];
dp[0] = 0;
for(int i = 1;i <= amount;i++){
int min = Integer.MAX_VALUE;
for(int j = 0;j < coins.length;j++){
if(coins[j] <= i && dp[i - coins[j]] < min){
min = dp[i - coins[j]] + 1;
}
}
dp[i] = min;
}
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}
}
我思考了一下,我的超时写法不同之处在于,对于每一个amount,我的内部判断循环是 for(int j=1;j<=i/2;j++),对于每一个小于等于i/2的值都进行了判断,但是实际上,这些小于等于i/2的数如果没有出现在coins中,那么他们至少都是由2个硬币才能组成,而出现在coins中的,只需要一个硬币就能组成了
43 518. 零钱兑换 II
class Solution {
public int change(int amount, int[] coins) {
// dp[i][j] i代表到下标为i的硬币为止(不包括) amount为j的组合数
int [][] dp = new int [coins.length + 1][amount + 1];
for(int i=0;i<=coins.length;i++) dp[i][0]=1;
for (int i = 0; i < coins.length; ++i) {
// 要转移到的目标
for (int j = 1; j <= amount; ++j) {
// 不选择当前硬币
dp[i + 1][j] += dp[i][j];
if (j >= coins[i]) {
// 选择当前的硬币 则需要加上 j - coin 的组合数
// 因为可以重复选同一个 所以是从dp[i + 1]转移过来的(可以理解为选择当前硬币的时候对应的组合出j - coins[i],即dp[i + 1][j - coins[i]])
dp[i + 1][j] += dp[i + 1][j - coins[i]];
}
}
}
return dp[coins.length][amount];
}
}
一维的我实在是有点理解不了
class Solution {
public int change(int amount, int[] coins) {
int dp[] = new int[amount+1];
// 设置起始状态
dp[0] = 1;
for (int coin : coins) {
// 记录每添加一种面额的零钱,总金额j的变化
for (int j =coin; j <= amount; j++) {
// 在上一钟零钱状态的基础上增大
// 例如对于总额5,当只有面额为1的零钱时,只有一种可能 5x1
// 当加了面额为2的零钱时,除了原来的那一种可能外
// 还加上了组合了两块钱的情况,而总额为5是在总额为3的基础上加上两块钱来的
// 所以就加上此时总额为3的所有组合情况
dp[j] = dp[j] + dp[j - coin];
}
}
return dp[amount];
}
}
推荐阅读
零钱兑换II和爬楼梯问题到底有什么不同?
2021.08.02 (第21天)
44 377. 组合总和 Ⅳ
class Solution {
public int combinationSum4(int[] nums, int target) {
//这个题类似青蛙爬台阶,把target比作台阶数,那么nums中的元素就是青蛙每一次可以跳的步数
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(nums[j]<=i)
dp[i]+=dp[i-nums[j]];
}
}
return dp[target];
}
}
45 343. 整数拆分
class Solution {
public int integerBreak(int n) {
//思路,创建dp数组,dp[i]表示i拆分后的最大乘积
int dp[]=new int[n+1];
if(n==2) return 1;
dp[1]=1;dp[2]=1;
for(int i=3;i<=n;i++)
{
for(int j=1;j<=i/2;j++)
{
//比如3拆分成1 2 那么3拆分后的乘积最大值取决于1拆分和2拆分,2可以不拆,所以,即Math.max(dp[j],j),意思是2>=dp[2]的话就不拆,否则拆分
//4可以拆成1 3 ,2 2,其中3和2可以继续拆分,那么最大乘积就取决于是否继续拆分,看是拆分过后的乘积大,还是不拆分的乘积大
dp[i]=Math.max(dp[i],Math.max(dp[i-j],i-j)*Math.max(dp[j],j));
}
}
return dp[n];
}
}
46 279. 完全平方数
class Solution {
public int numSquares(int n) {
if(n==1) return 1;
//dp[i]代表i需要的最小完全平方数的个数
int dp[]=new int[n+1];
Arrays.fill(dp,Integer.MAX_VALUE);
dp[1]=1;
for(int i=2;i<=n;i++)
{
//首先判断这个数是不是完全平方数,如果是dp[i]=1
int a=(int)Math.sqrt(i);
if(a*a==i) dp[i]=1;
else
{
//如果这个数不是完全平方数,就把它拆分,取dp[i]和dp[j]+dp[i-j]中较小的那个
for(int j=1;j<=i/2;j++)
{
dp[i]=Math.min(dp[i],dp[j]+dp[i-j]);
}
}
}
return dp[n];
}
}
官方的题解就是这么优雅
class Solution {
public int numSquares(int n) {
int[] f = new int[n + 1];
for (int i = 1; i <= n; i++) {
int minn = Integer.MAX_VALUE;
for (int j = 1; j * j <= i; j++) {
minn = Math.min(minn, f[i - j * j]);
}
f[i] = minn + 1;
}
return f[n];
}
}
最后
以上就是友好砖头为你收集整理的力扣动态规划入门21天刷题计划(共计46题)推荐阅读2021.07.13(第1天)2021.07.14(第2天)2021.07.15(第3天)2021.07.16(第4天)2021.07.17(第5天)2021.07.18 (第6天)2021.07.19 (第7天)2021.07.20 (第8天)2021.07.21(第9天)2021.07.22(第10天)2021.07.23(第11天)2021.07.24(第12天)2021.07.25 (第13天)2021.07.26 (第14天)2021的全部内容,希望文章能够帮你解决力扣动态规划入门21天刷题计划(共计46题)推荐阅读2021.07.13(第1天)2021.07.14(第2天)2021.07.15(第3天)2021.07.16(第4天)2021.07.17(第5天)2021.07.18 (第6天)2021.07.19 (第7天)2021.07.20 (第8天)2021.07.21(第9天)2021.07.22(第10天)2021.07.23(第11天)2021.07.24(第12天)2021.07.25 (第13天)2021.07.26 (第14天)2021所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复