diff --git a/README.md b/README.md index 80a37391..aae26979 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ * [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw) * [深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/Yzrkim-5bY0Df66Ao-hoqA) * [北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/FQTzoZtqXQ2rlS1UthGrag) + * [上海有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/msqbX6eR2-JBQOYFfec4sg) * 算法性能分析 * [究竟什么是时间复杂度,怎么求时间复杂度,看这一篇就够了](https://mp.weixin.qq.com/s/lYL9TSxLqCeFXIdjt4dcIw) @@ -123,8 +124,7 @@ * [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA) * [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg) * [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) - * [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) - * [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg) + * [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) * [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg) * [本周小结!(二叉树)](https://mp.weixin.qq.com/s/JWmTeC7aKbBfGx4TY6uwuQ) * [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) * [二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg) @@ -175,6 +175,7 @@ * [本周小结!(回溯算法系列三)](https://mp.weixin.qq.com/s/tLkt9PSo42X60w8i94ViiA) * [本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag) * [视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg) + * [视频来了!!回溯算法:组合问题](https://mp.weixin.qq.com/s/a_r5JR93K_rBKSFplPGNAA) * [回溯算法:重新安排行程](https://mp.weixin.qq.com/s/3kmbS4qDsa6bkyxR92XCTA) * [回溯算法:N皇后问题](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) * [回溯算法:解数独](https://mp.weixin.qq.com/s/eWE9TapVwm77yW9Q81xSZQ) @@ -315,6 +316,7 @@ |[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| |[0455.分发饼干](https://github.com/youngyangyang04/leetcode/blob/master/problems/0455.分发饼干.md) |贪心 |简单| **贪心**| |[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| +|[0474.一和零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0474.一和零.md) |动态规划 |中等| **多重背包** 好题目| |[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**| |[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法** 这个去重有意思| |[0496.下一个更大元素I](https://github.com/youngyangyang04/leetcode/blob/master/problems/0496.下一个更大元素I.md) |栈 |中等|**单调栈** 入门题目,但是两个数组还是有点绕的| diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index 09486475..3e3f5881 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -1,23 +1,79 @@ -## 链接 -https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ +> 贪心有时候比动态规划更巧妙,更好用! -## 思路 +# 122.买卖股票的最佳时机II -首先要知道第0天买入,第3天卖出的利润:prices[3] - prices[0],相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + ()prices[1] - prices[0]) +题目链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/ + +给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 + +设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + +注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + + +示例 1: +输入: [7,1,5,3,6,4] +输出: 7 +解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 + +示例 2: +输入: [1,2,3,4,5] +输出: 4 +解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 + +示例 3: +输入: [7,6,4,3,1] +输出: 0 +解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 + +提示: +* 1 <= prices.length <= 3 * 10 ^ 4 +* 0 <= prices[i] <= 10 ^ 4 + +# 思路 + +本题首先要清楚两点: + +* 只有一只股票! +* 当前只有买股票或者买股票的操作 + +想获得利润至少要两天为一个交易单元。 + +## 贪心算法 + +这道题目可能我们只会想,选一个低的买入,在选个高的卖,在选一个低的买入.....循环反复。 + +**如果想到其实最终利润是可以分解的,那么本题就很容易了!** + +如果分解呢? + +假如第0天买入,第3天卖出,那么利润为:prices[3] - prices[0]。 + +相当于(prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])。 + +**此时就是把利润分解为每天为单位的维度,而不是从0天到第3天整体去考虑!** 那么根据prices可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])。 -可以发现,我们需要收集每天的正利润就可以,收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润就可以了,不需要记录区间。 - -**这就是贪心所贪的地方,只收集正利润**。 - 如图: - +![122.买卖股票的最佳时机II](https://img-blog.csdnimg.cn/2020112917480858.png) + +一些同学陷入:第一天怎么就没有利润呢,第一天到底算不算的困惑中。 + +第一天当然没有利润,至少要第二天才会有利润,所以利润的序列比股票序列少一天! + +从图中可以发现,其实我们需要收集每天的正利润就可以,**收集正利润的区间,就是股票买卖的区间,而我们只需要关注最终利润,不需要记录区间**。 + +那么只收集正利润就是贪心所贪的地方! + +**局部最优:收集每天的正利润,全局最优:求得最大利润**。 + +局部最优可以推出全局最优,找不出反例,试一试贪心! 对应C++代码如下: -``` +```C++ class Solution { public: int maxProfit(vector& prices) { @@ -29,6 +85,47 @@ public: } }; ``` +* 时间复杂度O(n) +* 空间复杂度O(1) + +## 动态规划 + +动态规划将在下一个系列详细讲解,本题解先给出我的C++代码(带详细注释),感兴趣的同学可以自己先学习一下。 + +```C++ +class Solution { +public: + int maxProfit(vector& prices) { + // dp[i][1]第i天持有的最多现金 + // dp[i][0]第i天持有股票后的最多现金 + int n = prices.size(); + vector> dp(n, vector(2, 0)); + dp[0][0] -= prices[0]; // 持股票 + for (int i = 1; i < n; i++) { + // 第i天持股票所剩最多现金 = max(第i-1天持股票所剩现金, 第i-1天持现金-买第i天的股票) + dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); + // 第i天持有最多现金 = max(第i-1天持有的最多现金,第i-1天持有股票的最多现金+第i天卖出股票) + dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]); + } + return max(dp[n - 1][0], dp[n - 1][1]); + } +}; +``` +* 时间复杂度O(n) +* 空间复杂度O(n) + +# 总结 + +股票问题其实是一个系列的,属于动态规划的范畴,因为目前在讲解贪心系列,所以股票问题会在之后的动态规划系列中详细讲解。 + +**可以看出有时候,贪心往往比动态规划更巧妙,更好用,所以别小看了贪心算法**。 + +**本题中理解利润拆分是关键点!** 不要整块的去看,而是把整体利润拆为每天的利润。 + +一旦想到这里了,很自然就会想到贪心了,即:只收集每天的正利润,最后稳稳的就是最大利润了。 + +就酱,「代码随想录」是技术公众号里的一抹清流,值得推荐给你的朋友同学们! + > 我是[程序员Carl](https://github.com/youngyangyang04),组队刷题可以找我,本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已收录,更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),期待你的关注! diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index b0a3668e..5582c8ac 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -1,4 +1,5 @@ +## 思路 * dp[i]的定义 dp[i]表示i之前包括i的最长上升子序列。 @@ -11,6 +12,7 @@ dp[i]表示i之前包括i的最长上升子序列。 * 状态转移方程 +位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。 if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md new file mode 100644 index 00000000..ba154584 --- /dev/null +++ b/problems/0474.一和零.md @@ -0,0 +1,32 @@ + +搞不懂 leetcode后台是什么牛逼的编译器,初始化int dp[101][101] = {0}; 可以 ,int dp[101][101];就不行,有其他默认值,坑死。 +代码我做了实验,后台会拿findMaxForm,运行两次,取第二次的结果,dp有上次记录的数值。 + +``` +// 即使做了很多动态规划的题目,做这个依然懵逼 +// 这道题目有点 程序员自己给自己出难进急转弯的意思 +// 该子集中 最多 有 m 个 0 和 n 个 1 。 指的是整体子集 +// 这是二维背包,多重背包 +// dp[i][j] 有i个0,j个1最大有多少个子集,但是遍历的时候 顶部是哪里呢? +class Solution { +public: + int findMaxForm(vector& strs, int m, int n) { + int dp[101][101] = {0}; // 默认初始化0 + for (int i = 0; i < strs.size(); i++) { + int oneNum = 0, zeroNum = 0; + for (char c : strs[i]) { + if (c == '0') zeroNum++; + else oneNum++; + } + // 果然还是从后向前,模拟01背包 + for (int j = m; j >= zeroNum; j--) { + for (int k = n; k >= oneNum; k--) { + dp[j][k] = max(dp[j][k], dp[j - zeroNum][k - oneNum] + 1); + } + } + } + return dp[m][n]; + } +}; + +```