1、LeetCode 209 长度最小的子数组
题目分析:
初看这个题目,可以使用暴力解法来解决,主要思路如下:
外层循环遍历数组的每个元素,内层循环从这个元素之后往后面遍历,并累计求和,一旦发现和sum大于target,此时就可以得到这个子数组的长度,不断更新。
这里的更新方法很巧妙:我们需要一个最小值,那么我们就设置一个result=很大,这个很大就是int数值类型的上线INT32_MAX。一旦我们得到的子数组的长度小于这个result,那么就不断更新即可。
我们这里也使用了三目运算符进行更新,因为三目运算符比if-else更快。
但是!!!,暴力算法在本题中超时了,因此要使用一些别的方法。
由于本质上还是要遍历数组所有的元素,我们还是使用双指针法来进行尝试,并且称这个双指针法为滑动窗口法。
题目解答:
方法一:暴力法(超出时间限制)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// 使用暴力解法
int sum = 0;
int subLen = 0; // 子数组的长度
int result = INT32_MAX; // 最终的结果
for (int i = 0; i < nums.size(); i++) {
sum = 0; // 每一个循环里面sum都要等于0
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
if (sum >= target) {
subLen = j - i + 1;
// 这里巧妙地使用了一个三目运算符,并且设置了int的上限
result = result < subLen ? result : subLen;
break;
}
}
}
return result == INT32_MAX ? 0 : result; // 最后的返回也是使用三目运算符
}
};
方法二:滑动窗口法(双指针法)
滑动窗口法的精华是动态更新窗口的界限以及窗口内部的和。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int winLeft = 0; // 窗口左端
int winRight = 0; // 窗口右端
int result = INT32_MAX; // 最终的结果
int sum = 0; // 窗口内元素的和
int subLen = 0;
while (winRight < nums.size()) {
sum += nums[winRight];
// 注意这里为什么是while而不是if,一定要想想清楚
while (sum >= target) {
subLen = winRight - winLeft + 1; // 计算滑动窗的长度
sum -= nums[winLeft++]; // 这里是滑动窗口的精华所在
result = result < subLen ? result : subLen; // 进行结果的更新
}
winRight++; // 滑动窗口右边界更新
}
// 如果result没动
return result == INT32_MAX ? 0 : result;
}
};
这里的难点就在于滑动窗的更新那里,使用的while而不是if,下面举例说明:
如果数组为 1 1 1 1 100, target = 101, 那么当left在第一个1的时候就已经满足105 > 101了,此时得到的子数组长度是5,但是明显最小的字数粗长度是2,所以这种情况就错误了!
必须使用while而不是if。
即使使用while作为判断条件,算法的时间复杂度还是O(n),空间复杂度O(1)。
这个时间复杂度我之后再研究研究。
day3的内容23日还需要进行补充!
203.2.23日进行补充
2、LeetCode 209 长度最小的子数组
题目分析:
本题还是牵涉到遍历数组的问题,还是可以使用滑动窗口来解决。具体的思路在代码中进行体现。
class Solution {
public:
int totalFruit(vector<int>& fruits) {
set<int> classes; // 存放水果的种类
int fast = 0; // 快指针,依次遍历数组中的所有元素,一旦出现第三个类别,回退到第二个
int slow = 0; // 慢指针,始终指向第一个类,方便fast回退
int type = 0; // 篮子中水果的种类
int result = 0; // 最终的结果
int subLen = 0; // 子数组的长度
while (fast < fruits.size()) {
classes.insert(fruits[fast]);
type = classes.size();
if (type <= 2) {
subLen++; // 子数组的长度增加
if (type == 1) slow++; // slow只会指向第一个类
fast++; // 继续往后遍历
result = result > subLen ? result : subLen;
}
else {
// 此时的slow由于刚刚经过了++,现在已经指向第二个类了
subLen = 0;
fast = slow - 1; // 快指针回退到慢指针的位置,也就是第二个类了,但是待会儿还要加1,所以这里减1
classes.erase(classes.begin(), classes.end()); // 清除类别列表
}
}
return result == 0 ? 0 : result;
}
};
3、LeetCode 76 最小覆盖字串
本题还需要思考,做出来了更新。
总结:今天涉及的题目解决的方法是滑动时间窗,本质上还是双指针的应用。
注意第一个题的时间窗的更新,用的是while作为判断条件,而不是if,想明白原因。