Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

@tab <1>

![二分查找算法 1](https://qcdn.itcharge.cn/images/20230906133729.png)
![二分查找算法 1](https://qcdn.itcharge.cn/images/20230907144632.png)

@tab <2>

Expand Down
128 changes: 79 additions & 49 deletions Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以将滑动窗口看做是快慢指针的一种特殊形式。

![滑动窗口](https://qcdn.itcharge.cn/images/20230907105115.png)

## 2. 滑动窗口适用范围

滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。
Expand All @@ -23,21 +25,25 @@

下面来分别讲解一下这两种类型题目。

## 3. 固定长度窗口
## 3. 固定长度滑动窗口

> **固定长度滑动窗口算法(Fixed Length Sliding Window)**:在给定数组 / 字符串上维护一个固定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。

![固定长度滑动窗口](https://qcdn.itcharge.cn/images/20230907110356.png)

### 3.1 固定长度窗口求解步骤
### 3.1 固定长度滑动窗口算法步骤

假设窗口的固定大小为 `window_size`
假设窗口的固定大小为 $window\underline{}size$

1. 使用两个指针 `left`、`right`。初始时,`left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0` ,区间 `[left, right]` 被称为一个「窗口」。
2. 当窗口未达到 `window_size` 大小时,不断移动 `right`,先将 `window_size` 个元素填入窗口中。
2. 当窗口达到 `window_size` 大小时,判断窗口内的连续元素是否满足题目限定的条件。
1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。
2. 当窗口未达到 $window\underline{}size$ 大小时,不断移动 $right$,先将数组前 $window\underline{}size$ 个元素填入窗口中,即 `window.append(nums[right])`
2. 当窗口达到 $window\underline{}size$ 大小时,即满足 `right - left + 1 >= window_size` ,判断窗口内的连续元素是否满足题目限定的条件。
1. 如果满足,再根据要求更新最优解。
2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `window_size`
3. 向右移动 `right`,将元素填入窗口中。
4. 重复 2 ~ 4 步,直到 `right` 到达序列末尾
2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $window\underline{}size$
3. 向右移动 $right$,将元素填入窗口中,即 `window.append(nums[right])`
4. 重复 $2 \sim 4$ 步,直到 $right$ 到达数组末尾

### 3.2 固定长度窗口模板
### 3.2 固定长度滑动窗口代码模板

```python
left = 0
Expand Down Expand Up @@ -66,9 +72,9 @@ while right < len(nums):

#### 3.3.2 题目大意

**描述**:给定一个整数数组 `arr` 和两个整数 `k``threshold`
**描述**:给定一个整数数组 $arr$ 和两个整数 $k$$threshold$

**要求**:返回长度为 `k` 且平均值大于等于 `threshold` 的子数组数目。
**要求**:返回长度为 $k$ 且平均值大于等于 $threshold$ 的子数组数目。

**说明**:

Expand All @@ -79,12 +85,17 @@ while right < len(nums):

**示例**:

- 示例 1:

```python
输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4
输出:3
解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。
```

- 示例 2:

```python
输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5
输出:6
解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。
Expand All @@ -94,15 +105,15 @@ while right < len(nums):

##### 思路 1:滑动窗口(固定长度)

这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 `k`。具体做法如下:
这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 $k$。具体做法如下:

1. `ans` 用来维护答案数目。`window_sum` 用来维护窗口中元素的和。
2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`
3. 向右移动 `right`,先将 `k` 个元素填入窗口中。
4. 当窗口元素个数为 `k` 时,即:`right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 `threshold`
1. 如果满足,则答案数目 + 1
2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`
5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。
1. $ans$ 用来维护答案数目。$window\underline{}sum$ 用来维护窗口中元素的和。
2. $left$ 、$right$ 都指向序列的第一个元素,即:$left = 0$,$right = 0$
3. 向右移动 $right$,先将 $k$ 个元素填入窗口中,即 `window_sum += arr[right]`
4. 当窗口元素个数为 $k$ 时,即满足 `right - left + 1 >= k` 时,判断窗口内的元素和平均值是否大于等于阈值 $threshold$
1. 如果满足,则答案数目加 $1$
2. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 $k$
5. 重复 $3 \sim 4$ 步,直到 $right$ 到达数组末尾。
6. 最后输出答案数目。

##### 思路 1:代码
Expand Down Expand Up @@ -134,18 +145,22 @@ class Solution:
- **时间复杂度**:$O(n)$。
- **空间复杂度**:$O(n)$。

## 4. 不定长度窗口
## 4. 不定长度滑动窗口

> **不定长度滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。

### 4.1 不定长度窗口求解步骤
![不定长度滑动窗口](https://qcdn.itcharge.cn/images/20230907132630.png)

1. 使用两个指针 `left`、`right`。初始时,`left`、`right` 都指向序列的第一个元素。即:`left = 0`,`right = 0`,区间 `[left, right]` 被称为一个「窗口」。
### 4.1 不定长度滑动窗口算法步骤

1. 使用两个指针 $left$、$right$。初始时,$left$、$right$ 都指向序列的第一个元素。即:$left = 0$,$right = 0$,区间 $[left, right]$ 被称为一个「窗口」。
2. 将区间最右侧元素添加入窗口中,即 `window.add(s[right])`。
3. 然后向右移动 `right`,从而增大窗口长度,即 `right += 1`。直到窗口中的连续元素满足要求。
3. 然后向右移动 $right$,从而增大窗口长度,即 `right += 1`。直到窗口中的连续元素满足要求。
4. 此时,停止增加窗口大小。转向不断将左侧元素移出窗口,即 `window.popleft(s[left])`。
5. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`。直到窗口中的连续元素不再满足要求。
6. 重复 2 ~ 5 步,直到 `right` 到达序列末尾。
5. 然后向右移动 $left$,从而缩小窗口长度,即 `left += 1`。直到窗口中的连续元素不再满足要求。
6. 重复 2 ~ 5 步,直到 $right$ 到达序列末尾。

### 4.2 不定长度窗口模板
### 4.2 不定长度滑动窗口代码模板

```python
left = 0
Expand All @@ -171,23 +186,28 @@ while right < len(nums):

#### 4.3.2 题目大意

**描述**:给定一个字符串 `s`
**描述**:给定一个字符串 $s$

**要求**:找出其中不含有重复字符的最长子串的长度。

**说明**:

- $0 \le s.length \le 5 * 10^4$。
- `s` 由英文字母、数字、符号和空格组成。
- $s$ 由英文字母、数字、符号和空格组成。

**示例**:

- 示例 1:

```python
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
```

- 示例 2:

```python
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
Expand All @@ -197,13 +217,13 @@ while right < len(nums):

##### 思路 1:滑动窗口(不定长度)

用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。
用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。

1. 设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。
2. 一开始,`left`、`right` 都指向 `0`
3. 向右移动 `right`,将最右侧字符 `s[right]` 加入当前窗口 `window` 中,记录该字符个数。
4. 如果该窗口中该字符的个数多于 1 个,即 `window[s[right]] > 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `window[s[right]] <= 1`
5. 维护更新无重复字符的最长子串长度。然后继续右移 `right`,直到 `right >= len(nums)` 结束。
1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。
2. 一开始,$left$、$right$ 都指向 $0$
3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。
4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$
5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。
6. 输出无重复字符的最长子串长度。

##### 思路 1:代码
Expand Down Expand Up @@ -245,9 +265,9 @@ class Solution:

#### 4.4.2 题目大意

**描述**:给定一个只包含正整数的数组 `nums` 和一个正整数 `target`
**描述**:给定一个只包含正整数的数组 $nums$ 和一个正整数 $target$

**要求**:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`
**要求**:找出数组中满足和大于等于 $target$ 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 $0$

**说明**:

Expand All @@ -257,12 +277,17 @@ class Solution:

**示例**:

- 示例 1:

```python
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
```

- 示例 2:

```python
输入:target = 4, nums = [1,4,4]
输出:1
```
Expand All @@ -273,12 +298,12 @@ class Solution:

最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。

用滑动窗口来记录连续子数组的和,设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 `target`
用滑动窗口来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 $target$

1. 一开始,`left`、`right` 都指向 `0`
2. 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。
3. 如果 `window_sum >= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`
4. 然后继续右移 `right`,直到 `right >= len(nums)` 结束。
1. 一开始,$left$、$right$ 都指向 $0$
2. 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{}sum$ 中。
3. 如果 $window\underline{}sum \ge target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{}sum < target$
4. 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。
5. 输出窗口和的最小值作为答案。

##### 思路 1:代码
Expand Down Expand Up @@ -318,9 +343,9 @@ class Solution:

#### 4.5.2 题目大意

**描述**:给定一个正整数数组 `nums`和整数 `k`
**描述**:给定一个正整数数组 $nums$ 和整数 $k$

**要求**:找出该数组内乘积小于 `k` 的连续的子数组的个数。
**要求**:找出该数组内乘积小于 $k$ 的连续的子数组的个数。

**说明**:

Expand All @@ -330,12 +355,17 @@ class Solution:

**示例**:

- 示例 1:

```python
输入:nums = [10,5,2,6], k = 100
输出:8
解释:8 个乘积小于 100 的子数组分别为:[10]、[5]、[2],、[6]、[10,5]、[5,2]、[2,6]、[5,2,6]。需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。
```

- 示例 2:

```python
输入:nums = [1,2,3], k = 0
输出:0
```
Expand All @@ -344,11 +374,11 @@ class Solution:

##### 思路 1:滑动窗口(不定长度)

1. 设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 `window_product` 都小于 `k`。使用 `window_product` 记录窗口中的乘积值,使用 `count` 记录符合要求的子数组个数。
2. 一开始,`left`、`right` 都指向 `0`
3. 向右移动 `right`,将最右侧元素加入当前子数组乘积 `window_product` 中。
4. 如果 `window_product >= k` ,则不断右移 `left`,缩小滑动窗口长度,并更新当前乘积值 `window_product` 直到 `window_product < k`
5. 记录累积答案个数加 `1`,继续右移 `right`,直到 `right >= len(nums)` 结束。
1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 $window\underline{}product$ 都小于 $k$。使用 $window\underline{}product$ 记录窗口中的乘积值,使用 $count$ 记录符合要求的子数组个数。
2. 一开始,$left$、$right$ 都指向 $0$
3. 向右移动 $right$,将最右侧元素加入当前子数组乘积 $window\underline{}product$ 中。
4. 如果 $window\underline{}product \ge k$,则不断右移 $left$,缩小滑动窗口长度,并更新当前乘积值 $window\underline{}product$ 直到 $window\underline{}product < k$
5. 记录累积答案个数加 $1$,继续右移 $right$,直到 $right \ge len(nums)$ 结束。
6. 输出累积答案个数。

##### 思路 1:代码
Expand Down
15 changes: 7 additions & 8 deletions Solutions/0003. 无重复字符的最长子串.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## 题目大意

**描述**:给定一个字符串 `s`
**描述**:给定一个字符串 $s$

**要求**:找出其中不含有重复字符的最长子串的长度。

Expand Down Expand Up @@ -36,13 +36,13 @@

### 思路 1:滑动窗口(不定长度)

用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。
用滑动窗口 $window$ 来记录不重复的字符个数,$window$ 为哈希表类型。

1. 设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。
2. 一开始,`left`、`right` 都指向 `0`
3. 向右移动 `right`,将最右侧字符 `s[right]` 加入当前窗口 `window` 中,记录该字符个数。
4. 如果该窗口中该字符的个数多于 1 个,即 `window[s[right]] > 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `window[s[right]] <= 1`
5. 维护更新无重复字符的最长子串长度。然后继续右移 `right`,直到 `right >= len(nums)` 结束。
1. 设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。
2. 一开始,$left$、$right$ 都指向 $0$
3. 向右移动 $right$,将最右侧字符 $s[right]$ 加入当前窗口 $window$ 中,记录该字符个数。
4. 如果该窗口中该字符的个数多于 $1$ 个,即 $window[s[right]] > 1$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 $window[s[right]] \le 1$
5. 维护更新无重复字符的最长子串长度。然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。
6. 输出无重复字符的最长子串长度。

### 思路 1:代码
Expand Down Expand Up @@ -75,4 +75,3 @@ class Solution:

- **时间复杂度**:$O(n)$。
- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。

14 changes: 7 additions & 7 deletions Solutions/0209. 长度最小的子数组.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

## 题目大意

**描述**:给定一个只包含正整数的数组 `nums` 和一个正整数 `target`
**描述**:给定一个只包含正整数的数组 $nums$ 和一个正整数 $target$

**要求**:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`
**要求**:找出数组中满足和大于等于 $target$ 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 $0$

**说明**:

Expand Down Expand Up @@ -38,12 +38,12 @@

最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。

用滑动窗口来记录连续子数组的和,设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 `target`
用滑动窗口来记录连续子数组的和,设定两个指针:$left$、$right$,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 $target$

1. 一开始,`left`、`right` 都指向 `0`
2. 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。
3. 如果 `window_sum >= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`
4. 然后继续右移 `right`,直到 `right >= len(nums)` 结束。
1. 一开始,$left$、$right$ 都指向 $0$
2. 向右移动 $right$,将最右侧元素加入当前窗口和 $window\underline{}sum$ 中。
3. 如果 $window\underline{}sum \ge target$,则不断右移 $left$,缩小滑动窗口长度,并更新窗口和的最小值,直到 $window\underline{}sum < target$
4. 然后继续右移 $right$,直到 $right \ge len(nums)$ 结束。
5. 输出窗口和的最小值作为答案。

### 思路 1:代码
Expand Down
Loading