1. 长度最小的子数组(209)
题目描述:
算法原理:
这题使用滑动窗口的方法来进行解决,而滑动窗口实际上就是通过定义同向双指针的方式来实现的,两个指针就是窗口的左右边界,然后结合这样的思想来给出这题的解法。
题目给出的是一个正整数的数组,首先定义一对指针left以及right都是从0开始的,然后right开始向右移动(这显然可以通过一层循环实现),这就是nums数组中元素进窗口的过程。当我们窗口中的元素和大于等于target时,此时我们不去加元素进窗口了,此时right不移动,left向右移动,left左边的元素出窗口,一直循环直至窗口内的元素和小于等于target,此时right又开始移动,又开始有新元素进窗口,重复上述过程。因为题目要求返回最短的满足条件的子数组的长度,因此我们在发现满足大于等于target的窗口时要记录此时的长度并不断更新得到最终的结果。
至于为什么这样操作可以得到正确的结果,我们知道这题的暴力解法时通过遍历左右的各种可能,然后对左右的元素累加进行判断是否大于等于target,如果满足就更新最终返回的长度值。然后我们看这里的滑动窗口当我们最初得到满足条件的窗口时,此时有left和right指针,可以看成固定了left,去得到和left搭配的各种区间可能,最初满足条件的就是left和right搭配,如果此时right向右移只会增加满足条件的子数组长度,所以我们直接跳过后面的可能,left和right搭配就是固定left时子数组的最小长度,按照代码逻辑后面就是要进行left+1的操作并将left位置元素出窗口,此时后面的操作也类似于前面,就相当于固定left+1位置元素,然后去找满足条件的最小的子数组长度。综上,实际上滑动窗口也是遍历各种可能,但是它省略掉了一些不需要去进行遍历处理的可能所以提升了效率。
代码如下:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int sum = 0, n = nums.length, len = Integer.MAX_VALUE;
for (int left = 0, right = 0; right < n; right++) {
sum += nums[right]; // 进窗口
while (sum >= target) {
len = Math.min(len, right - left + 1); // 更新结果
sum -= nums[left]; // 出窗口
left++;
}
}
return len == Integer.MAX_VALUE ? 0 : len;
}
}
时间复杂度:
这题时间复杂度不需要去看代码,只需要去理解,因为我们使用了同向双指针,所以代码最多也就是2n的级别(n是nums数组的长度),所以时间复杂度为O(N)。
题目链接
2. 无重复字符的最长子串(3)
题目描述:
算法原理:
这题我们首先来想一下它的暴力解法,就是遍历这个字符串,然后以每一个字符为起点去不断的向右添加字符直至添加的过程中出现重复的字符,此时我们认为起始的字符的指针为left,向右添加字符的指针为right,那么这个过程就是先right一直++,直到出现重复的字符,此时left++并且right回到left的位置重新开始++直至出现重复的字符,我们不难发现这个过程是可以优化的。因为当出现重复字符时我们根本无需将right回到left的位置,我们只需要先判断s[left]的字符是否和s[right]的字符是否相等,如果相等那么将left++,此时left到right又是一个无重复字符的子串,我们就可以更新结果了。这也是一个滑动窗口问题,right++的过程就是进窗口,left–的过程就是出窗口。具体看代码。
代码如下:
class Solution {
public int lengthOfLongestSubstring(String ss) {
int n = ss.length();
char[] s = ss.toCharArray();
int[] count = new int[128];
int ret = 0;
for (int right = 0, left = 0; right < n; right++) {
count[s[right]]++;
while (count[s[right]] > 1) {
count[s[left]]--;
left++;
}
ret = Math.max(ret, right - left + 1);
}
return ret;
}
}
时间复杂度:
别看代码有两层的循环,但是实际上就是一个left以及right指针在s上的从左到右的移动,因此时间复杂度为O(N)。
题目链接
3. 最大连续1的个数 III(1004)
题目描述:
算法原理:
根据题目意思我们可以转换为去得到一个最长的子数组,要求子数组中的0的个数不能超过k个。这题就可以当作一个滑动窗口的问题来进行解决。
和前面类似,就是定义两个指针left以及right,right不断++直至0的个数超过k,如果是暴力解法那么将left++之后再将right回到left的位置。我们滑动窗口方法就是基于暴力解法来进行优化,当right++的过程是进窗口,当0的个数超过k,此时判断nums[lett]是否等于0,如果不等于0直接left++,这就是出窗口,如果等于0,那么也是left++(出窗口),但是此时left到right区间内0的数目就减掉一个了,因此left到right区间又是满足条件了,可以更新返回的结果,并且之后right也可以继续移动直至0的个数直至再次大于k,重复上述的过程。具体看代码。
代码如下:
class Solution {
public int longestOnes(int[] nums, int k) {
int n = nums.length;
int zero = 0; // 0的个数
int ret = 0; // 返回的最大个数
for (int right = 0, left = 0; right < n; right++) {
if (nums[right] == 0) {
zero++; // 进窗口的同时统计0的个数
}
while (zero > k) { // 判断是否达到最大0的个数
if (nums[left] == 0) {
zero--; // 出窗口
}
left++;
}
ret = Math.max(ret, right - left + 1); // 更新返回值
}
return ret;
}
}
题目链接
4. 将 x 减到 0 的最小操作数(1658)
题目描述:
算法原理:
根据题意我们知道需要每次都是从左边或者右边删去数字使得删去的数字总和为x,正难则反,我们反着思考,既然这样那么我们可以构建一个中间的子数组,使得这个子数组的总和加起来是等于数组总和减去x的值,这样找出这样中间子数组的长度之后,再将数组总长减去中间数组长度即可。
综上这题又可以转换成一个滑动窗口问题,只需要将中间的子数组或者说是窗口中的和等于数组和sum-x即可。当sum-x<0时直接返回-1,当窗口中的数字和大于sum-x时,此时right停止向右,left中数字出窗口即left++,然后如果此时窗口内的数字和仍然大于sum-x则重复上述过程,如果小于则right继续向右,直至子数组和重新大于,重复上述总体过程。至于更新返回值的时机就是在满足子数组即窗口内的数字和等于sum-x时。
代码如下:
class Solution {
public int minOperations(int[] nums, int x) {
int n = nums.length;
int sum = 0;
int aim = 0;
for (int num : nums) {
sum += num;
}
aim = sum - x;
if (aim < 0) {
return -1;
}
int ret = -1;
int tmp = 0;
for (int right = 0, left = 0; right < n; right++) {
tmp += nums[right];
while (tmp > aim) {
tmp -= nums[left];
left++;
}
if (tmp == aim) {
ret = Math.max(ret, right - left + 1);
}
}
return ret == -1 ? -1 : n - ret;
}
}
题目链接