Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
youngyangyang04 committed Feb 18, 2023
1 parent b0c67e3 commit d231137
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 45 deletions.
61 changes: 36 additions & 25 deletions problems/0042.接雨水.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
* 动态规划
* 单调栈

## 双指针解法
## 暴力解法

这道题目使用双指针法并不简单,我们来看一下思路
本题暴力解法也是也是使用双指针

首先要明确,要按照行来计算,还是按照列来计算。

Expand Down Expand Up @@ -75,6 +75,7 @@
一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。

首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下:

```CPP
for (int i = 0; i < height.size(); i++) {
// 第一个柱子和最后一个柱子不接雨水
Expand Down Expand Up @@ -129,28 +130,25 @@ public:
};
```

因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2)。
空间复杂度为O(1)。

因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。

力扣后面修改了后台测试数据,所以以上暴力解法超时了。

## 双指针优化

## 动态规划解法

在上一节的双指针解法中,我们可以看到只要记录左边柱子的最高高度 和 右边柱子的最高高度,就可以计算当前位置的雨水面积,这就是通过列来计算。
在暴力解法中,我们可以看到只要记录左边柱子的最高高度 和 右边柱子的最高高度,就可以计算当前位置的雨水面积,这就是通过列来计算。

当前列雨水面积:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度。

为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)。这样就避免了重复计算,这就用到了动态规划
为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight),这样就避免了重复计算

当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。

即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);

从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);

这样就找到递推公式。

代码如下:

```CPP
Expand Down Expand Up @@ -185,10 +183,13 @@ public:

## 单调栈解法

这个解法可以说是最不好理解的了,所以下面我花了大量的篇幅来介绍这种方法。
关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)

单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。

而接雨水这道题目,我们正需要寻找一个元素,右边最大元素以及左边最大元素,来计算雨水面积。

### 准备工作

Expand All @@ -212,6 +213,7 @@ public:

![42.接雨水4](https://img-blog.csdnimg.cn/2021022309321229.png)

关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。

3. 遇到相同高度的柱子怎么办。

Expand All @@ -227,13 +229,13 @@ public:

4. 栈里要保存什么数值

是用单调栈,其实是通过 长 * 宽 来计算雨水面积的。
使用单调栈,也是通过 长 * 宽 来计算雨水面积的。

长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算,

那么栈里有没有必要存一个pair<int, int>类型的元素,保存柱子的高度和下标呢。

其实不用,栈里就存放int类型的元素就行了,表示下标,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。
其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。

所以栈的定义如下:

Expand All @@ -243,9 +245,17 @@ stack<int> st; // 存着下标,计算的时候用下标对应的柱子高度

明确了如上几点,我们再来看处理逻辑。

### 单调栈处理逻辑
### 单调栈处理逻辑

以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)

先将下标0的柱子加入到栈中,`st.push(0);`
以下逻辑主要就是三种情况

* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()]
* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()]
* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()]

先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。

然后开始从下标1开始遍历所有的柱子,`for (int i = 1; i < height.size(); i++)`

Expand Down Expand Up @@ -278,7 +288,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况

当前遍历的元素i,就是凹槽右边的位置,下标为i,对应的高度为height[i](就是图中的高度3)。

此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的三个元素来接水**
此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水**

那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];`

Expand Down Expand Up @@ -367,7 +377,7 @@ public:

### Java:

双指针法
暴力解法:
```java
class Solution {
public int trap(int[] height) {
Expand All @@ -393,7 +403,7 @@ class Solution {
}
```

