209. 长度最小的子数组
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回 0
。
Solution: 本题使用滑动窗口思想,窗口内的元素和sum
与目标target
进行比较,如果满足要求,则更新当前最短的结果子数组长度,并且窗口左指针向右移动,试图寻找更短的子数组;一旦窗口元素和小于target
,那么尝试右移右指针,重新寻找满足要求的子数组。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int sum = 0, i = 0, j = i, result = Integer.MAX_VALUE;
for (; i < nums.length; ++i) {
sum += nums[i];
while (sum >= target) {
if (i - j + 1 < result) result = i - j + 1;
sum -= nums[j++];
}
}
return (result != Integer.MAX_VALUE) ? result : 0;
}
}
904. 水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
Solution: 本题略为复杂,下面展示两种解法。
解法一: 使用双指针滑动窗口,开始指针st
和结束指针end
,窗口范围内是可摘的水果,fruit1
和fruit2
分别代表当前采摘水果类型,count1
和count2
分别代表两种类型水果当前采摘数量。当end
向右移动时,有三种情况:
end
指向水果为fruit1
或者fruit2
,说明当前水果可摘,那么对应的数量+1
。fruit1
和fruit2
至少一个为空(此处记为-1
),说明新的可摘水果类型来到,那么将end
指向水果填入,对应count
置为1。fruit1
和fruit2
均不为空, 且end
指向第三种水果(非fruit1
或fruit2
),那么需要从原来两种水果类型中删去一种,秉持数量最大原则,这里将st
右移,并依次将st
指向的水果类型对应采摘数量-1
,直到某一水果采摘数量(count1
或count2
)为0为止,此时置对应的水果类型为空,然后重新指向end
代表的水果并将对应数量置为1,则窗口内仍然只有两种水果。
注意,在每次进入情况三的处理之前,需要计算窗口大小来更新当前最大采摘水果数量,而最后返回的是max(ans, end - st + 1) - 1
是因为end
遍历结束时窗口超出数组一个位置。 同时,这里的情况2可以合并到情况3处理完成后,因为它们都需要填入新水果类型。
class Solution {
public int totalFruit(int[] fruits) {
int fruit1 = -1, fruit2 = -1, count1 = 0, count2 = 0;
int st = 0, end = 0, ans = 0;
for (end = 0; end < fruits.length; ++end) {
if (fruit1 == fruits[end]) ++count1;
else if (fruit2 == fruits[end]) ++count2;
else {
if (fruit1 != -1 && fruit2 != -1) {
ans = Math.max(ans, end - st + 1);
while (st < end && count1 > 0 && count2 > 0) {
if (fruits[st] == fruit1) --count1;
else if (fruits[st] == fruit2) --count2;
if (count1 == 0) fruit1 = -1;
else if (count2 == 0) fruit2 = -1;
++st;
}
}
if (fruit1 == -1) {
fruit1 = fruits[end];
count1 = 1;
} else {
fruit2 = fruits[end];
count2 = 1;
}
}
}
return Math.max(ans, end - st + 1) - 1;
}
}
解法二: 同滑动指针,与解法一的区别在于,删去count1
和count2
,在情况三中对st
的右移改为从st = end - 1
处左移,直到st
和st - 1
指向不同类型水果为止,由于窗口内始终只有两种水果,此时就意味着st - 1
指向的水果将被抛弃,新的水果类型是fruits[st]
和fruits[end]
,这样做避免了从左到右移动st
的冗余以及额外的count
计数,降低了耗时。另外由于不需要count
来计数,那么剩余情况只剩下:
- 某一种采摘水果类型为空,则填入
fruits[end]
。 - 两种采摘水果类型均不为空,且
fruits[end]
是新的类型,那么左移st
直到找到要抛弃的水果类型,并更新当前采摘水果类型为fruits[st]
和fruits[end]
。
class Solution {
public int totalFruit(int[] fruits) {
int st = 0, end = 0;
int fruit1 = -1, fruit2 = -1;
int ans = 0;
while(end < fruits.length){
if (fruit1 == -1){
fruit1 = fruits[end];
} else if (fruit2 == -1 && fruits[end] != fruit1) {
fruit2 = fruits[end];
} else if (fruits[end] != fruit1 && fruits[end] != fruit2) {
ans = Math.max(ans, end - st + 1);
st = end - 1;
while (fruits[st] == fruits[st - 1]){
--st;
}
fruit1 = fruits[st];
fruit2 = fruits[end];
}
++end;
}
return Math.max(ans, end - st + 1) - 1;
}
}
76. 最小覆盖子串
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
Solution: 使用双指针,首先遍历字符串t
,保存其各个字符出现频率count
(此处由于字符数量有限,可用哈希表也可直接用数组),来表示当前窗口满足要求所需要的各个字符频率,并保存t
的总长度记为t_num
,用于记录当前窗口满足要求所需要的字符数量。
然后使用双指针遍历字符串s
,右指针不断向右移动,每遇到一个字符则对应count
减去1(该字符进入了窗口)。如果该字符频率在-1后仍然≥0
,说明这个字符是窗口满足要求所需要的,应该从t_num
中减去1,否则该字符是多余的,不需要动t_num
。
如果t_num
变为0,那么当前窗口内字符串已经满足要求,更新最优覆盖子串的位置,并且开始右移左指针,遇到的字符其count
+1(因为该字符离开了窗口)。如果该字符频率在+1后>0
,那么窗口内该字符数量不够了,需要将t_num
加上1,否则窗口内该字符数量足够,不需要动t_num
。一旦t_num
不再为0,那么当前窗口不满足要求,需要回到右指针的右移过程。
注意: 如果使用哈希表存储count
,那么在增减count
之前需要判断指针指向的字符是否在表内,以避免无关字符进入表内,增加无谓空间消耗;如果使用数组储存则不需要这一过程。实际上无论采用哪种储存方式,无关字符其实对整个过程都不产生影响,因为在右指针右移过程中,无关字符的频率不可能≥0
,同样左指针右移时,无关字符频率也不可能>0
。
class Solution {
public String minWindow(String s, String t) {
int n = s.length(), t_num = t.length();
int[] count = new int[52];
for (char x : t.toCharArray()) {
++count[getIdx(x)];
}
int best_l = 0, best_len = Integer.MAX_VALUE;
for (int l = 0, r = 0; r < n; r++) {
int idx_r = getIdx(s.charAt(r));
if (--count[idx_r] >= 0) --t_num;
while (t_num == 0) {
if (r - l + 1 < best_len) {
best_l = l;
best_len = r - l + 1;
}
int idx_l = getIdx(s.charAt(l++));
if (++count[idx_l] > 0) ++t_num;
}
}
return (best_len == Integer.MAX_VALUE) ? "" : s.substring(best_l, best_l + best_len);
}
int getIdx(char x) {
return x >= 'A' && x <= 'Z' ? x - 'A' + 26 : x - 'a';
}
}