Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
youngyangyang04 committed Dec 20, 2022
1 parent 5fc3187 commit 77f1c52
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 292 deletions.
24 changes: 24 additions & 0 deletions problems/0018.四数之和.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ public:

```

## 补充

二级剪枝的部分:

```C++
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
```

可以优化为:

```C++
if (nums[k] + nums[i] > target && nums[i] >= 0) {
break;
}
```

因为只要 nums[k] + nums[i] > target,那么 nums[i] 后面的数都是正数的话,就一定 不符合条件了。

不过这种剪枝 其实有点 小绕,大家能够理解 文章给的完整代码的剪枝 就够了。






Expand Down
16 changes: 8 additions & 8 deletions problems/0121.买卖股票的最佳时机.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ public:
};
```
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
当然该方法超时了。
Expand All @@ -72,8 +72,8 @@ public:
};
```

* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n)
* 空间复杂度:O(1)

### 动态规划

Expand Down Expand Up @@ -157,8 +157,8 @@ public:
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
* 时间复杂度:O(n)
* 空间复杂度:O(n)
从递推公式可以看出,dp[i]只是依赖于dp[i - 1]的状态。
Expand Down Expand Up @@ -187,8 +187,8 @@ public:
};
```

* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n)
* 空间复杂度:O(1)

这里能写出版本一就可以了,版本二虽然原理都一样,但是想直接写出版本二还是有点麻烦,容易自己给自己找bug。

Expand Down
47 changes: 36 additions & 11 deletions problems/0123.买卖股票的最佳时机III.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,20 @@

一天一共就有五个状态,

0. 没有操作
1. 第一次买入
2. 第一次卖出
3. 第二次买入
4. 第二次卖出
0. 没有操作 (其实我们也可以不设置这个状态)
1. 第一次持有股票
2. 第一次不持有股票
3. 第二次持有股票
4. 第二次不持有股票

dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。

需要注意:dp[i][1]**表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区**

例如 dp[i][1] ,并不是说 第i点一定买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续买入股票的这个状态。

2. 确定递推公式

需要注意:dp[i][1]**表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区**

达到dp[i][1]状态,有两个具体操作:

Expand Down Expand Up @@ -95,11 +98,7 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);

第0天做第一次卖出的操作,这个初始值应该是多少呢?

首先卖出的操作一定是收获利润,整个股票买卖最差情况也就是没有盈利即全程无操作现金为0,

从递推公式中可以看出每次是取最大值,那么既然是收获利润如果比0还小了就没有必要收获这个利润了。

所以dp[0][2] = 0;
此时还没有买入,怎么就卖出呢? 其实大家可以理解当天买入,当天卖出,所以dp[0][2] = 0;

第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢?

Expand Down Expand Up @@ -188,6 +187,32 @@ dp[1] = max(dp[1], dp[0] - prices[i]); 如果dp[1]取dp[1],即保持买入股

对于本题,把版本一的写法研究明白,足以!

## 拓展

其实我们可以不设置,‘0. 没有操作’ 这个状态,因为没有操作,手上的现金自然就是0, 正如我们在 [121.买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)[122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html) 也没有设置这一状态是一样的。

代码如下:

