2020.12.17
1.无重复字符的最长子串(leetcode3)
思路:使用滑动窗口机制
设置右指针移动,其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
如何移动?我们只要把队列的左边的元素移出就行了,直到满足题目要求!
一直维持这样的队列,找出队列出现最长的长度时候,求出解!
public int lengthOfLongestSubstring(String s) {
//HashMap<Character, Integer> map = new HashMap<>();
int[] map = new int[128];//使用int数组可以当做map使用
int maxLen = 0;//用于记录最大不重复子串的长度
int left = 0;//滑动窗口左指针
for (int i = 0; i < s.length() ; i++)
{
/**
1、首先,判断当前字符是否包含在map中,如果不包含,将该字符添加到map(字符,字符在数组下标),
此时没有出现重复的字符,左指针不需要变化。此时不重复子串的长度为:i-left+1,与原来的maxLen比较,取最大值;
2、如果当前字符 ch 包含在 map中,此时有2类情况:
1)当前字符包含在当前有效的子段中,如:abca,当我们遍历到第二个a,当前有效最长子段是 abc,我们又遍历到a,
那么此时更新 left 为 map.get(a)+1=1,当前有效子段更新为 bca;
2)当前字符不包含在当前最长有效子段中,如:abba,我们先添加a,b进map,此时left=0,我们再添加b,发现map中包含b,
而且b包含在最长有效子段中,就是1)的情况,我们更新 left=map.get(b)+1=2,此时子段更新为 b,而且map中仍然包含a,map.get(a)=0;
随后,我们遍历到a,发现a包含在map中,且map.get(a)=0,如果我们像1)一样处理,就会发现 left=map.get(a)+1=1,实际上,left此时
应该不变,left始终为2,子段变成 ba才对。
为了处理以上2类情况,我们每次更新left,left=Math.max(left , map.get(ch)+1).
另外,更新left后,不管原来的 s.charAt(i) 是否在最长子段中,我们都要将 s.charAt(i) 的位置更新为当前的i,
因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中!
*/
//if(map.containsKey(s.charAt(i)))
if (map[s.charAt(i)] != -1)
{
left = Math.max(left, map[s.charAt(i)]+1);
//left = Math.max(left , map.get(s.charAt(i))+1);
}
//不管是否更新left,都要更新 s.charAt(i) 的位置!
map.put(s.charAt(i) , i);
maxLen = Math.max(maxLen , i-left+1);
}
return maxLen;
}
2.最小覆盖子串(leetcode76)
思路:使用滑动窗口机制
- 设置右指针
right
移动,使滑动窗口增大,直到窗口包含了T的所有元素。 - 不断增加左指针
left
使滑动窗口缩小,并查看窗口是否包含所有元素。如果满足则记录此时滑动窗口的长度,并保存最小值 - 让
left
再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到right
超出了字符串S范围。
其中,需要注意的是:判断是否包含所有元素的方法是利用两个hashmap集合实现(也可以直接用数组表示),key为字符,value为字符的次数。满足条件对应的是window集合中元素个数要大于等于need集合中元素个数,在这里利用valid
来判断是否满足条件。
class Solution {
public String minWindow(String s, String t) {
if (s == null || s.length() < t.length())
return "";
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (Character ch : t.toCharArray()) {
need.put(ch, need.getOrDefault(ch,0)+1);
}
int left = 0;
int valid = 0;
int len = Integer.MAX_VALUE;
int start = 0;
for (int right=0;right<s.length();right++) {
Character c1 = s.charAt(right);
if (need.containsKey(c1)) {
window.put(c1, window.getOrDefault(c1,0)+1);
if (need.get(c1).equals(window.get(c1))) {
valid++;
}
}
while (valid == need.size() && left<=right) {
if (right-left<len) {
start = left;
len = right-left+1;
}
Character c2 = s.charAt(left);
if (need.containsKey(c2)) {
window.put(c2, window.getOrDefault(c2,0)-1);
if (window.get(c2) < need.get(c2)) {
valid--;
}
}
left++;
}
}
return len==Integer.MAX_VALUE ? "" : s.substring(start, start+len);
}
}
3.滑动窗口最大值(leetcode239)
思路:使用单调递减栈实现,即双端队列。
- 遍历数组,将数组对应的下标存入队列中。
- 数组需要按照数(即下标对应数组中的值)从大到小排列,如果当前遍历的数比队尾的值大,则需要弹出队尾值,直到队列重新满足从大到小的要求。
- 每次取队列头部即窗口最大值。如果头部位置已经超出窗口左边界的话,则移除头部。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums == null || nums.length < 2) return nums;
// 双向队列 保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
LinkedList<Integer> queue = new LinkedList();
// 结果数组
int[] result = new int[nums.length-k+1];
// 遍历nums数组
for(int i = 0;i < nums.length;i++){
// 保证从大到小 如果前面数小则需要依次弹出,直至满足要求
while(!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
queue.pollLast();
}
// 添加当前值对应的数组下标
queue.addLast(i);
// 判断当前队列中队首的值是否有效
if(queue.peek() <= i-k){
queue.poll();
}
// 当窗口长度为k时 保存当前窗口中最大值
if(i+1 >= k){
result[i+1-k] = nums[queue.peek()];
}
}
return result;
}
}