动态规划法
双指针:
```java
class Solution {
public int trap(int[] height) {
Expand Down Expand Up @@ -470,7 +480,7 @@ class Solution {

### Python:

双指针法
暴力解法:
```Python
class Solution:
def trap(self, height: List[int]) -> int:
Expand All @@ -490,7 +500,8 @@ class Solution:
res += res1
return res
```
动态规划

双指针:
```python
class Solution:
def trap(self, height: List[int]) -> int:
Expand Down Expand Up @@ -602,7 +613,7 @@ func trap(height []int) int {
}
```

动态规划解法
双指针解法

```go
func trap(height []int) int {
Expand Down Expand Up @@ -681,7 +692,7 @@ func min(x, y int) int {
### JavaScript:

```javascript
//双指针
//暴力解法
var trap = function(height) {
const len = height.length;
let sum = 0;
Expand All @@ -702,7 +713,7 @@ var trap = function(height) {
return sum;
};

//动态规划
//双指针
var trap = function(height) {
const len = height.length;
if(len <= 2) return 0;
Expand Down Expand Up @@ -782,7 +793,7 @@ var trap = function(height) {

### TypeScript

双指针法
暴力解法

```typescript
function trap(height: number[]): number {
Expand All @@ -809,7 +820,7 @@ function trap(height: number[]): number {
};
```

动态规划
双指针

```typescript
function trap(height: number[]): number {
Expand Down
6 changes: 6 additions & 0 deletions problems/0209.长度最小的子数组.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

提示:

* 1 <= target <= 10^9
* 1 <= nums.length <= 10^5
* 1 <= nums[i] <= 10^5

# 思路

为了易于大家理解,我特意录制了B站视频[拿下滑动窗口! | LeetCode 209 长度最小的子数组](https://www.bilibili.com/video/BV1tZ4y1q7XE),结合视频看本题解,事半功倍!
Expand Down
6 changes: 3 additions & 3 deletions problems/0496.下一个更大元素I.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ for (int i = 0; i < nums1.size(); i++) {

栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。

可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。其实递减栈就是求右边第一个比自己小的元素了。
可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。**其实递减栈就是求右边第一个比自己小的元素了**


接下来就要分析如下三种情况,一定要分析清楚。
Expand All @@ -101,7 +101,7 @@ for (int i = 0; i < nums1.size(); i++) {

判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。

记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2中右面第一个大的元素是nums2[i]即当前遍历元素。
记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2数组中右面第一个大的元素是nums2[i]即当前遍历元素

代码如下:

Expand All @@ -116,7 +116,7 @@ while (!st.empty() && nums2[i] > nums2[st.top()]) {
st.push(i);
```

以上分析完毕,C++代码如下:
以上分析完毕,C++代码如下:(其实本题代码和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 是基本差不多的)


```CPP
Expand Down
55 changes: 47 additions & 8 deletions problems/0503.下一个更大元素II.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
* 输出: [2,-1,2]
* 解释: 第一个 1 的下一个更大的数是 2;数字 2 找不到下一个更大的数;第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

提示:

* 1 <= nums.length <= 10^4
* -10^9 <= nums[i] <= 10^9


# 思路

做本题之前建议先做[739. 每日温度](https://programmercarl.com/0739.每日温度.html)[496.下一个更大元素 I](https://programmercarl.com/0496.下一个更大元素I.html)

这道题和[739. 每日温度](https://programmercarl.com/0739.每日温度.html)也几乎如出一辙。

不同的时候本题要循环数组了
不过,本题要循环数组了

关于单调栈的讲解我在题解[739. 每日温度](https://programmercarl.com/0739.每日温度.html)中已经详细讲解了。

Expand All @@ -33,7 +38,7 @@

确实可以!

讲两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。
将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了。

代码如下:

Expand All @@ -51,12 +56,17 @@ public:

// 开始单调栈
stack<int> st;
for (int i = 0; i < nums.size(); i++) {
while (!st.empty() && nums[i] > nums[st.top()]) {
result[st.top()] = nums[i];
st.pop();
st.push(0);
for (int i = 1; i < nums.size(); i++) {
if (nums[i] < nums[st.top()]) st.push(i);
else if (nums[i] == nums[st.top()]) st.push(i);
else {
while (!st.empty() && nums[i] > nums[st.top()]) {
result[st.top()] = nums[i];
st.pop();
}
st.push(i);
}
st.push(i);
}
// 最后再把结果集即result数组resize到原数组大小
result.resize(nums.size() / 2);
Expand All @@ -74,6 +84,36 @@ resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了

代码如下:

```CPP
// 版本二
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> result(nums.size(), -1);
if (nums.size() == 0) return result;
stack<int> st;
st.push(0);
for (int i = 1; i < nums.size() * 2; i++) {
// 模拟遍历两边nums,注意一下都是用i % nums.size()来操作
if (nums[i % nums.size()] < nums[st.top()]) st.push(i % nums.size());
else if (nums[i % nums.size()] == nums[st.top()]) st.push(i % nums.size());
else {
while (!st.empty() && nums[i % nums.size()] > nums[st.top()]) {
result[st.top()] = nums[i % nums.size()];
st.pop();
}
st.push(i % nums.size());
}
}
return result;
}
};
```
可以版本二不仅代码精简了,也比版本一少做了无用功!
最后在给出 单调栈的精简版本,即三种情况都做了合并的操作。
```CPP
// 版本二
class Solution {
Expand All @@ -95,7 +135,6 @@ public:
};
```

可以版本二不仅代码精简了,也比版本一少做了无用功!

## 其他语言版本

Expand Down
2 changes: 1 addition & 1 deletion problems/0714.买卖股票的最佳时机含手续费.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public:
// 计算利润,可能有多次计算利润,最后一次计算利润才是真正意义的卖出
if (prices[i] > minPrice + fee) {
result += prices[i] - minPrice - fee;
minPrice = prices[i] - fee; // 情况一,这一步很关键
minPrice = prices[i] - fee; // 情况一,这一步很关键,避免重复扣手续费
}
}
return result;
Expand Down
Loading

0 comments on commit d231137

Please sign in to comment.