``` CPP
// 版本三
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[i][1] = max(dp[i - 1][1], 0 - prices[i]);
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[prices.size() - 1][4];
}
};
```
## 其他语言版本
Java:
Expand Down
12 changes: 5 additions & 7 deletions problems/0188.买卖股票的最佳时机IV.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,15 @@ for (int j = 0; j < 2 * k - 1; j += 2) {

第0天做第一次卖出的操作,这个初始值应该是多少呢?

首先卖出的操作一定是收获利润,整个股票买卖最差情况也就是没有盈利即全程无操作现金为0,
此时还没有买入,怎么就卖出呢? 其实大家可以理解当天买入,当天卖出,所以dp[0][2] = 0;

从递推公式中可以看出每次是取最大值,那么既然是收获利润如果比0还小了就没有必要收获这个利润了。
第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢?

所以dp[0][2] = 0;
第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后在买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。

第0天第二次买入操作,初始值应该是多少呢?
所以第二次买入操作,初始化为:dp[0][3] = -prices[0];

不用管第几次,现在手头上没有现金,只要买入,现金就做相应的减少。

第二次买入操作,初始化为:dp[0][3] = -prices[0];
第二次卖出初始化dp[0][4] = 0;

**所以同理可以推出dp[0][j]当j为奇数的时候都初始化为 -prices[0]**

Expand Down
9 changes: 6 additions & 3 deletions problems/0200.岛屿数量.深搜版.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,9 @@ public:
很多录友可能有疑惑,为什么 以上代码中的dfs函数,没有终止条件呢? 感觉递归没有终止很危险。
其实终止条件 就写在了调用dfs的地方,如果遇到不合法的方向,直接不会去调用dfs。
其实终止条件 就写在了 调用dfs的地方,如果遇到不合法的方向,直接不会去调用dfs。
当然,也可以这么写
当然也可以这么写
```CPP
// 版本二
Expand Down Expand Up @@ -122,7 +122,7 @@ public:
};
```

这里大家应该能看出区别了,无疑就是版本一中 调用dfs 的条件,放在了 版本二 的 终止条件位置上。
这里大家应该能看出区别了,无疑就是版本一中 调用dfs 的条件判断 放在了 版本二 的 终止条件位置上。

**版本一的写法**是 :下一个节点是否能合法已经判断完了,只要调用dfs就是可以合法的节点。

Expand All @@ -137,6 +137,9 @@ public:

其实本题是 dfs,bfs 模板题,但正是因为是模板题,所以大家或者一些题解把重要的细节都很忽略了,我这里把大家没注意的但以后会踩的坑 都给列出来了。

本篇我只给出的dfs的写法,大家发现我写的还是比较细的,那么后面我再单独更本题的bfs写法,虽然是模板题,但依然有很多注意的点,敬请期待!





Expand Down
5 changes: 5 additions & 0 deletions problems/0235.二叉搜索树的最近公共祖先.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
* 所有节点的值都是唯一的。
* p、q 为不同节点且均存在于给定的二叉搜索树中。

# 视频讲解

**《代码随想录》算法视频公开课:[二叉搜索树找祖先就有点不一样了!| 235. 二叉搜索树的最近公共祖先](https://www.bilibili.com/video/BV1Zt4y1F7ww),相信结合视频再看本篇题解,更有助于大家对本题的理解**


# 思路


Expand Down
36 changes: 24 additions & 12 deletions problems/0300.最长上升子序列.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@


示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
* 输入:nums = [10,9,2,5,3,7,101,18]
* 输出:4
* 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
* 输入:nums = [0,1,0,3,2,3]
* 输出:4

示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
* 输入:nums = [7,7,7,7,7,7,7]
* 输出:1

提示:

Expand All @@ -33,11 +33,21 @@

## 思路

最长上升子序列是动规的经典题目,这里dp[i]是可以根据dp[j] (j < i)推导出来的,那么依然用动规五部曲来分析详细一波:
首先通过本题大家要明确什么是子序列,“子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序”。

本题也是代码随想录中子序列问题的第一题,如果没接触过这种题目的话,本题还是很难的,甚至想暴力去搜索也不知道怎么搜。
子序列问题是动态规划解决的经典问题,当前下标i的递增子序列长度,其实和i之前的下表j的子序列长度有关系,那那又是什么样的关系呢。

接下来,我们依然用动规五部曲来分析详细一波:

1. dp[i]的定义

**dp[i]表示i之前包括i的以nums[i]结尾最长上升子序列的长度**
本题中,正确定义dp数组的含义十分重要。

**dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度**

为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在 做 递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如果算递增呢。


2. 状态转移方程

Expand All @@ -49,13 +59,15 @@

3. dp[i]的初始化

每一个i,对应的dp[i]即最长上升子序列)起始大小至少都是1.
每一个i,对应的dp[i]即最长递增子序列)起始大小至少都是1.

4. 确定遍历顺序

dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。
dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。

j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯 从前向后遍历。

j其实就是0到i-1,遍历i的循环在外层,遍历j则在内层,代码如下:
遍历i的循环在外层,遍历j则在内层,代码如下:

```CPP
for (int i = 1; i < nums.size(); i++) {
Expand Down
Loading

0 comments on commit 77f1c52

Please sign in to comment.