904.水果成篮
题目
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits
表示,其中 fruits[i]
是第 i
棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits
,返回你可以收集的水果的 最大 数目。
示例 1:
输入:fruits = [1,2,1] 输出:3 解释:可以采摘全部 3 棵树。
示例 2:
输入:fruits = [0,1,2,2] 输出:3 解释:可以采摘 [1,2,2] 这三棵树。 如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。
示例 3:
输入:fruits = [1,2,3,2,2] 输出:4 解释:可以采摘 [2,3,2,2] 这四棵树。 如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。
示例 4:
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4] 输出:5 解释:可以采摘 [1,2,1,1,2] 这五棵树。
代码(哈希表)
class Solution {
public int totalFruit(int[] fruits) {
HashMap<Integer,Integer> map = new HashMap<>();
int left = 0; //滑动窗口左区间
int right = 0; //滑动窗口右区间
int ans = -1; //长度
//right遍历数组
while(right < fruits.length){
//把right加入窗口(right可能不存在)
map.put(fruits[right],map.getOrDefault(fruits[right],0)+1);
//只要水果种类大于2,不断缩小窗口,左区间右移
while(map.size() > 2){
//left右移
map.put(fruits[left],map.get(fruits[left])-1); //left的key对应的value减1
//如果left长度为0,从hash表中删除
if(map.get(fruits[left]) == 0){
map.remove(fruits[left]); //根据key移除left种类
}
left++; //left已经从滑动窗口里删除了,left++
}
ans = Math.max(ans,right-left+1); //更新最大区间ans
right++; //扩大窗口,right右移继续遍历
}
return ans;
}
}
总结
题目看着很绕,其实就要找一个最大的连续数组(数字种类小于等于2)而已。
查找方法就是用滑动窗口,left和right固定。
然后,right遍历水果数组,把right种类put到哈希表中(key是水果种类,value是出现次数)。map存储的是当前left和right窗口中的水果种类和对应的数量。
如果map的大小>2,即水果种类大于2,就要缩小窗口,修改map,map中key为left的value减1。如果value为0,直接把left从map中remove。然后,left右移,直到size小于2。
最后,更新最大长度ans结果,right++继续遍历。
76.最小覆盖子串
题目
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
代码(哈希表)
class Solution {
//tmap保存t字符串对应的哈希表
HashMap<Character,Integer> tmap = new HashMap<>();
//smap保存s字符串对应的哈希表,保存滑动窗口的字符串哈希表
HashMap<Character,Integer> smap = new HashMap<>();
public String minWindow(String s, String t) {
//先遍历t,转为tmap
for(int i=0;i < t.length();i++){
char c = t.charAt(i); //获取字符c
tmap.put(c,tmap.getOrDefault(c,0)+1); //把c加入tmap中
}
int minlen = Integer.MAX_VALUE; //最小长度
int minleft = -1; //minlen对应的左指针
int minright = -1; //minlen对应的右指针
int left = 0; //滑动窗口左区间
int right = 0; //滑动窗口右区间
//再遍历s
while(right < s.length()){
char c = s.charAt(right); //获取s中的right字符
smap.put(c,smap.getOrDefault(c,0)+1);
//判断smap是不是>=tmap,即smap是否可行
while(check()){ //smap可行
int length = right - left + 1; //计算当前窗口大小
//更新最小窗口大小和对应的指针
if(length < minlen){
minlen = length;
minleft = left;
minright = right;
}
//当前可行窗口以及记录好了,要缩小区间了
char ch = s.charAt(left);
smap.put(ch,smap.get(ch)-1);
//这个if不写也行
if(smap.get(ch) == 0){
smap.remove(ch);
}
left++;
}
right++; //窗口扩大,right右移
}
return minlen == Integer.MAX_VALUE ? "" : s.substring(minleft,minright+1);
}
//只有smap中的key和count都满足了,smap才是可行哈希表
public boolean check(){
//遍历tmap,判断每个key对应的value,再smap中是否够了
for(Map.Entry<Character,Integer> entry : tmap.entrySet()){
char key = entry.getKey(); //获取tmap的key
int value = entry.getValue(); //获取tmap的value
//看看smap中是否存在这个key,且value值要>=tmap中的
if(smap.getOrDefault(key,0) < value){
return false; //说明这个entry的smap就已经不可行了
}
}
return true; //tmap中每个entry,smap都满足,说明smap可行
}
}
总结
先用tmap存储t字符串对应的keyvalue哈希表。然后用smap存储s字符串滑动窗口对应的哈希表。用滑动窗口遍历smap,用left和right固定。
先把当前right字符put到smap中。然后循环判断smap是否满足可行要求。
如果不满足,继续扩大窗口,right右移。
如果满足,就要更新minlen最小长度以及对应的区间指针,同时,把smap的entry进行缩小修改。
最后,当right遍历完s字符串后,返回minleft和minright对应的子字符串即可。