文章目录
滑动窗口
滑动窗口算法是一种常用的算法技巧,适用于处理数组或字符串中的连续子序列问题。它通过动态调整窗口的大小和位置,能够在线性时间内解决许多复杂的子数组或子字符串问题。滑动窗口的核心思想是利用两个指针来表示窗口的左右边界,通过移动这两个指针来维护和调整窗口,使得窗口内的数据满足特定的条件。
滑动窗口的基本步骤
- 初始化:设定窗口的初始位置。通常情况下,左指针和右指针都从数组或字符串的起始位置开始。
- 扩展窗口:移动右指针以扩展窗口,直到窗口内的数据满足特定条件。
- 收缩窗口:在窗口满足条件的情况下,移动左指针以收缩窗口,同时检查并更新结果,以优化结果。
- 重复步骤2-3:直到右指针遍历完整个数组或字符串。
滑动窗口的两种常见类型
- 固定大小窗口:窗口大小固定,通过移动窗口的位置来检查每一个固定大小的子数组或子字符串。
- 可变大小窗口:窗口大小可变,通过调整左右指针的位置来动态改变窗口的大小,直到找到满足条件的最优解。
2024.5.27(3题、定长滑动窗口)
1456. 定长子串中元音的最大数目
给你字符串 s
和整数 k
。
请返回字符串 s
中长度为 k
的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a
, e
, i
, o
, u
)。
示例 1:
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。
示例 2:
输入:s = "aeiou", k = 2
输出:2
解释:任意长度为 2 的子字符串都包含 2 个元音字母。
示例 3:
输入:s = "leetcode", k = 3
输出:2
解释:"lee"、"eet" 和 "ode" 都包含 2 个元音字母。
示例 4:
输入:s = "rhythms", k = 4
输出:0
解释:字符串 s 中不含任何元音字母。
示例 5:
输入:s = "tryhard", k = 4
输出:1
提示:
1 <= s.length <= 10^5
s
由小写英文字母组成1 <= k <= s.length
解题思路
- 使用一个滑动窗口,窗口大小固定为
k
。 - 初始化窗口,计算第一个窗口内的元音字母数。
- 通过移动窗口(即右移左边界和右边界),更新窗口内的元音字母数,同时记录出现的最大元音字母数。
Java代码实现
class Solution {
public int maxVowels(String s, int k) {
// 用字符串表示元音字母集,便于查找
String vowels = "aeiou";
int maxVowels = 0;
int currentVowels = 0;
// 初始化窗口,计算第一个窗口内的元音字母数
for (int i = 0; i < k; i++) {
if (vowels.indexOf(s.charAt(i)) != -1) {
currentVowels++;
}
}
maxVowels = currentVowels;
// 通过移动窗口更新元音字母数并记录最大值
for (int i = k; i < s.length(); i++) {
// 移除左边界元素
if (vowels.indexOf(s.charAt(i - k)) != -1) {
currentVowels--;
}
// 添加右边界元素
if (vowels.indexOf(s.charAt(i)) != -1) {
currentVowels++;
}
// 更新最大元音字母数
maxVowels = Math.max(maxVowels, currentVowels);
}
return maxVowels;
}
}
详细解释
-
定义元音字母集:
- 使用字符串存储元音字母(a, e, i, o, u),便于快速查找。
-
初始化窗口:
- 计算字符串
s
中前k
个字符中的元音字母数,并将其存储在currentVowels
中。
- 计算字符串
-
滑动窗口:
- 从索引
k
开始遍历字符串s
,每次向右移动窗口:- 如果窗口移除的左边界字符是元音字母,
currentVowels
减一。 - 如果窗口添加的右边界字符是元音字母,
currentVowels
加一。
- 如果窗口移除的左边界字符是元音字母,
- 每次移动后,更新
maxVowels
,使其始终记录当前窗口中最多的元音字母数。
- 从索引
-
返回结果:
- 遍历结束后,返回
maxVowels
,即为字符串s
中长度为k
的子字符串中可能包含的最大元音字母数。
- 遍历结束后,返回
这个方法的时间复杂度为 O(n),其中 n 是字符串 s
的长度,适合处理长度较大的字符串。
2269. 找到一个数字的 K 美丽值
一个整数 num
的 k 美丽值定义为 num
中符合以下条件的 子字符串 数目:
- 子字符串长度为
k
。 - 子字符串能整除
num
。
给你整数 num
和 k
,请你返回 num
的 k 美丽值。
注意:
- 允许有 前缀 0 。
0
不能整除任何值。
一个 子字符串 是一个字符串里的连续一段字符序列。
示例 1:
输入:num = 240, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "240" 中的 "24" :24 能整除 240 。
- "240" 中的 "40" :40 能整除 240 。
所以,k 美丽值为 2 。
示例 2:
输入:num = 430043, k = 2
输出:2
解释:以下是 num 里长度为 k 的子字符串:
- "430043" 中的 "43" :43 能整除 430043 。
- "430043" 中的 "30" :30 不能整除 430043 。
- "430043" 中的 "00" :0 不能整除 430043 。
- "430043" 中的 "04" :4 不能整除 430043 。
- "430043" 中的 "43" :43 能整除 430043 。
所以,k 美丽值为 2 。
提示:
1 <= num <= 109
1 <= k <= num.length
(将num
视为字符串)
解题思路
- 将整数
num
转换为字符串形式,方便提取子字符串。 - 使用滑动窗口方法,窗口大小固定为
k
,从字符串的起始位置开始滑动。 - 对于每个窗口内的子字符串,检查其是否能整除
num
。 - 如果能整除,则计数器加一。
- 最终返回计数器的值。
Java代码实现
class Solution {
public int divisorSubstrings(int num, int k) {
String numStr = String.valueOf(num);
int length = numStr.length();
int kBeautyCount = 0;
for (int i = 0; i <= length - k; i++) {
String subStr = numStr.substring(i, i + k);
int subNum = Integer.parseInt(subStr);
// 排除子字符串为0的情况,因为0不能整除任何数
if (subNum != 0 && num % subNum == 0) {
kBeautyCount++;
}
}
return kBeautyCount;
}
}
详细解释
-
将整数转换为字符串:
String numStr = String.valueOf(num);
- 这样我们可以方便地获取
num
中的子字符串。
-
滑动窗口遍历:
- 使用一个
for
循环,遍历字符串numStr
,窗口大小为k
。 for (int i = 0; i <= numStr.length() - k; i++) { ... }
- 每次循环中,提取当前窗口的子字符串。
String subStr = numStr.substring(i, i + k);
- 使用一个
-
检查子字符串是否能整除
num
:- 将子字符串转换为整数。
int subNum = Integer.parseInt(subStr);
- 检查
subNum
是否不为 0 且能整除num
。 if (subNum != 0 && num % subNum == 0) { kBeautyCount++; }
-
返回结果:
- 最终返回计数器
kBeautyCount
,即为num
的k
美丽值。
- 最终返回计数器
这个方法的时间复杂度为 O(n),其中 n 是字符串 num
的长度,因为我们需要遍历每个长度为 k
的子字符串并进行检查。空间复杂度为 O(1),因为我们只使用了常数级别的额外空间。
1984. 学生分数的最小差值
给你一个 下标从 0 开始 的整数数组 nums
,其中 nums[i]
表示第 i
名学生的分数。另给你一个整数 k
。
从数组中选出任意 k
名学生的分数,使这 k
个分数间 最高分 和 最低分 的 差值 达到 最小化 。
返回可能的 最小差值 。
示例 1:
输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:
- [90] 最高分和最低分之间的差值是 90 - 90 = 0
可能的最小差值是 0
示例 2:
输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
- [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
可能的最小差值是 2
提示:
1 <= k <= nums.length <= 1000
0 <= nums[i] <= 105
解题思路
- 将数组
nums
排序。 - 使用滑动窗口遍历排序后的数组,对于每个窗口,计算最大值和最小值的差值。
- 记录并返回最小的差值。
Java代码实现
class Solution {
public int minimumDifference(int[] nums, int k) {
if (k == 1) {
return 0; // 如果 k 为 1,差值肯定为 0
}
Arrays.sort(nums); // 将数组排序
int minDifference = Integer.MAX_VALUE;
// 使用滑动窗口遍历排序后的数组
for (int i = 0; i <= nums.length - k; i++) {
int currentDifference = nums[i + k - 1] - nums[i];
minDifference = Math.min(minDifference, currentDifference);
}
return minDifference;
}
}
详细解释
- 边界情况处理:
- 如果
k
为 1,那么差值肯定为 0,因为只有一个元素。
- 如果
- 排序数组:
- 使用
Arrays.sort(nums)
对数组进行排序。排序后,相邻的元素差值最小。
- 使用
- 滑动窗口:
- 遍历排序后的数组,使用一个大小为
k
的滑动窗口计算差值。窗口起始位置为i
,终止位置为i + k - 1
。 int currentDifference = nums[i + k - 1] - nums[i];
计算当前窗口内的最大值和最小值之间的差值。- 记录最小的差值:
minDifference = Math.min(minDifference, currentDifference);
- 遍历排序后的数组,使用一个大小为
- 返回结果:
- 最终返回
minDifference
,即为可能的最小差值。
- 最终返回
这个算法的时间复杂度是 O(n log n),主要花费在数组的排序上。遍历数组的时间复杂度是 O(n),所以总体时间复杂度是 O(n log n)。空间复杂度是 O(1),因为我们只使用了常数级别的额外空间。