diff --git a/Assets/Origins/Categories-List.md b/Assets/Origins/Categories-List.md index 85923981..ad71b1da 100644 --- a/Assets/Origins/Categories-List.md +++ b/Assets/Origins/Categories-List.md @@ -46,11 +46,11 @@ #### 桶排序题目 -###### 0912. 排序数组、0164. 最大间距 +###### 0912. 排序数组、0220. 存在重复元素 III、0164. 最大间距 #### 基数排序题目 -###### 0164. 最大间距、0561. 数组拆分 I +###### 0164. 最大间距、0561. 数组拆分 #### 其他排序题目 @@ -89,11 +89,11 @@ #### 固定长度窗口题目 -###### 1343. 大小为 K 且平均值大于等于阈值的子数组数目、0643. 子数组最大平均数 I、1052. 爱生气的书店老板、1423. 可获得的最大点数、1456. 定长子串中元音的最大数目、0567. 字符串的排列、1100. 长度为 K 的无重复字符子串、1151. 最少交换次数来组合所有的 1、1176. 健身计划评估、0438. 找到字符串中所有字母异位词、0995. K 连续位的最小翻转次数、0683. K 个关闭的灯泡、0239. 滑动窗口最大值、0480. 滑动窗口中位数 +###### 1343. 大小为 K 且平均值大于等于阈值的子数组数目、0643. 子数组最大平均数 I、1052. 爱生气的书店老板、1423. 可获得的最大点数、1456. 定长子串中元音的最大数目、0567. 字符串的排列、1100. 长度为 K 的无重复字符子串、1151. 最少交换次数来组合所有的 1、1176. 健身计划评估、0438. 找到字符串中所有字母异位词、0995. K 连续位的最小翻转次数、0683. K 个关闭的灯泡、0220. 存在重复元素 III、0239. 滑动窗口最大值、0480. 滑动窗口中位数 #### 不定长度窗口题目 -###### 0674. 最长连续递增序列、0485. 最大连续 1 的个数、0487. 最大连续1的个数 II、0076. 最小覆盖子串、0718. 最长重复子数组、0209. 长度最小的子数组、0862. 和至少为 K 的最短子数组、1004. 最大连续1的个数 III、1658. 将 x 减到 0 的最小操作数、0424. 替换后的最长重复字符、0003. 无重复字符的最长子串、1695. 删除子数组的最大得分、1208. 尽可能使字符串相等、1493. 删掉一个元素以后全为 1 的最长子数组、0727. 最小窗口子序列、0159. 至多包含两个不同字符的最长子串、0340. 至多包含 K 个不同字符的最长子串、0795. 区间子数组个数、0992. K 个不同整数的子数组、0713. 乘积小于K的子数组、0904. 水果成篮、1358. 包含所有三种字符的子字符串数目、0467. 环绕字符串中唯一的子字符串、0220. 存在重复元素 III、1438. 绝对差不超过限制的最长连续子数组 +###### 0674. 最长连续递增序列、0485. 最大连续 1 的个数、0487. 最大连续1的个数 II、0076. 最小覆盖子串、0718. 最长重复子数组、0209. 长度最小的子数组、0862. 和至少为 K 的最短子数组、1004. 最大连续1的个数 III、1658. 将 x 减到 0 的最小操作数、0424. 替换后的最长重复字符、0003. 无重复字符的最长子串、1695. 删除子数组的最大得分、1208. 尽可能使字符串相等、1493. 删掉一个元素以后全为 1 的最长子数组、0727. 最小窗口子序列、0159. 至多包含两个不同字符的最长子串、0340. 至多包含 K 个不同字符的最长子串、0795. 区间子数组个数、0992. K 个不同整数的子数组、0713. 乘积小于K的子数组、0904. 水果成篮、1358. 包含所有三种字符的子字符串数目、0467. 环绕字符串中唯一的子字符串、1438. 绝对差不超过限制的最长连续子数组 ## 02. 链表 @@ -251,7 +251,7 @@ ### [贪心算法题目](../../Contents/09.Algorithm-Base/05.Greedy-Algorithm/02.Greedy-Algorithm-List.md) -###### 0455. 分发饼干、0860. 柠檬水找零、0056. 合并区间、0435. 无重叠区间、0452. 用最少数量的箭引爆气球、0055. 跳跃游戏、0045. 跳跃游戏 II、0392. 判断子序列、0122. 买卖股票的最佳时机 II、0561. 数组拆分 I、1710. 卡车上的最大单元数、1217. 玩筹码、1247. 交换字符使得字符串相同、1400. 构造 K 个回文字符串、0921. 使括号有效的最少添加、1029. 两地调度、1605. 给定行和列的和求可行矩阵、0135. 分发糖果、0134. 加油站、0053. 最大子数组和、0376. 摆动序列、0738. 单调递增的数字、0402. 移掉 K 位数字、0861. 翻转矩阵后的得分、0670. 最大交换 +###### 0455. 分发饼干、0860. 柠檬水找零、0056. 合并区间、0435. 无重叠区间、0452. 用最少数量的箭引爆气球、0055. 跳跃游戏、0045. 跳跃游戏 II、0392. 判断子序列、0122. 买卖股票的最佳时机 II、0561. 数组拆分、1710. 卡车上的最大单元数、1217. 玩筹码、1247. 交换字符使得字符串相同、1400. 构造 K 个回文字符串、0921. 使括号有效的最少添加、1029. 两地调度、1605. 给定行和列的和求可行矩阵、0135. 分发糖果、0134. 加油站、0053. 最大子数组和、0376. 摆动序列、0738. 单调递增的数字、0402. 移掉 K 位数字、0861. 翻转矩阵后的得分、0670. 最大交换 ### [位运算题目](../../Contents/09.Algorithm-Base/06.Bit-Operation/02.Bit-Operation-List.md) diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md index d5c2d246..7208ac32 100644 --- a/Contents/00.Introduction/05.Categories-List.md +++ b/Contents/00.Introduction/05.Categories-List.md @@ -233,6 +233,7 @@ | 0438 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、滑动窗口 | 中等 | | 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、数组、前缀和、滑动窗口 | 困难 | | 0683 | [K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md) | 树状数组、数组、有序集合、滑动窗口 | 困难 | +| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 排序、有序集合、哈希表 | 中等 | | 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列,数组、滑动窗口、单调队列、堆(优先队列) | 困难 | | 0480 | [滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | @@ -263,7 +264,6 @@ | 0904 | [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md) | 数组、哈希表、滑动窗口 | 中等 | | 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动数组 | 中等 | | 0467 | [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 排序、有序集合、哈希表 | 中等 | | 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | ## 02. 链表 diff --git a/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md b/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md index e7ebedf2..e0474320 100644 --- a/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md +++ b/Contents/01.Array/04.Array-Two-Pointers/01.Array-Two-Pointers.md @@ -1,24 +1,23 @@ ## 1. 双指针简介 -> 双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞时针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 +> **双指针(Two Pointers)**:指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。 -在数组的区间问题上,暴力算法的时间复杂度往往是 $O(n^2)$。而双指针利用了区间「单调性」的性质,从而将时间复杂度降到了 $o(n)$。 +在数组的区间问题上,暴力算法的时间复杂度往往是 $O(n^2)$。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 $O(n)$。 ## 2. 对撞指针 -> 对撞指针:指的是两个指针 `left`、`right` 分别指向序列第一个元素和最后一个元素,然后 `left` 指针不断递增,`right` 不断递减,直到两个指针的值相撞(即 `left == right`),或者满足其他要求的特殊条件为止。 +> **对撞指针**:指的是两个指针 `left`、`right` 分别指向序列第一个元素和最后一个元素,然后 `left` 指针不断递增,`right` 不断递减,直到两个指针的值相撞(即 `left == right`),或者满足其他要求的特殊条件为止。 ### 2.1 对撞指针求解步骤 -- 使用两个指针 `left`,`right`。`left` 指向序列第一个元素,即:`left = 0`,`right` 指向序列最后一个元素,即:`right = len(nums) - 1`。 -- 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,`left += 1`。当满足另外一定条件时,将右指针左移,`right -= 1`。 -- 直到两指针相撞(即 `left == right`),或者满足其他要求的特殊条件时,跳出循环体。 +1. 使用两个指针 `left`,`right`。`left` 指向序列第一个元素,即:`left = 0`,`right` 指向序列最后一个元素,即:`right = len(nums) - 1`。 +2. 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,`left += 1`。当满足另外一定条件时,将右指针左移,`right -= 1`。 +3. 直到两指针相撞(即 `left == right`),或者满足其他要求的特殊条件时,跳出循环体。 ### 2.2 对撞指针伪代码模板 ```Python -left = 0 -right = len(nums) - 1 +left, right = 0, len(nums) - 1 while left < right: if 满足要求的特殊条件: @@ -28,7 +27,7 @@ while left < right: elif 一定条件 2: right -= 1 -return 没找到 或 对应值 +return 没找到 或 找到对应值 ``` ### 2.3 对撞指针适用范围 @@ -48,15 +47,34 @@ return 没找到 或 对应值 #### 2.4.2 题目大意 -给定一个升序排列的整数数组:`numbers` 和一个目标值 `target`。 +**描述**:给定一个下标从 `1` 开始计数、升序排列的整数数组:`numbers` 和一个目标值 `target`。 -要求:从数组中找出满足相加之和等于 `target` 的两个数,并返回两个数在数组中下的标值。 +**要求**:从数组中找出满足相加之和等于 `target` 的两个数,并返回两个数在数组中下的标值。 -注意:数组下标从 `1` 开始计数。 +**说明**: + +- 2ドル \le numbers.length \le 3 * 10^4$。 +- $-1000 \le numbers[i] \le 1000$。 +- $numbers$ 按非递减顺序排列。 +- $-1000 \le target \le 1000$。 +- 仅存在一个有效答案。 + +**示例**: + +```Python +输入:numbers = [2,7,11,15], target = 9 +输出:[1,2] +解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 + + +输入:numbers = [2,3,4], target = 6 +输出:[1,3] +解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。 +``` #### 2.4.3 解题思路 -这道题如果暴力遍历数组,从中找到相加之和等于 `target` 的两个数,时间复杂度为 `O(n^2)`,可以尝试一下。 +这道题如果暴力遍历数组,从中找到相加之和等于 `target` 的两个数,时间复杂度为 $O(n^2),ドル可以尝试一下。 ```Python class Solution: @@ -71,17 +89,19 @@ class Solution: 结果不出意外的超时了。所以我们要想办法减少时间复杂度。 -因为数组是有序的,所以我们可以考虑使用双指针来减少时间复杂度。具体做法如下: +##### 思路 1:对撞指针 + +可以考虑使用对撞指针来减少时间复杂度。具体做法如下: -- 使用两个指针 `left`,`right`。`left` 指向数组第一个值最小的元素位置,`right` 指向数组值最大元素位置。 -- 判断两个位置上的元素的和与目标值的关系。 - - 如果元素和等于目标值,则返回两个元素位置。 - - 如果元素和大于目标值,则让 `right` 左移,继续检测。 - - 如果元素和小于目标值,则让 `left` 右移,继续检测。 -- 直到 `left` 和 `right` 移动到相同位置停止检测。 -- 如果最终仍没找到,则返回 `[-1, -1]`。 +1. 使用两个指针 `left`,`right`。`left` 指向数组第一个值最小的元素位置,`right` 指向数组值最大元素位置。 +2. 判断两个位置上的元素的和与目标值的关系。 + 1. 如果元素和等于目标值,则返回两个元素位置。 + 2. 如果元素和大于目标值,则让 `right` 左移,继续检测。 + 3. 如果元素和小于目标值,则让 `left` 右移,继续检测。 +3. 直到 `left` 和 `right` 移动到相同位置停止检测。 +4. 如果最终仍没找到,则返回 `[-1, -1]`。 -#### 2.4.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -99,6 +119,11 @@ class Solution: return [-1, -1] ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 + ### 2.5 验证回文串 #### 2.5.1 题目链接 @@ -107,24 +132,41 @@ class Solution: #### 2.5.2 题目大意 -给定一个字符串 `s`。 +**描述**:给定一个字符串 `s`。 + +**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 -要求:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 +**说明**: - 回文串:正着读和反着读都一样的字符串。 +- 1ドル \le s.length \le 2 * 10^5$。 +- `s` 仅由可打印的 ASCII 字符组成。 + +**示例**: + +```Python +输入: "A man, a plan, a canal: Panama" +输出:true +解释:"amanaplanacanalpanama" 是回文串。 + + +输入:"race a car" +输出:false +解释:"raceacar" 不是回文串。 +``` #### 2.5.3 解题思路 -使用对撞指针求解。具体步骤如下: +##### 思路 1:对撞指针 -- 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 -- 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 -- 然后判断 `s[start]` 是否和 `s[end]` 相等(注意大小写)。 - - 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 - - 如果不相等,则说明不是回文串,直接返回 `False`。 -- 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 +1. 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 +2. 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 +3. 然后判断 `s[start]` 是否和 `s[end]` 相等(注意大小写)。 + 1. 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 + 2. 如果不相等,则说明不是回文串,直接返回 `False`。 +4. 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 -#### 2.5.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -148,6 +190,11 @@ class Solution: return True ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(len(s))$。 +- **空间复杂度**:$O(len(s))$。 + ### 2.6 盛最多水的容器 #### 2.6.1 题目链接 @@ -156,32 +203,42 @@ class Solution: #### 2.6.2 题目大意 -给定 `n` 个非负整数 $a_1,a_2, ...,a_n,ドル每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 `n` 条垂直线,垂直线 `i` 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 +**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n,ドル每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 + +**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 + +**说明**: -要求:找出其中的两条垂直线,使得它们与 `x` 轴共同构成的容器可以容纳最多的水。 +- $n == height.length$。 +- 2ドル \le n \le 10^5$。 +- 0ドル \le height[i] \le 10^4$。 -示例: +**示例**:  -- 输入:`[1,8,6,2,5,4,8,3,7]` -- 输出:`49` -- 解释:图中垂直线代表输入数组 `[1,8,6,2,5,4,8,3,7]`。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 `49`。 +```Python +输入:[1,8,6,2,5,4,8,3,7] +输出:49 +解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +``` #### 2.6.3 解题思路 +##### 思路 1:对撞指针 + 从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 `左右两端直线中较低直线的高度 * 两端直线之间的距离 ` 所决定的。所以我们应该使得 **较低直线的高度尽可能的高**,这样才能使盛水面积尽可能的大。 -可以使用双指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: +可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: -- 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 -- 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 -- 判断 `left` 和 `right` 的高度值大小。 - - 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 - - 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 -- 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 +1. 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 +2. 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 +3. 判断 `left` 和 `right` 的高度值大小。 + 1. 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 + 2. 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 +4. 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 -#### 2.6.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -199,15 +256,20 @@ class Solution: return ans ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ## 3. 快慢指针 -> 快慢指针:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。 +> **快慢指针**:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。 ### 3.1 快慢指针求解步骤 -- 使用两个指针 `slow`、`fast`。`slow` 一般指向序列第一个元素,即:`slow = 0`,`fast` 一般指向序列第二个元素,即:`fast = 1`。 -- 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 `slow += 1`。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 `fast += 1`。 -- 到快指针移动到数组尾端(即 `fast == len(nums) - 1`),或者两指针相交,或者满足其他特殊条件时跳出循环体。 +1. 使用两个指针 `slow`、`fast`。`slow` 一般指向序列第一个元素,即:`slow = 0`,`fast` 一般指向序列第二个元素,即:`fast = 1`。 +2. 在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即 `slow += 1`。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 `fast += 1`。 +3. 到快指针移动到数组尾端(即 `fast == len(nums) - 1`),或者两指针相交,或者满足其他特殊条件时跳出循环体。 ### 3.2 快慢指针伪代码模板 @@ -235,14 +297,31 @@ return 合适的值 #### 3.4.2 题目大意 -给定一个有序数组 `nums`。 +**描述**:给定一个有序数组 `nums`。 + +**要求**:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 + +**说明**: + +- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 + +**示例**: + +```Python +输入:nums = [1,1,2] +输出:2, nums = [1,2,_] +解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 -要求:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 -注意:不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 +输入:nums = [0,0,1,1,1,2,2,3,3,4] +输出:5, nums = [0,1,2,3,4] +解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 +``` #### 3.4.3 解题思路 +##### 思路 1:快慢指针 + 因为数组是有序的,那么重复的元素一定会相邻。 删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: @@ -250,13 +329,12 @@ return 合适的值 1. 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向去除重复元素后的数组的末尾位置。`fast` 指向当前元素。 2. 令 `slow` 在后, `fast` 在前。令 `slow = 0`,`fast = 1`。 3. 比较 `slow` 位置上元素值和 `fast` 位置上元素值是否相等。 - - 如果不相等,则将 `slow` 右移一位,将 `fast` 指向位置的元素赋值到 `slow` 位置上。 + - 如果不相等,则将 `slow` 后移一位,将 `fast` 指向位置的元素复制到 `slow` 位置上。 4. 将 `fast` 右移 `1` 位。 +5. 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 +6. 返回 `slow + 1` 即为新数组长度。 -- 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 -- 返回 `slow + 1` 即为新数组长度。 - -#### 3.4.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -275,17 +353,22 @@ class Solution: return slow + 1 ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ## 4. 分离双指针 -> 分离双指针:两个指针分别属于不同的数组 / 链表,两个指针分别在两个数组 / 链表中移动。 +> **分离双指针**:两个指针分别属于不同的数组 / 链表,两个指针分别在两个数组 / 链表中移动。 ### 4.1 分离双指针求解步骤 -- 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组 / 链表的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组 / 链表的第一个元素,即:`left_2 = 0`。 -- 当满足一定条件时,两个指针同时右移,即 `left_1 += 1`、`left_2 += 1`。 -- 当满足另外一定条件时,将 `left_1` 指针右移,即 `left_1 += 1`。 -- 当满足其他一定条件时,将 `left_2` 指针右移,即 `left_2 += 1`。 -- 当其中一个数组 / 链表遍历完时或者满足其他特殊条件时跳出循环体。 +1. 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组 / 链表的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组 / 链表的第一个元素,即:`left_2 = 0`。 +2. 当满足一定条件时,两个指针同时右移,即 `left_1 += 1`、`left_2 += 1`。 +3. 当满足另外一定条件时,将 `left_1` 指针右移,即 `left_1 += 1`。 +4. 当满足其他一定条件时,将 `left_2` 指针右移,即 `left_2 += 1`。 +5. 当其中一个数组 / 链表遍历完时或者满足其他特殊条件时跳出循环体。 ### 4.2 分离双指针伪代码模板 @@ -305,7 +388,9 @@ while left_1 < len(nums1) and left_2 < len(nums2): ### 4.3 分离双指针使用范围 -分离双指针一般用于处理有序数组合并,求交集、并集问题。下面我们根据具体例子来讲解如何使用分离双指针来解决问题。 +分离双指针一般用于处理有序数组合并,求交集、并集问题。 + +下面我们根据具体例子来讲解如何使用分离双指针来解决问题。 ### 4.4 两个数组的交集 @@ -315,22 +400,39 @@ while left_1 < len(nums1) and left_2 < len(nums2): #### 4.4.2 题目大意 -给定两个数组 `nums1`、`nums2`。 +**描述**:给定两个数组 `nums1` 和 `nums2`。 + +**要求**:返回两个数组的交集。重复元素只计算一次。 -要求:编写一个函数来计算它们的交集。重复元素只计算一次。 +**说明**: + +- 1ドル \le nums1.length, nums2.length \le 1000$。 +- 0ドル \le nums1[i], nums2[i] \le 1000$。 + +**示例**: + +```Python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2] +示例 2: + +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[9,4] +解释:[4,9] 也是可通过的 +``` #### 4.4.3 解题思路 -使用分离双指针求解,具体步骤如下: +##### 思路 1:分离双指针 -- 对数组 `nums1`、`nums2` 先排序。 -- 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组的第一个元素,即:`left_2 = 0`。 -- 如果 `nums1[left_1]` 等于 `nums2[left_2]`,则将其加入答案数组(注意去重),并将 `left_1` 和 `left_2` 右移。 -- 如果 `nums1[left_1]` 小于 `nums2[left_2]`,则将 `left_1` 右移。 -- 如果 `nums1[left_1]` 大于 `nums2[left_2]`,则将 `left_2` 右移。 -- 最后输出答案数组。 +1. 对数组 `nums1`、`nums2` 先排序。 +2. 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组的第一个元素,即:`left_2 = 0`。 +3. 如果 `nums1[left_1]` 等于 `nums2[left_2]`,则将其加入答案数组(注意去重),并将 `left_1` 和 `left_2` 右移。 +4. 如果 `nums1[left_2]` 小于 `nums2[left_2]`,则将 `left_1` 右移。 +5. 如果 `nums1[left_2]` 大于 `nums2[left_2]`,则将 `left_2` 右移。 +6. 最后输出答案数组。 -#### 4.4.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -354,13 +456,18 @@ class Solution: return res ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ## 5. 双指针总结 双指针分为「对撞指针」、「快慢指针」、「分离双指针」。 -- 对撞指针:两个指针方向相反。适合解决查找有序数组中满足某些约束条件的一组元素问题、字符串反转问题。 -- 快慢指针:两个指针方向相同。适合解决数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。 -- 分离双指针:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。 +- **对撞指针**:两个指针方向相反。适合解决查找有序数组中满足某些约束条件的一组元素问题、字符串反转问题。 +- **快慢指针**:两个指针方向相同。适合解决数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。 +- **分离双指针**:两个指针分别属于不同的数组 / 链表。适合解决有序数组合并,求交集、并集问题。 ## 参考资料 diff --git a/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md b/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md index 2ef1a0cf..375efb91 100644 --- a/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md +++ b/Contents/01.Array/05.Array-Sliding-Window/01.Array-Sliding-Window.md @@ -2,18 +2,18 @@ 在计算机网络中,滑动窗口协议(Sliding Window Protocol)是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。我们所要讲解的滑动窗口算法也是利用了同样的特性。 -> 滑动窗口(Sliding Window):在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 +> **滑动窗口算法(Sliding Window)**:在给定数组 / 字符串上维护一个固定长度或不定长度的窗口。可以对窗口进行滑动操作、缩放操作,以及维护最优解操作。 -- 滑动操作:窗口可按照一定方向进行移动。最常见的是向右侧移动。 -- 缩放操作:对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度。 +- **滑动操作**:窗口可按照一定方向进行移动。最常见的是向右侧移动。 +- **缩放操作**:对于不定长度的窗口,可以从左侧缩小窗口长度,也可以从右侧增大窗口长度。 -滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以可以将滑动窗口看做是快慢指针的一种特殊形式。 +滑动窗口利用了双指针中的快慢指针技巧,我们可以将滑动窗口看做是快慢指针两个指针中间的区间,也可以将滑动窗口看做是快慢指针的一种特殊形式。 ## 2. 滑动窗口适用范围 滑动窗口算法一般用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。该算法可以将一部分问题中的嵌套循环转变为一个单循环,因此它可以减少时间复杂度。 -根据问题,我们可以将滑动窗口分为以下两种: +按照窗口长度的固定情况,我们可以将滑动窗口题目分为以下两种: - **固定长度窗口**:窗口大小是固定的。 - **不定长度窗口**:窗口大小是不固定的。 @@ -66,26 +66,46 @@ while right < len(nums): #### 3.3.2 题目大意 -给你一个整数数组 `arr` 和两个整数 `k` 和 `threshold` 。 +**描述**:给定一个整数数组 `arr` 和两个整数 `k` 和 `threshold` 。 -要求:返回长度为 `k` 且平均值大于等于 `threshold` 的子数组数目。 +**要求**:返回长度为 `k` 且平均值大于等于 `threshold` 的子数组数目。 + +**说明**: + +- 1ドル \le arr.length \le 10^5$。 +- 1ドル \le arr[i] \le 10^4$。 +- 1ドル \le k \le arr.length$。 +- 0ドル \le threshold \le 10^4$。 + +**示例**: + +```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 的值)。 + + +输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 +输出:6 +解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 +``` #### 3.3.3 解题思路 +##### 思路 1:滑动窗口(固定长度) + 这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 `k`。具体做法如下: 1. `ans` 用来维护答案数目。`window_sum` 用来维护窗口中元素的和。 2. `left` 、`right` 都指向序列的第一个元素,即:`left = 0`,`right = 0`。 -3. 当窗口元素个数不足 `k` 个时,不断移动 `right`,先将 `k` 个元素填入窗口中。 -4. 当窗口元素个数为 `k` 时,即:`right - left + 1>= k` 时,判断窗口内的元素和平均值是否大于等于阈值。 +3. 向右移动 `right`,先将 `k` 个元素填入窗口中。 +4. 当窗口元素个数为 `k` 时,即:`right - left + 1>= k` 时,判断窗口内的元素和平均值是否大于等于阈值 `threshold`。 1. 如果满足,则答案数目 + 1。 2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 -5. 向右移动 `right`,将元素填入窗口中。 -6. 重复 3 ~ 5 步,直到 `right` 到达数组末尾。 +5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 +6. 最后输出答案数目。 -最后输出答案数目。 - -#### 3.3.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -109,6 +129,11 @@ class Solution: return ans ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + ## 4. 不定长度窗口 ### 4.1 不定长度窗口求解步骤 @@ -146,23 +171,42 @@ while right < len(nums): #### 4.3.2 题目大意 -给定一个字符串 `s`。 +**描述**:给定一个字符串 `s`。 + +**要求**:找出其中不含有重复字符的最长子串的长度。 + +**说明**: -要求:找出其中不含有重复字符的 最长子串 的长度。 +- 0ドル \le s.length \le 5 * 10^4$。 +- `s` 由英文字母、数字、符号和空格组成。 + +**示例**: + +```Python +输入: s = "abcabcbb" +输出: 3 +解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 + + +输入: s = "bbbbb" +输出: 1 +解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 +``` #### 4.3.3 解题思路 -用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。 +##### 思路 1:滑动窗口(不定长度) -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 +用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。 -- 一开始,`left`、`right` 都指向 `0`。 -- 将最右侧字符 `s[right]` 加入当前窗口 `window` 中,记录该字符个数。 -- 如果该窗口中该字符的个数多于 1 个,即 `window[s[right]]> 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `window[s[right]] <= 1`。 -- 维护更新无重复字符的最长子串长度。然后右移 `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]] <= 1`。 +5. 维护更新无重复字符的最长子串长度。然后继续右移 `right`,直到 `right>= len(nums)` 结束。 +6. 输出无重复字符的最长子串长度。 -#### 4.3.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -188,6 +232,11 @@ class Solution: return ans ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 + ### 4.4 长度最小的子数组 #### 4.4.1 题目链接 @@ -196,23 +245,43 @@ class Solution: #### 4.4.2 题目大意 -给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 +**描述**:给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 + +**要求**:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`。 -要求:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`。 +**说明**: + +- 1ドル \le target \le 10^9$。 +- 1ドル \le nums.length \le 10^5$。 +- 1ドル \le nums[i] \le 10^5$。 + +**示例**: + +```Python +输入:target = 7, nums = [2,3,1,2,4,3] +输出:2 +解释:子数组 [4,3] 是该条件下的长度最小的子数组。 + + +输入:target = 4, nums = [1,4,4] +输出:1 +``` #### 4.4.3 解题思路 +##### 思路 1:滑动窗口(不定长度) + 最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 用滑动窗口来记录连续子数组的和,设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 `target`。 -- 一开始,`left`、`right` 都指向 `0`。 -- 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。 -- 如果 `window_sum>= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`。 -- 然后继续右移 `right`,直到 `right>= len(nums)` 结束。 -- 输出窗口和的最小值作为答案。 +1. 一开始,`left`、`right` 都指向 `0`。 +2. 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。 +3. 如果 `window_sum>= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`。 +4. 然后继续右移 `right`,直到 `right>= len(nums)` 结束。 +5. 输出窗口和的最小值作为答案。 -#### 4.4.4 代码 +##### 思路 1:代码 ```Python class Solution: @@ -236,6 +305,11 @@ class Solution: return ans if ans != size + 1 else 0 ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ### 4.5 乘积小于K的子数组 #### 4.5.1 题目链接 @@ -244,25 +318,40 @@ class Solution: #### 4.5.2 题目大意 -给定一个正整数数组 `nums`和整数 `k` 。 +**描述**:给定一个正整数数组 `nums`和整数 `k` 。 -要求:找出该数组内乘积小于 `k` 的连续的子数组的个数。 +**要求**:找出该数组内乘积小于 `k` 的连续的子数组的个数。 -#### 4.5.3 解题思路 +**说明**: -滑动窗口求解。 +- 1ドル \le nums.length \le 3 * 10^4$。 +- 1ドル \le nums[i] \le 1000$。 +- 0ドル \le k \le 10^6$。 -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 `window_product` 都小于 `k`。使用 `window_product` 记录窗口中的乘积值,使用 `count` 记录符合要求的子数组个数。 +**示例**: -- 一开始,`left`、`right` 都指向 `0`。 +```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 的子数组。 -- 将最右侧元素加入当前子数组乘积 `window_product` 中。 -- 如果 `window_product>= k` ,则不断右移 `left`,缩小滑动窗口长度,并更新当前乘积值 `window_product` 直到 `window_product < k`。 -- 记录累积答案个数 += 1,向右移动 `right`,直到 `right>= len(nums)` 结束。 -- 输出累积答案个数。 +输入:nums = [1,2,3], k = 0 +输出:0 +``` -#### 4.5.4 代码 +#### 4.5.3 解题思路 + +##### 思路 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)` 结束。 +6. 输出累积答案个数。 + +##### 思路 1:代码 ```Python class Solution: @@ -290,6 +379,11 @@ class Solution: return count ``` +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + ## 参考资料 - 【答案】[TCP 协议的滑动窗口具体是怎样控制流量的? - 知乎](https://www.zhihu.com/question/32255109/answer/68558623) diff --git a/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md b/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md index f14a24d4..9128dcaa 100644 --- a/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md +++ b/Contents/01.Array/05.Array-Sliding-Window/02.Array-Sliding-Window-List.md @@ -16,6 +16,7 @@ | 0438 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0438.%20%E6%89%BE%E5%88%B0%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E6%89%80%E6%9C%89%E5%AD%97%E6%AF%8D%E5%BC%82%E4%BD%8D%E8%AF%8D.md) | 哈希表、字符串、滑动窗口 | 中等 | | 0995 | [K 连续位的最小翻转次数](https://leetcode.cn/problems/minimum-number-of-k-consecutive-bit-flips/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0995.%20K%20%E8%BF%9E%E7%BB%AD%E4%BD%8D%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BF%BB%E8%BD%AC%E6%AC%A1%E6%95%B0.md) | 位运算、数组、前缀和、滑动窗口 | 困难 | | 0683 | [K 个关闭的灯泡](https://leetcode.cn/problems/k-empty-slots/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0683.%20K%20%E4%B8%AA%E5%85%B3%E9%97%AD%E7%9A%84%E7%81%AF%E6%B3%A1.md) | 树状数组、数组、有序集合、滑动窗口 | 困难 | +| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 排序、有序集合、哈希表 | 中等 | | 0239 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0239.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.md) | 队列,数组、滑动窗口、单调队列、堆(优先队列) | 困难 | | 0480 | [滑动窗口中位数](https://leetcode.cn/problems/sliding-window-median/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0480.%20%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E4%B8%AD%E4%BD%8D%E6%95%B0.md) | 数组、哈希表、滑动窗口、堆(优先队列) | 困难 | @@ -46,6 +47,5 @@ | 0904 | [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0904.%20%E6%B0%B4%E6%9E%9C%E6%88%90%E7%AF%AE.md) | 数组、哈希表、滑动窗口 | 中等 | | 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动数组 | 中等 | | 0467 | [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0467.%20%E7%8E%AF%E7%BB%95%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%94%AF%E4%B8%80%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 字符串、动态规划 | 中等 | -| 0220 | [存在重复元素 III](https://leetcode.cn/problems/contains-duplicate-iii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0220.%20%E5%AD%98%E5%9C%A8%E9%87%8D%E5%A4%8D%E5%85%83%E7%B4%A0%20III.md) | 排序、有序集合、哈希表 | 中等 | | 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | diff --git "a/Solutions/0003. 346円227円240円351円207円215円345円244円215円345円255円227円347円254円246円347円232円204円346円234円200円351円225円277円345円255円220円344円270円262円.md" "b/Solutions/0003. 346円227円240円351円207円215円345円244円215円345円255円227円347円254円246円347円232円204円346円234円200円351円225円277円345円255円220円344円270円262円.md" index 32ffe214..ca179d31 100644 --- "a/Solutions/0003. 346円227円240円351円207円215円345円244円215円345円255円227円347円254円246円347円232円204円346円234円200円351円225円277円345円255円220円344円270円262円.md" +++ "b/Solutions/0003. 346円227円240円351円207円215円345円244円215円345円255円227円347円254円246円347円232円204円346円234円200円351円225円277円345円255円220円344円270円262円.md" @@ -5,23 +5,42 @@ ## 题目大意 -描述:给定一个字符串 `s`。 +**描述**:给定一个字符串 `s`。 -要求:找出其中不含有重复字符的最长子串的长度。 +**要求**:找出其中不含有重复字符的最长子串的长度。 + +**说明**: + +- 0ドル \le s.length \le 5 * 10^4$。 +- `s` 由英文字母、数字、符号和空格组成。 + +**示例**: + +```Python +输入: s = "abcabcbb" +输出: 3 +解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 + + +输入: s = "bbbbb" +输出: 1 +解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 +``` ## 解题思路 -用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。 +### 思路 1:滑动窗口(不定长度) -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中没有重复字符。 +用滑动窗口 `window` 来记录不重复的字符个数,`window` 为哈希表类型。 -- 一开始,`left`、`right` 都指向 `0`。 -- 向右移动 `right`,将最右侧字符 `s[right]` 加入当前窗口 `window` 中,记录该字符个数。 -- 如果该窗口中该字符的个数多于 1 个,即 `window[s[right]]> 1`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口中对应字符的个数,直到 `window[s[right]] <= 1`。 -- 维护更新无重复字符的最长子串长度。然后继续右移 `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]] <= 1`。 +5. 维护更新无重复字符的最长子串长度。然后继续右移 `right`,直到 `right>= len(nums)` 结束。 +6. 输出无重复字符的最长子串长度。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -47,3 +66,8 @@ class Solution: return ans ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(| \sum |)$。其中 $\sum$ 表示字符集,$| \sum |$ 表示字符集的大小。 + diff --git "a/Solutions/0011. 347円233円233円346円234円200円345円244円232円346円260円264円347円232円204円345円256円271円345円231円250円.md" "b/Solutions/0011. 347円233円233円346円234円200円345円244円232円346円260円264円347円232円204円345円256円271円345円231円250円.md" index 12857bf6..e6f0f9f3 100644 --- "a/Solutions/0011. 347円233円233円346円234円200円345円244円232円346円260円264円347円232円204円345円256円271円345円231円250円.md" +++ "b/Solutions/0011. 347円233円233円346円234円200円345円244円232円346円260円264円347円232円204円345円256円271円345円231円250円.md" @@ -5,30 +5,42 @@ ## 题目大意 -给定 n 个非负整数 $a_1,a_2, ...,a_n,ドル每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 +**描述**:给定 $n$ 个非负整数 $a_1,a_2, ...,a_n,ドル每个数代表坐标中的一个点 $(i, a_i)$。在坐标内画 $n$ 条垂直线,垂直线 $i$ 的两个端点分别为 $(i, a_i)$ 和 $(i, 0)$。 -示例: +**要求**:找出其中的两条线,使得它们与 $x$ 轴共同构成的容器可以容纳最多的水。 + +**说明**: + +- $n == height.length$。 +- 2ドル \le n \le 10^5$。 +- 0ドル \le height[i] \le 10^4$。 + +**示例**:  -- 输入:`[1,8,6,2,5,4,8,3,7]` -- 输出:`49` -- 解释:图中垂直线代表输入数组 `[1,8,6,2,5,4,8,3,7]`。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 `49`。 +```Python +输入:[1,8,6,2,5,4,8,3,7] +输出:49 +解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 +``` ## 解题思路 -- 从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 `左右两端直线中较低直线的高度 * 两端直线之间的距离 ` 所决定的。所以我们应该使得 **较低直线的高度尽可能的高**,这样才能使盛水面积尽可能的大。 +### 思路 1:对撞指针 + +从示例中可以看出,如果确定好左右两端的直线,容纳的水量是由 `左右两端直线中较低直线的高度 * 两端直线之间的距离 ` 所决定的。所以我们应该使得 **较低直线的高度尽可能的高**,这样才能使盛水面积尽可能的大。 - 可以使用双指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: +可以使用对撞指针求解。移动较低直线所在的指针位置,从而得到不同的高度和面积,最终获取其中最大的面积。具体做法如下: - - 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 - - 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 - - 判断 `left` 和 `right` 的高度值大小。 - - 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 - - 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 - - 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 +1. 使用两个指针 `left`,`right`。`left` 指向数组开始位置,`right` 指向数组结束位置。 +2. 计算 `left` 和 `right` 所构成的面积值,同时维护更新最大面积值。 +3. 判断 `left` 和 `right` 的高度值大小。 + 1. 如果 `left` 指向的直线高度比较低,则将 `left` 指针右移。 + 2. 如果 `right` 指向的直线高度比较低,则将 `right` 指针左移。 +4. 如果遇到 `left == right`,跳出循环,最后返回最大的面积。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -46,3 +58,8 @@ class Solution: return ans ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0015. 344円270円211円346円225円260円344円271円213円345円222円214円.md" "b/Solutions/0015. 344円270円211円346円225円260円344円271円213円345円222円214円.md" index 42d6ac87..19229d51 100644 --- "a/Solutions/0015. 344円270円211円346円225円260円344円271円213円345円222円214円.md" +++ "b/Solutions/0015. 344円270円211円346円225円260円344円271円213円345円222円214円.md" @@ -5,22 +5,41 @@ ## 题目大意 -给定一个整数数组 nums,判断 nums 中是否存在三个元素 a、b、c,满足 a + b + c = 0。要求找出所有满足要求的不重复的三元组。 +**描述**:给定一个整数数组 `nums`。 + +**要求**:判断 `nums` 中是否存在三个元素 `a`、`b`、`c`,满足 `a + b + c == 0`。要求找出所有满足要求的不重复的三元组。 + +**说明**: + +- 3ドル \le nums.length \le 3000$。 +- $-10^5 \le nums[i] \le 10^5$。 + +**示例**: + +```Python +输入:nums = [-1,0,1,2,-1,-4] +输出:[[-1,-1,2],[-1,0,1]] + + +输入:nums = [0,1,1] +输出:[] +``` ## 解题思路 -直接三重遍历查找 a、b、c 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 +### 思路 1:对撞指针 -先将数组进行排序,以保证按顺序查找 a、b、c 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$ +直接三重遍历查找 `a`、`b`、`c` 的时间复杂度是:$O(n^3)$。我们可以通过一些操作来降低复杂度。 -第一重循环遍历 a,对于每个 a 元素,从 a 元素的下一个位置开始,使用双指针 left,right。left 指向 a 元素的下一个位置,right 指向末尾位置。先将 left 右移、right 左移去除重复元素,再进行下边的判断。 +先将数组进行排序,以保证按顺序查找 `a`、`b`、`c` 时,元素值为升序,从而保证所找到的三个元素是不重复的。同时也方便下一步使用双指针减少一重遍历。时间复杂度为:$O(nlogn)$。 -- 若 `nums[a] + nums[left] + nums[right] = 0`,则得到一个解,将其加入答案数组中,并继续将 left 右移,right 左移; +第一重循环遍历 `a`,对于每个 `a` 元素,从 `a` 元素的下一个位置开始,使用对撞指针 `left`,`right`。`left` 指向 `a` 元素的下一个位置,`right` 指向末尾位置。先将 `left` 右移、`right` 左移去除重复元素,再进行下边的判断。 -- 若 `nums[a] + nums[left] + nums[right]> 0`,说明 nums[right] 值太大,将 right 向左移; -- 若 `nums[a] + nums[left] + nums[right] < 0`,说明 nums[left] 值太小,将 left 右移。 +1. 如果 `nums[a] + nums[left] + nums[right] = 0`,则得到一个解,将其加入答案数组中,并继续将 `left` 右移,`right` 左移; +2. 如果 `nums[a] + nums[left] + nums[right]> 0`,说明 `nums[right]` 值太大,将 `right` 向左移; +3. 如果 `nums[a] + nums[left] + nums[right] < 0`,说明 `nums[left]` 值太小,将 `left` 右移。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -30,14 +49,14 @@ class Solution: ans = [] for i in range(n): - if i> 0 and nums[i] == nums[i-1]: + if i> 0 and nums[i] == nums[i - 1]: continue left = i + 1 right = n - 1 while left < right: - while left < right and left> i + 1 and nums[left] == nums[left-1]: + while left < right and left> i + 1 and nums[left] == nums[left - 1]: left += 1 - while left < right and right < n - 1 and nums[right+1] == nums[right]: + while left < right and right < n - 1 and nums[right + 1] == nums[right]: right -= 1 if left < right and nums[i] + nums[left] + nums[right] == 0: ans.append([nums[i], nums[left], nums[right]]) @@ -50,3 +69,8 @@ class Solution: return ans ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2)$。 +- **空间复杂度**:$O(n)$。 + diff --git "a/Solutions/0026. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円.md" "b/Solutions/0026. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円.md" index 7a35c6f7..6f3ea2d1 100644 --- "a/Solutions/0026. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円.md" +++ "b/Solutions/0026. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円.md" @@ -5,28 +5,44 @@ ## 题目大意 -给定一个有序数组 `nums`。 +**描述**:给定一个有序数组 `nums`。 -要求:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 +**要求**:删除数组 `nums` 中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。 -注意:不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 +**说明**: + +- 不能使用额外的数组空间,在原地修改数组,并在使用 $O(1)$ 额外空间的条件下完成。 + +**示例**: + +```Python +输入:nums = [1,1,2] +输出:2, nums = [1,2,_] +解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 + + +输入:nums = [0,0,1,1,1,2,2,3,3,4] +输出:5, nums = [0,1,2,3,4] +解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。 +``` ## 解题思路 -1. 因为数组是有序的,那么重复的元素一定会相邻。 +### 思路 1:快慢指针 - 删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: +因为数组是有序的,那么重复的元素一定会相邻。 - 1. 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向去除重复元素后的数组的末尾位置。`fast` 指向当前元素。 - 2. 令 `slow` 在后, `fast` 在前。令 `slow = 0`,`fast = 1`。 - 3. 比较 `slow` 位置上元素值和 `fast` 位置上元素值是否相等。 - - 如果不相等,则将 `slow` 后移一位,将 `fast` 指向位置的元素复制到 `slow` 位置上。 - 4. 将 `fast` 右移 `1` 位。 +删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下: - - 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 - - 返回 `slow + 1` 即为新数组长度。 +1. 定义两个快慢指针 `slow`,`fast`。其中 `slow` 指向去除重复元素后的数组的末尾位置。`fast` 指向当前元素。 +2. 令 `slow` 在后, `fast` 在前。令 `slow = 0`,`fast = 1`。 +3. 比较 `slow` 位置上元素值和 `fast` 位置上元素值是否相等。 + - 如果不相等,则将 `slow` 后移一位,将 `fast` 指向位置的元素复制到 `slow` 位置上。 +4. 将 `fast` 右移 `1` 位。 +5. 重复上述 3 ~ 4 步,直到 `fast` 等于数组长度。 +6. 返回 `slow + 1` 即为新数组长度。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -45,3 +61,8 @@ class Solution: return slow + 1 ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0027. 347円247円273円351円231円244円345円205円203円347円264円240円.md" "b/Solutions/0027. 347円247円273円351円231円244円345円205円203円347円264円240円.md" index 93bae82d..96ea0551 100644 --- "a/Solutions/0027. 347円247円273円351円231円244円345円205円203円347円264円240円.md" +++ "b/Solutions/0027. 347円247円273円351円231円244円345円205円203円347円264円240円.md" @@ -5,30 +5,55 @@ ## 题目大意 -给定一个数组 nums,和一个值 val。不使用额外数组空间,将数组中所有数值等于 val 值的元素移除掉,并且返回新数组的长度。 +**描述**:给定一个数组 `nums`,和一个值 `val`。 -## 解题思路 +**要求**:不使用额外数组空间,将数组中所有数值等于 `val` 值的元素移除掉,并且返回新数组的长度。 + +**说明**: + +- 0ドル \le nums.length \le 100$。 +- 0ドル \le nums[i] \le 50$。 +- 0ドル \le val \le 100$。 -使用两个指针 left,right。left 指向处理好的非 val 值元素数组的尾部,right 指针指向当前待处理元素。 +**示例**: -不断向右移动 right 指针,每次移动到非 val 值的元素,则将左右指针对应的数交换,交换同时将 left 右移。 +```Python +输入:nums = [3,2,2,3], val = 3 +输出:2, nums = [2,2] +解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。 -这样就将非 val 值的元素进行前移,left 指针左边均为处理好的非 val 值元素,而从 left 指针指向的位置开始, right 指针左边都为 val 值。 -遍历结束之后,则所有 val 值元素都移动到了右侧,且保持了非零数的相对位置。此时 left 就是新数组的长度。 +输入:nums = [0,1,2,2,3,0,4,2], val = 2 +输出:5, nums = [0,1,4,0,3] +解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。 +``` -## 代码 +## 解题思路 + +### 思路 1:快慢指针 + +1. 使用两个指针 `slow`,`fast`。`slow` 指向处理好的非 `val` 值元素数组的尾部,`fast` 指针指向当前待处理元素。 +2. 不断向右移动 `fast` 指针,每次移动到非 `val` 值的元素,则将左右指针对应的数交换,交换同时将 `slow` 右移。 +3. 这样就将非 `val` 值的元素进行前移,`slow` 指针左边均为处理好的非 `val` 值元素,而从 `slow` 指针指向的位置开始, `fast` 指针左边都为 `val `值。 +4. 遍历结束之后,则所有 `val` 值元素都移动到了右侧,且保持了非零数的相对位置。此时 `slow` 就是新数组的长度。 + +### 思路 1:代码 ```Python class Solution: def removeElement(self, nums: List[int], val: int) -> int: - left = 0 - right = 0 - while right < len(nums): - if nums[right] != val: - nums[left], nums[right] = nums[right], nums[left] - left += 1 - right += 1 - return left + slow = 0 + fast = 0 + while fast < len(nums): + if nums[fast] != val: + nums[slow], nums[fast] = nums[fast], nums[slow] + slow += 1 + fast += 1 + return slow ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0080. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円 II.md" "b/Solutions/0080. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円 II.md" index 3fe20750..3046c130 100644 --- "a/Solutions/0080. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円 II.md" +++ "b/Solutions/0080. 345円210円240円351円231円244円346円234円211円345円272円217円346円225円260円347円273円204円344円270円255円347円232円204円351円207円215円345円244円215円351円241円271円 II.md" @@ -5,26 +5,43 @@ ## 题目大意 -给定一个有序数组 `nums`。 +**描述**:给定一个有序数组 `nums`。 -要求:在原数组空间基础上删除重复出现 2 次以上的元素,并返回删除后数组的新长度。 +**要求**:在原数组空间基础上删除重复出现 `2` 次以上的元素,并返回删除后数组的新长度。 -## 解题思路 +**说明**: -因为数组是有序的,所以重复元素必定是连续的。可以使用双指针来解决。具体做法如下: +- 1ドル \le nums.length \le 3 * 10^4$。 +- $-10^4 \le nums[i] \le 10^4$。 +- `nums` 已按升序排列。 -使用两个指针 `slow`,`fast`。`slow` 指针指向即将放置元素的位置,`fast` 指针指向当前待处理元素。 +**示例**: -本题要求相同元素最多出现 2 次,并且 `slow - 2` 是上上次放置了元素的位置。则应该检查 `nums[slow - 2]` 和当前待处理元素 `nums[fast]` 是否相同。 +```Python +输入:nums = [1,1,1,2,2,3] +输出:5, nums = [1,1,2,2,3] +解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。 -- 如果 `nums[slow - 2] == nums[fast]` 时,此时必有 `nums[slow - 2] == nums[slow - 1] == nums[fast]`,则当前 `nums[fast]` 不保留,直接向右移动快指针 `fast`。 -- 如果 `nums[slow - 2] != nums[fast]` 时,则保留 `nums[fast]`。将 `nums[fast]` 赋值给 `nums[slow]` ,同时将 `slow` 右移。然后再向右移动快指针 `fast`。 -这样 `slow` 指针左边均为处理好的数组元素,而从 `slow` 指针指向的位置开始, `fast` 指针左边都为舍弃的重复元素。 +输入:nums = [0,0,1,1,1,1,2,3,3] +输出:7, nums = [0,0,1,1,2,3,3] +解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 不需要考虑数组中超出新长度后面的元素。 +``` + +## 解题思路 -遍历结束之后,此时 `slow` 就是新数组的长度。 +### 思路 1:快慢指针 -## 代码 +因为数组是有序的,所以重复元素必定是连续的。可以使用快慢指针来解决。具体做法如下: + +1. 使用两个指针 `slow`,`fast`。`slow` 指针指向即将放置元素的位置,`fast` 指针指向当前待处理元素。 +2. 本题要求相同元素最多出现 2 次,并且 `slow - 2` 是上上次放置了元素的位置。则应该检查 `nums[slow - 2]` 和当前待处理元素 `nums[fast]` 是否相同。 + 1. 如果 `nums[slow - 2] == nums[fast]` 时,此时必有 `nums[slow - 2] == nums[slow - 1] == nums[fast]`,则当前 `nums[fast]` 不保留,直接向右移动快指针 `fast`。 + 2. 如果 `nums[slow - 2] != nums[fast]` 时,则保留 `nums[fast]`。将 `nums[fast]` 赋值给 `nums[slow]` ,同时将 `slow` 右移。然后再向右移动快指针 `fast`。 +3. 这样 `slow` 指针左边均为处理好的数组元素,而从 `slow` 指针指向的位置开始, `fast` 指针左边都为舍弃的重复元素。 +4. 遍历结束之后,此时 `slow` 就是新数组的长度。 + +### 思路 1:代码 ```Python class Solution: @@ -41,3 +58,8 @@ class Solution: return slow ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" "b/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" index 71acd5f2..4ce65e97 100644 --- "a/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" +++ "b/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" @@ -5,24 +5,41 @@ ## 题目大意 -给定一个字符串 `s`。 +**描述**:给定一个字符串 `s`。 -要求:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 +**要求**:判断是否为回文串(只考虑字符串中的字母和数字字符,并且忽略字母的大小写)。 + +**说明**: - 回文串:正着读和反着读都一样的字符串。 +- 1ドル \le s.length \le 2 * 10^5$。 +- `s` 仅由可打印的 ASCII 字符组成。 + +**示例**: + +```Python +输入: "A man, a plan, a canal: Panama" +输出:true +解释:"amanaplanacanalpanama" 是回文串。 + + +输入:"race a car" +输出:false +解释:"raceacar" 不是回文串。 +``` ## 解题思路 -使用对撞指针求解。具体步骤如下: +### 思路 1:对撞指针 -- 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 -- 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 -- 然后判断 `s[start]` 是否和 `s[end]` 相等(注意大小写)。 - - 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 - - 如果不相等,则说明不是回文串,直接返回 `False`。 -- 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 +1. 使用两个指针 `left`,`right`。`left` 指向字符串开始位置,`right` 指向字符串结束位置。 +2. 判断两个指针对应字符是否是字母或数字。 通过 `left` 右移、`right` 左移的方式过滤掉字母和数字以外的字符。 +3. 然后判断 `s[left]` 是否和 `s[right]` 相等(注意大小写)。 + 1. 如果相等,则将 `left` 右移、`right` 左移,继续进行下一次过滤和判断。 + 2. 如果不相等,则说明不是回文串,直接返回 `False`。 +4. 如果遇到 `left == right`,跳出循环,则说明该字符串是回文串,返回 `True`。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -46,3 +63,8 @@ class Solution: return True ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(len(s))$。 +- **空间复杂度**:$O(len(s))$。 + diff --git "a/Solutions/0167. 344円270円244円346円225円260円344円271円213円345円222円214円 II - 350円276円223円345円205円245円346円234円211円345円272円217円346円225円260円347円273円204円.md" "b/Solutions/0167. 344円270円244円346円225円260円344円271円213円345円222円214円 II - 350円276円223円345円205円245円346円234円211円345円272円217円346円225円260円347円273円204円.md" index 6b47860d..fe37262a 100644 --- "a/Solutions/0167. 344円270円244円346円225円260円344円271円213円345円222円214円 II - 350円276円223円345円205円245円346円234円211円345円272円217円346円225円260円347円273円204円.md" +++ "b/Solutions/0167. 344円270円244円346円225円260円344円271円213円345円222円214円 II - 350円276円223円345円205円245円346円234円211円345円272円217円346円225円260円347円273円204円.md" @@ -32,9 +32,7 @@ ## 解题思路 -### 思路 1:二分查找 - -这道题如果暴力遍历数组,从中找到相加之和等于 `target` 的两个数,时间复杂度为 `O(n^2)`,可以尝试一下。 +这道题如果暴力遍历数组,从中找到相加之和等于 `target` 的两个数,时间复杂度为 $O(n^2),ドル可以尝试一下。 ```Python class Solution: @@ -47,9 +45,48 @@ class Solution: return [-1, -1] ``` -结果不出意外的超时了。所以我们要想办法减少时间复杂度。 +结果不出意外的超时了。所以我们要想办法降低时间复杂度。 + +### 思路 1:二分查找 + +因为数组是有序的,可以考虑使用二分查找来减少时间复杂度。具体做法如下: + +1. 使用一重循环遍历数组,先固定第一个数,即 `numsbers[i]`。 +2. 然后使用二分查找的方法寻找符合要求的第二个数。 +3. 使用两个指针 `left`,`right`。`left` 指向数组第一个数的下一个数,`right` 指向数组值最大元素位置。 +4. 判断第一个数 `numsbers[i]` 和两个指针中间元素 `numbers[mid]` 的和与目标值的关系。 + 1. 如果 `numbers[mid] + numbers[i] < target`,排除掉不可能区间 `[left, mid]`,在 `[mid + 1, right]` 中继续搜索。 + 2. 如果 `numbers[mid] + numbers[i]>= target`,则第二个数可能在 `[left, mid]` 中,则在 `[left, mid]` 中继续搜索。 +5. 直到 `left` 和 `right` 移动到相同位置停止检测。如果 `numbers[left] + numbers[i] == target`,则返回两个元素位置 `[left + 1, i + 1]`(下标从 `1` 开始计数)。 +6. 如果最终仍没找到,则返回 `[-1, -1]`。 -因为数组是有序的,所以我们可以考虑使用双指针来减少时间复杂度。具体做法如下: +### 思路 1:代码 + +```Python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + for i in range(len(numbers)): + left, right = i + 1, len(numbers) - 1 + while left < right: + mid = left + (right - left) // 2 + if numbers[mid] + numbers[i] < target: + left = mid + 1 + else: + right = mid + if numbers[left] + numbers[i] == target: + return [i + 1, left + 1] + + return [-1, -1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2 n)$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:对撞指针 + +可以考虑使用对撞指针来减少时间复杂度。具体做法如下: 1. 使用两个指针 `left`,`right`。`left` 指向数组第一个值最小的元素位置,`right` 指向数组值最大元素位置。 2. 判断两个位置上的元素的和与目标值的关系。 @@ -59,7 +96,7 @@ class Solution: 3. 直到 `left` 和 `right` 移动到相同位置停止检测。 4. 如果最终仍没找到,则返回 `[-1, -1]`。 -### 思路 1:代码 +### 思路 2:代码 ```Python class Solution: @@ -77,8 +114,8 @@ class Solution: return [-1, -1] ``` -### 思路 1:复杂度分析 +### 思路 2:复杂度分析 -- **时间复杂度**:$O(\log_2 n)$。二分查找算法的时间复杂度为 $O(\log_2 n)$。 +- **时间复杂度**:$O(n)$。 - **空间复杂度**:$O(1)$。只用到了常数空间存放若干变量。 diff --git "a/Solutions/0209. 351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" "b/Solutions/0209. 351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" index ae9568b6..fe3c8ebe 100644 --- "a/Solutions/0209. 351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" +++ "b/Solutions/0209. 351円225円277円345円272円246円346円234円200円345円260円217円347円232円204円345円255円220円346円225円260円347円273円204円.md" @@ -5,23 +5,43 @@ ## 题目大意 -给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 +**描述**:给定一个只包含正整数的数组 `nums` 和一个正整数 `target`。 -要求:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`。 +**要求**:找出数组中满足和大于等于 `target` 的长度最小的「连续子数组」,并返回其长度。如果不存在符合条件的子数组,返回 `0`。 + +**说明**: + +- 1ドル \le target \le 10^9$。 +- 1ドル \le nums.length \le 10^5$。 +- 1ドル \le nums[i] \le 10^5$。 + +**示例**: + +```Python +输入:target = 7, nums = [2,3,1,2,4,3] +输出:2 +解释:子数组 [4,3] 是该条件下的长度最小的子数组。 + + +输入:target = 4, nums = [1,4,4] +输出:1 +``` ## 解题思路 +### 思路 1:滑动窗口(不定长度) + 最直接的做法是暴力枚举,时间复杂度为 $O(n^2)$。但是我们可以利用滑动窗口的方法,在时间复杂度为 $O(n)$ 的范围内解决问题。 用滑动窗口来记录连续子数组的和,设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口中的和刚好大于等于 `target`。 -- 一开始,`left`、`right` 都指向 `0`。 -- 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。 -- 如果 `window_sum>= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`。 -- 然后继续右移 `right`,直到 `right>= len(nums)` 结束。 -- 输出窗口和的最小值作为答案。 +1. 一开始,`left`、`right` 都指向 `0`。 +2. 向右移动 `right`,将最右侧元素加入当前窗口和 `window_sum` 中。 +3. 如果 `window_sum>= target`,则不断右移 `left`,缩小滑动窗口长度,并更新窗口和的最小值,直到 `window_sum < target`。 +4. 然后继续右移 `right`,直到 `right>= len(nums)` 结束。 +5. 输出窗口和的最小值作为答案。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -45,3 +65,8 @@ class Solution: return ans if ans != size + 1 else 0 ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0220. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 III.md" "b/Solutions/0220. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 III.md" index 59233f14..71668423 100644 --- "a/Solutions/0220. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 III.md" +++ "b/Solutions/0220. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 III.md" @@ -29,20 +29,29 @@ ## 解题思路 -### 思路 1:桶排序 +题目中需要满足两个要求,一个是元素值的要求(`abs(nums[i] - nums[j]) <= t`) ,一个是下标范围的要求(`abs(i - j) <= k`)。 + +对于任意一个位置 `i` 来说,合适的 `j` 应该在区间 `[i - k, i + k]` 内,同时 `nums[j]` 值应该在区间 `[nums[i] - t, nums[i] + t]` 内。 + +最简单的做法是两重循环遍历数组,第一重循环遍历位置 `i`,第二重循环遍历 `[i - k, i + k]` 的元素,判断是否满足 `abs(nums[i] - nums[j]) <= t`。但是这样做的时间复杂度为 $O(n \times k),ドル其中 $n$ 是数组 `nums` 的长度。 -对于数组第 `i` 个元素 `nums[i]`,需要查找的区间为 `[i - t, i + t]`。我们可以利用桶排序的思想。 +我们需要优化一下检测相邻 `2 * k` 个元素是否满足 `abs(nums[i] - nums[j]) <= t` 的方法。有两种思路:「桶排序」和「滑动窗口(固定长度)」。 -桶的大小设置为 `t + 1`。我们将元素按照大小依次放入不同的桶中。 +### 思路 1:桶排序 -遍历数组 `nums` 中的元素,对于元素` nums[i]` : +1. 利用桶排序的思想,将桶的大小设置为 `t + 1`。只需要使用一重循环遍历位置 `i`,然后根据 `nums[i] // (t + 1)`,从而决定将 `nums[i]` 放入哪个桶中。 +2. 这样在同一个桶内各个元素之间的差值绝对值都小于等于 `t`。而相邻桶之间的元素,只需要校验一下两个桶之间的差值是否不超过 `t`。这样就可以以 $O(1)$ 的时间复杂度检测相邻 `2 * k` 个元素是否满足 `abs(nums[i] - nums[j]) <= t`。 +3. 而 `abs(i - j) <= k` 条件则可以通过在一重循环遍历时,将超出范围的 `nums[i - k]` 从对应桶中删除,从而保证桶中元素一定满足 `abs(i - j) <= k`。 -- 如果 `nums[i]` 放入桶之前桶里已经有元素了,那么这两个元素必然满足 `abs(nums[i] - nums[j]) <= t`, -- 如果之前桶里没有元素,那么就将 `nums[i]` 放入对应桶中。 -- 然后再判断左右桶的左右两侧桶中是否有元素满足 `abs(nums[i] - nums[j]) <= t`。 -- 然后将 `nums[i - k]` 之前的桶清空,因为这些桶中的元素与 `nums[i]` 已经不满足 `abs(i - j) <= k` 了。 +具体步骤如下: -最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 +1. 将每个桶的大小设置为 `t + 1`。我们将元素按照大小依次放入不同的桶中。 +2. 遍历数组 `nums` 中的元素,对于元素` nums[i]` : + 1. 如果 `nums[i]` 放入桶之前桶里已经有元素了,那么这两个元素必然满足 `abs(nums[i] - nums[j]) <= t`, + 2. 如果之前桶里没有元素,那么就将 `nums[i]` 放入对应桶中。 + 3. 再判断左右桶的左右两侧桶中是否有元素满足 `abs(nums[i] - nums[j]) <= t`。 + 4. 然后将 `nums[i - k]` 之前的桶清空,因为这些桶中的元素与 `nums[i]` 已经不满足 `abs(i - j) <= k` 了。 +3. 最后上述满足条件的情况就返回 `True`,最终遍历完仍不满足条件就返回 `False`。 ### 思路 1:代码 @@ -79,3 +88,57 @@ class Solution: - **时间复杂度**:$O(n)$。$n$ 是给定数组长度。 - **空间复杂度**:$O(min(n, k))$。桶中最多包含 $min(n, k + 1)$ 个元素。 +### 思路 2:滑动窗口(固定长度) + +1. 使用一个长度为 `k` 的滑动窗口,每次遍历到 `nums[right]` 时,滑动窗口内最多包含 `nums[right]` 之前最多 `k` 个元素。只需要检查前 `k` 个元素是否在 `[nums[right] - t, nums[right] + t]` 区间内即可。 +2. 检查 `k` 个元素是否在 `[nums[right] - t, nums[right] + t]` 区间,可以借助保证有序的数据结构(比如 `SortedList`)+ 二分查找来解决,从而减少时间复杂度。 + +具体步骤如下: + +1. 使用有序数组类 `window` 维护一个长度为 `k` 的窗口,满足数组内元素有序,且支持增加和删除操作。 +2. `left`、`right` 都指向序列的第一个元素。即:`left = 0`,`right = 0`。 +3. 将当前元素填入窗口中,即 `window.add(nums[right])`。 +4. 当窗口元素大于 `k` 个时,即 `right - left> k`,移除窗口最左侧元素,并向右移动 `left`。 +5. 当窗口元素小于等于 `k` 个时: + 1. 使用二分查找算法,查找 `nums[right]` 在 `window` 中的位置 `idx`。 + 2. 判断 `window[idx]` 与相邻位置上元素差值绝对值,若果满足 `abs(window[idx] - window[idx - 1]) <= t` 或者 `abs(window[idx + 1] - window[idx]) <= t` 时返回 `True`。 +6. 向右移动 `right`。 +7. 重复 `3` ~ `6` 步,直到 `right` 到达数组末尾,如果还没找到满足条件的情况,则返回 `False`。 + +### 思路 2:代码 + +```Python +from sortedcontainers import SortedList + +class Solution: + def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: + size = len(nums) + window = SortedList() + left, right = 0, 0 + while right < size: + window.add(nums[right]) + + if right - left> k: + window.remove(nums[left]) + left += 1 + + idx = bisect.bisect_left(window, nums[right]) + + if idx> 0 and abs(window[idx] - window[idx - 1]) <= t: + return True + if idx < len(window) - 1 and abs(window[idx + 1] - window[idx]) <= t: + return True + + right += 1 + + return False +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \log_2(min(n, k)))$。 +- **空间复杂度**:$O(min(n, k))$。 + +## 参考资料 + +- 【题解】[利用桶的原理O(n),Python3 - 存在重复元素 III - 力扣](https://leetcode.cn/problems/contains-duplicate-iii/solution/li-yong-tong-de-yuan-li-onpython3-by-zhou-pen-chen/) diff --git "a/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" "b/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" index 51027d28..a916fb62 100644 --- "a/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" +++ "b/Solutions/0283. 347円247円273円345円212円250円351円233円266円.md" @@ -7,7 +7,7 @@ **描述**:给定一个数组 `nums`。 -**要求**:将所有 0 移动到末尾,并保持原有的非 0 数字的相对顺序。 +**要求**:将所有 `0` 移动到末尾,并保持原有的非 `0` 数字的相对顺序。 **说明**: @@ -52,11 +52,11 @@ class Solution: - **时间复杂度**:$O(n^2)$。 - **空间复杂度**:$O(1)$。 -### 思路 2:双指针 +### 思路 2:快慢指针 -1. 使用两个指针 `left`,`right`。`left` 指向处理好的非 `0` 数字数组的尾部,`right` 指针指向当前待处理元素。 -2. 不断向右移动 `right` 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 `left` 右移。 -3. 此时,`left` 指针左侧均为处理好的非零数,而从 `left` 指针指向的位置开始, `right` 指针左边为止都为 `0`。 +1. 使用两个指针 `slow`,`fast`。`slow` 指向处理好的非 `0` 数字数组的尾部,`fast` 指针指向当前待处理元素。 +2. 不断向右移动 `fast` 指针,每次移动到非零数,则将左右指针对应的数交换,交换同时将 `slow` 右移。 +3. 此时,`slow` 指针左侧均为处理好的非零数,而从 `slow` 指针指向的位置开始, `fast` 指针左边为止都为 `0`。 遍历结束之后,则所有 `0` 都移动到了右侧,且保持了非零数的相对位置。 @@ -65,13 +65,13 @@ class Solution: ```Python class Solution: def moveZeroes(self, nums: List[int]) -> None: - left = 0 - right = 0 - while right < len(nums): - if nums[right] != 0: - nums[left], nums[right] = nums[right], nums[left] - left += 1 - right += 1 + slow = 0 + fast = 0 + while fast < len(nums): + if nums[fast] != 0: + nums[slow], nums[fast] = nums[fast], nums[slow] + slow += 1 + fast += 1 ``` ### 思路 2:复杂度分析 diff --git "a/Solutions/0344. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円.md" "b/Solutions/0344. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円.md" index 1eb206dc..6f241478 100644 --- "a/Solutions/0344. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円.md" +++ "b/Solutions/0344. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円.md" @@ -5,18 +5,49 @@ ## 题目大意 -给定一个字符串数组,将其反转。要求不能使用额外的数组空间。 +**描述**:给定一个字符数组 `s`。 + +**要求**:将其反转。 + +**说明**: + +- 不能使用额外的数组空间,必须原地修改输入数组、使用 $O(1)$ 的额外空间解决问题。 +- 1ドル \le s.length \le 10^5$。 +- `s[i]` 都是 ASCII 码表中的可打印字符。 + +**示例**: + +```Python +输入:s = ["h","e","l","l","o"] +输出:["o","l","l","e","h"] + + +输入:s = ["H","a","n","n","a","h"] +输出:["h","a","n","n","a","H"] +``` ## 解题思路 -简单题,遍历字符串,将 s[i] 与 s[len-1-i] 交换即可。 +### 思路 1:对撞指针 -## 代码 +1. 使用两个指针 `left`,`right`。`left` 指向字符数组开始位置,`right` 指向字符数组结束位置。 +2. 交换 `s[left]` 和 `s[right]`,将 `left` 右移、`right` 左移。 +3. 如果遇到 `left == right`,跳出循环。 + +### 思路 1:代码 ```Python -def reverseString(self, s: List[str]) -> None: - n = len(s) - for i in range(n//2): - s[i], s[n-1-i] = s[n-1-i], s[i] +class Solution: + def reverseString(self, s: List[str]) -> None: + left, right = 0, len(s) - 1 + while left < right: + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/0349. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円.md" "b/Solutions/0349. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円.md" index a2d1ffe3..751ee17c 100644 --- "a/Solutions/0349. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円.md" +++ "b/Solutions/0349. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円.md" @@ -5,30 +5,35 @@ ## 题目大意 -给定两个数组,编写一个函数来计算它们的交集。重复元素只计算一次。 +**描述**:给定两个数组 `nums1` 和 `nums2`。 -## 解题思路 +**要求**:返回两个数组的交集。重复元素只计算一次。 + +**说明**: -### 思路一:哈希表 +- 1ドル \le nums1.length, nums2.length \le 1000$。 +- 0ドル \le nums1[i], nums2[i] \le 1000$。 -先遍历第一个数组,利用哈希表来存放第一个数组的元素,对应字典值设为 1。 +**示例**: -然后遍历第二个数组,如果哈希表中存在该元素,则将该元素加入到答案数组中,并且将该键值清空。 +```Python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2] +示例 2: -### 思路二:双指针 +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[9,4] +解释:[4,9] 也是可通过的 +``` -使用分离双指针求解,具体步骤如下: +## 解题思路 -- 对数组 `nums1`、`nums2` 先排序。 -- 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组的第一个元素,即:`left_2 = 0`。 -- 如果 `nums1[left_1]` 等于 `nums2[left_2]`,则将其加入答案数组(注意去重),并将 `left_1` 和 `left_2` 右移。 -- 如果 `nums1[left_2]` 小于 `nums2[left_2]`,则将 `left_1` 右移。 -- 如果 `nums1[left_2]` 大于 `nums2[left_2]`,则将 `left_2` 右移。 -- 最后输出答案数组。 +### 思路 1:哈希表 -## 代码 +1. 先遍历第一个数组,利用哈希表来存放第一个数组的元素,对应字典值设为 `1`。 +2. 然后遍历第二个数组,如果哈希表中存在该元素,则将该元素加入到答案数组中,并且将该键值清空。 -- 思路一:哈希表 +### 思路 1:代码 ```Python class Solution: @@ -45,7 +50,21 @@ class Solution: return nums ``` -- 思路二:双指针 +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + +### 思路 2:分离双指针 + +1. 对数组 `nums1`、`nums2` 先排序。 +2. 使用两个指针 `left_1`、`left_2`。`left_1` 指向第一个数组的第一个元素,即:`left_1 = 0`,`left_2` 指向第二个数组的第一个元素,即:`left_2 = 0`。 +3. 如果 `nums1[left_1]` 等于 `nums2[left_2]`,则将其加入答案数组(注意去重),并将 `left_1` 和 `left_2` 右移。 +4. 如果 `nums1[left_2]` 小于 `nums2[left_2]`,则将 `left_1` 右移。 +5. 如果 `nums1[left_2]` 大于 `nums2[left_2]`,则将 `left_2` 右移。 +6. 最后输出答案数组。 + +### 思路 2:代码 ```Python class Solution: @@ -68,3 +87,8 @@ class Solution: left_2 += 1 return res ``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0473. 347円201円253円346円237円264円346円213円274円346円255円243円346円226円271円345円275円242円.md" "b/Solutions/0473. 347円201円253円346円237円264円346円213円274円346円255円243円346円226円271円345円275円242円.md" index 0c8de6f8..16aa37fd 100644 --- "a/Solutions/0473. 347円201円253円346円237円264円346円213円274円346円255円243円346円226円271円345円275円242円.md" +++ "b/Solutions/0473. 347円201円253円346円237円264円346円213円274円346円255円243円346円226円271円345円275円242円.md" @@ -5,30 +5,54 @@ ## 题目大意 -给定一个表示火柴长度的数组 `matchsticks`,其中 `matchsticks[i]` 表示第 `i` 根火柴的长度。 +**描述**:给定一个表示火柴长度的数组 `matchsticks`,其中 `matchsticks[i]` 表示第 `i` 根火柴的长度。 -要求:找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以将火柴连接起来,并且每根火柴都要用到。如果能拼成正方形,则返回 `True`,否则返回 `False`。 +**要求**:找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以将火柴连接起来,并且每根火柴都要用到。如果能拼成正方形,则返回 `True`,否则返回 `False`。 + +**说明**: + +- 1ドル \le matchsticks.length \le 15$。 +- 1ドル \le matchsticks[i] \le 10^8$。 + +**示例**: + +```Python +输入: matchsticks = [1,1,2,2,2] +输出: True +解释: 能拼成一个边长为 2 的正方形,每边两根火柴。 + + +输入: matchsticks = [3,3,3,3,4] +输出: False +解释: 不能用所有火柴拼成一个正方形。 +``` ## 解题思路 -- 先排除数组为空和火柴总长度不是 4 的倍数的情况,直接返回 `False`。 -- 然后将火柴按照从大到小排序。用数组 `sums` 记录四个边长分组情况。 -- 将火柴分为四组,把每一根火柴依次向 4 条边上放。直到放置最后一根,判断能否构成正方形,若能构成正方形,则返回 `True`,否则返回 `False`。 +### 思路 1:回溯算法 + +1. 先排除数组为空和火柴总长度不是 `4` 的倍数的情况,直接返回 `False`。 +2. 然后将火柴按照从大到小排序。用数组 `sums` 记录四个边长分组情况。 +3. 将火柴分为 `4` 组,把每一根火柴依次向 `4` 条边上放。 +4. 直到放置最后一根,判断能否构成正方形,若能构成正方形,则返回 `True`,否则返回 `False`。 -## 代码 +### 思路 1:代码 ```Python class Solution: def dfs(self, index, sums, matchsticks, size, side_len): if index == size: - return sums[0] == sums[1] == sums[2] == side_len + return True for i in range(4): - if sums[i] + matchsticks[index] <= side_len: - sums[i] += matchsticks[index] - if self.dfs(index + 1, sums, matchsticks, size, side_len): - return True - sums[i] -= matchsticks[index] + # 如果两条边的情况相等,只需要计算一次,没必要多次重复计算 + if i> 0 and sums[i] == sums[i - 1]: + continue + sums[i] += matchsticks[index] + if sums[i] <= side_len and self.dfs(index + 1, sums, matchsticks, size, side_len): + return True + sums[i] -= matchsticks[index] + return False def makesquare(self, matchsticks: List[int]) -> bool: @@ -46,3 +70,8 @@ class Solution: return self.dfs(0, sums, matchsticks, size, side_len) ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(4^n)$。$n$ 是火柴的数目。 +- **空间复杂度**:$O(n)$。递归栈的空间复杂度为 $O(n)$。 + diff --git "a/Solutions/0674. 346円234円200円351円225円277円350円277円236円347円273円255円351円200円222円345円242円236円345円272円217円345円210円227円.md" "b/Solutions/0674. 346円234円200円351円225円277円350円277円236円347円273円255円351円200円222円345円242円236円345円272円217円345円210円227円.md" index d43f7522..8f7d7ed9 100644 --- "a/Solutions/0674. 346円234円200円351円225円277円350円277円236円347円273円255円351円200円222円345円242円236円345円272円217円345円210円227円.md" +++ "b/Solutions/0674. 346円234円200円351円225円277円350円277円236円347円273円255円351円200円222円345円242円236円345円272円217円345円210円227円.md" @@ -73,3 +73,41 @@ class Solution: - **时间复杂度**:$O(n)$。一重循环遍历的时间复杂度为 $O(n),ドル最后求最大值的时间复杂度是 $O(n),ドル所以总体时间复杂度为 $O(n)$。 - **空间复杂度**:$O(n)$。用到了一维数组保存状态,所以总体空间复杂度为 $O(n)$。 +### 思路 2:滑动窗口(不定长度) + +1. 设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内为连续递增序列。使用 `window_len` 存储当前窗口大小,使用 `max_len` 维护最大窗口长度。 +2. 一开始,`left`、`right` 都指向 `0`。 +3. 将最右侧元素 `nums[right]` 加入当前连续递增序列中,即当前窗口长度加 `1`(`window_len += 1`)。 +4. 判断当前元素 `nums[right] `是否满足连续递增序列。 +5. 如果 `right> 0` 并且 `nums[right - 1]>= nums[right]` ,说明不满足连续递增序列,则将 `left` 移动到窗口最右侧,重置当前窗口长度为 `1`(`window_len = 1`)。 +6. 记录当前连续递增序列的长度,并更新最长连续递增序列的长度。 +7. 继续右移 `right`,直到 `right>= len(nums)` 结束。 +8. 输出最长连续递增序列的长度 `max_len`。 + +### 思路 2:代码 + +```Python +class Solution: + def findLengthOfLCIS(self, nums: List[int]) -> int: + size = len(nums) + left, right = 0, 0 + window_len = 0 + max_len = 0 + + while right < size: + window_len += 1 + + if right> 0 and nums[right - 1]>= nums[right]: + left = right + window_len = 1 + + max_len = max(max_len, window_len) + right += 1 + + return max_len +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/0713. 344円271円230円347円247円257円345円260円217円344円272円216円K347円232円204円345円255円220円346円225円260円347円273円204円.md" "b/Solutions/0713. 344円271円230円347円247円257円345円260円217円344円272円216円K347円232円204円345円255円220円346円225円260円347円273円204円.md" index 515452fd..fed4eed7 100644 --- "a/Solutions/0713. 344円271円230円347円247円257円345円260円217円344円272円216円K347円232円204円345円255円220円346円225円260円347円273円204円.md" +++ "b/Solutions/0713. 344円271円230円347円247円257円345円260円217円344円272円216円K347円232円204円345円255円220円346円225円260円347円273円204円.md" @@ -5,25 +5,40 @@ ## 题目大意 -给定一个正整数数组 `nums`和整数 `k` 。 +**描述**:给定一个正整数数组 `nums`和整数 `k` 。 -要求:找出该数组内乘积小于 `k` 的连续的子数组的个数。 +**要求**:找出该数组内乘积小于 `k` 的连续的子数组的个数。 -## 解题思路 +**说明**: -滑动窗口求解。 +- 1ドル \le nums.length \le 3 * 10^4$。 +- 1ドル \le nums[i] \le 1000$。 +- 0ドル \le k \le 10^6$。 -设定两个指针:`left`、`right`,分别指向滑动窗口的左右边界,保证窗口内所有数的乘积 `window_product` 都小于 `k`。使用 `window_product` 记录窗口中的乘积值,使用 `count` 记录符合要求的子数组个数。 +**示例**: -- 一开始,`left`、`right` 都指向 `0`。 +```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 的子数组。 -- 向右移动 `right`,将最右侧元素加入当前子数组乘积 `window_product` 中。 -- 如果 `window_product>= k` ,则不断右移 `left`,缩小滑动窗口长度,并更新当前乘积值 `window_product` 直到 `window_product < k`。 -- 记录累积答案个数 += 1,继续右移 `right`,直到 `right>= len(nums)` 结束。 -- 输出累积答案个数。 +输入:nums = [1,2,3], k = 0 +输出:0 +``` + +## 解题思路 -## 代码 +### 思路 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)` 结束。 +6. 输出累积答案个数。 + +### 思路 1:代码 ```Python class Solution: @@ -51,3 +66,8 @@ class Solution: return count ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/1004. 346円234円200円345円244円247円350円277円236円347円273円2551円347円232円204円344円270円252円346円225円260円 III.md" "b/Solutions/1004. 346円234円200円345円244円247円350円277円236円347円273円2551円347円232円204円344円270円252円346円225円260円 III.md" index c0d83beb..306f0efd 100644 --- "a/Solutions/1004. 346円234円200円345円244円247円350円277円236円347円273円2551円347円232円204円344円270円252円346円225円260円 III.md" +++ "b/Solutions/1004. 346円234円200円345円244円247円350円277円236円347円273円2551円347円232円204円344円270円252円346円225円260円 III.md" @@ -5,15 +5,41 @@ ## 题目大意 -给定一个由 0、1 组成的数组 nums,再给定一个整数 k。最多可以将 k 个值从 0 变到 1。返回仅包含 1 的最长连续子数组的长度。 +**描述**:给定一个由 `0`、`1` 组成的数组 `nums`,再给定一个整数 `k`。最多可以将 `k` 个值从 `0` 变到 `1`。 + +**要求**:返回仅包含 `1` 的最长连续子数组的长度。 + +**说明**: + +- 1ドル \le nums.length \le 10^5$。 +- `nums[i]` 不是 `0` 就是 `1`。 +- 0ドル \le k \le nums.length$。 + +**示例**: + +```Python +输入:nums = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], K = 2 +输出:6 +解释:[1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1] +将 nums[5]、nums[10] 从 0 翻转到 1,最长的子数组长度为 6。 + + +输入:nums = [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1], K = 3 +输出:10 +解释:[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1] +将 nums[4]、nums[5]、nums[9] 从 0 翻转到 1,最长的子数组长度为 10。 +``` ## 解题思路 -使用滑动窗口的方法来做。使用两个指针 left、right 指向数组开始位置。使用 max_count 来维护仅包含 1 的最长连续子数组的长度。 +### 思路 1:滑动窗口(不定长度) -不断右移 right 指针,扩大滑动窗口范围,并统计窗口内 0 元素的个数,直到 0 元素的个数超过 k 时将 left 右移,缩小滑动窗口范围,并减小 0 元素的个数。并维护 max_count。 +1. 使用两个指针 `left`、`right` 指向数组开始位置。使用 `max_count` 来维护仅包含 `1` 的最长连续子数组的长度。 +2. 不断右移 `right` 指针,扩大滑动窗口范围,并统计窗口内 `0` 元素的个数。 +3. 直到 `0` 元素的个数超过 `k` 时将 `left` 右移,缩小滑动窗口范围,并减小 `0` 元素的个数,同时维护 `max_count`。 +4. 最后输出最长连续子数组的长度 `max_count`。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -33,3 +59,8 @@ class Solution: return max_count ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 + diff --git "a/Solutions/1343. 345円244円247円345円260円217円344円270円272円 K 344円270円224円345円271円263円345円235円207円345円200円274円345円244円247円344円272円216円347円255円211円344円272円216円351円230円210円345円200円274円347円232円204円345円255円220円346円225円260円347円273円204円346円225円260円347円233円256円.md" "b/Solutions/1343. 345円244円247円345円260円217円344円270円272円 K 344円270円224円345円271円263円345円235円207円345円200円274円345円244円247円344円272円216円347円255円211円344円272円216円351円230円210円345円200円274円347円232円204円345円255円220円346円225円260円347円273円204円346円225円260円347円233円256円.md" index 0bf9636e..948b626c 100644 --- "a/Solutions/1343. 345円244円247円345円260円217円344円270円272円 K 344円270円224円345円271円263円345円235円207円345円200円274円345円244円247円344円272円216円347円255円211円344円272円216円351円230円210円345円200円274円347円232円204円345円255円220円346円225円260円347円273円204円346円225円260円347円233円256円.md" +++ "b/Solutions/1343. 345円244円247円345円260円217円344円270円272円 K 344円270円224円345円271円263円345円235円207円345円200円274円345円244円247円344円272円216円347円255円211円344円272円216円351円230円210円345円200円274円347円232円204円345円255円220円346円225円260円347円273円204円346円225円260円347円233円256円.md" @@ -5,12 +5,34 @@ ## 题目大意 -给你一个整数数组 `arr` 和两个整数 `k` 和 `threshold` 。 +**描述**:给定一个整数数组 `arr` 和两个整数 `k` 和 `threshold` 。 -要求:返回长度为 `k` 且平均值大于等于 `threshold` 的子数组数目。 +**要求**:返回长度为 `k` 且平均值大于等于 `threshold` 的子数组数目。 + +**说明**: + +- 1ドル \le arr.length \le 10^5$。 +- 1ドル \le arr[i] \le 10^4$。 +- 1ドル \le k \le arr.length$。 +- 0ドル \le threshold \le 10^4$。 + +**示例**: + +```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 的值)。 + + +输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 +输出:6 +解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。 +``` ## 解题思路 +### 思路 1:滑动窗口(固定长度) + 这道题目是典型的固定窗口大小的滑动窗口题目。窗口大小为 `k`。具体做法如下: 1. `ans` 用来维护答案数目。`window_sum` 用来维护窗口中元素的和。 @@ -20,10 +42,9 @@ 1. 如果满足,则答案数目 + 1。 2. 然后向右移动 `left`,从而缩小窗口长度,即 `left += 1`,使得窗口大小始终保持为 `k`。 5. 重复 3 ~ 4 步,直到 `right` 到达数组末尾。 +6. 最后输出答案数目。 -最后输出答案数目。 - -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -47,3 +68,8 @@ class Solution: return ans ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 +