通关进度
题目 |
说明 |
子数组最大平均数 |
通关 |
最长连续递增序列 |
通关 |
参考资料
滑动窗口思想简介
滑动窗口入门题
固定窗口
643. 子数组最大平均数 I
问题
给你一个由 n
个元素组成的整数数组 nums
和一个整数 k
。
请你找出平均数最大且 长度为 k
的连续子数组,并输出该最大平均数。
滑动窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public double findMaxAverage(int[] nums, int k) {
int sum = 0; int n = nums.length;
for (int i = 0; i < k; i++) { sum += nums[i]; } int maxSum = sum; for (int end = k; end < n; end++) { sum = sum - nums[end - k] + nums[end]; maxSum = Math.max(maxSum, sum); } return 1.0 * maxSum / k; }
|
可变窗口
674. 最长连续递增序列
问题
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度
滑动窗口
1 2 3 4 5 6 7 8 9 10 11 12
| public int findLengthOfLCIS(int[] nums) { int ans = 0; int n = nums.length; int start = 0; for (int i = 0; i < n; i++) { if (i > 0 && nums[i] <= nums[i - 1]) { start = i; } ans = Math.max(ans, i - start + 1); } return ans; }
|
滑动窗口经典题
通关进度
题目 |
说明 |
最长子串专题 |
通关 |
长度最小的子数组 |
通关 |
盛水最多的容器 |
通关 |
子串异位词专题 |
通关 |
推荐阅读
我写了一首诗,把滑动窗口算法变成了默写题
最长子串专题
无重复字符的最长子串
3. 无重复字符的最长子串
问题
给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 长度
滑动窗口
以 abcabcbb
为例,找出从每一个字符开始的,不包含重复字符的最长子串,那么其中最长的那个字符串即为答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public int lengthOfLongestSubstring(String s) { Set<Character> occ = new HashSet<Character>(); int n = s.length(); int rk = -1, ans = 0; for (int i = 0; i < n; ++i) { if (i != 0) { occ.remove(s.charAt(i - 1)); } while (rk + 1 < n && !occ.contains(s.charAt(rk + 1))) { occ.add(s.charAt(rk + 1)); ++rk; } ans = Math.max(ans, rk - i + 1); } return ans; }
|
hashMap
哈希解决方案
至多包含两个不同字符的最长子串
159. 至多包含两个不同字符的最长子串
问题
给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t 。
1 2 3 4 5 6 7
| 输入: "eceba" 输出: 3 解释: t 是 "ece",长度为3。
输入: "ccaabbb" 输出: 5 解释: t 是 "aabbb",长度为5。
|
算法思路
问题突破口:
- 判断只有两个元素
- 考虑移除的元素
解决方案:
- 键:字符串中的字符
- 值:滑动窗口中的最右边的字符位置
使用如下方案确定需要删除的元素和窗口 left 的新位置:
1 2 3
| del_idex = Collections.min(hashmap.values()); left = del_idex + 1;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public int lengthOfLongestSubStringTwoDistinct(String s) { if (s.length() < 3) { return s.length(); } int left = 0, right = 0; Map<Character, Integer> hashMap = new HashMap<>(); int maxLen = 2;
while (right < s.length()) { if (hashMap.size() < 3) { hashMap.put(s.charAt(right), right++); } if (hashMap.size() == 3) { int del_idex = Collections.min(hashMap.values()); hashMap.remove(s.charAt(del_idex)); left = del_idex + 1; } maxLen = Math.max(maxLen, right - left); } return maxLen; }
|
至多包含 K 个不同字符的最长子串
至多包含 K 个不同字符的最长子串
问题
给定一个字符串 s ,找出 至多 包含 K 个不同字符的最长子串 t 。
1 2 3
| 输入: "eceba", k = 2 输出: 3 解释: t 是 "ece",长度为 3。
|
算法思想
只要将判断 hashMap 数量大小改为 $k$,超过 $2$ 就是 $k+1$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public int lengthOfLongestSubStringKDistinct(String s, int k) { if (s.length() < k + 1) { return s.length(); } int left = 0, right = 0; Map<Character, Integer> hashMap = new HashMap<>(); int maxLen = k;
while (right < s.length()) { if (hashMap.size() < k + 1) { hashMap.put(s.charAt(right), right++); } if (hashMap.size() == k + 1) { int del_idex = Collections.min(hashMap.values()); hashMap.remove(s.charAt(del_idex)); left = del_idex + 1; } maxLen = Math.max(maxLen, right - left); } return maxLen; }
|
长度最小的子数组
209. 长度最小的子数组
问题
给定一个含有 n
个正整数的数组和一个正整数 target
。
找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组 ,并返回其长度。如果不存在符合条件的子数组,返回 0
。
标准答案
查看答案
盛水最多的容器
11. 盛最多水的容器
问题
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
算法思想
现有双指针 $i,j$,指向的水槽板高度分别为 $h[i],h[j]$,此状态下水槽面积为 $S(i,j)$。由于可容纳水的高度由两板中的短板决定,因此:
在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽底边宽度−1 变短:
- 若向内移动短板 ,水槽的短板
min(h[i],h[j])
可能变大,因此下个水槽的面积可能增大
若向内移动长板 ,水槽的短板 min(h[i],h[j])
不变或变小,因此下个水槽的面积一定变小
因此,只要初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public int maxArea(int[] height) { int l = 0, r = height.length - 1; int ans = 0; while (l < r) { int area = Math.min(height[l], height[r]) * (r - l); ans = Math.max(ans, area); if (height[l] <= height[r]) { ++l; } else { --r; } } return ans; }
|
【排列】子串异位词专题
字符串的排列
567. 字符串的排列
问题
给你两个字符串 s1
和 s2
,写一个函数来判断 s2
是否包含 s1
的排列。如果是,返回 true
;否则,返回 false
。
换句话说,s1
的排列之一是 s2
的 子串 。
1 2 3
| 输入:s1 = "ab" s2 = "eidbaooo" 输出:true 解释:s2 包含 s1 的排列之一 ("ba").
|
滑动窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public boolean checkInclusion(String s1, String s2) { int n = s1.length(), m = s2.length(); if (n > m) { return false; } int[] cnt1 = new int[26]; int[] cnt2 = new int[26]; for (int i = 0; i < n; ++i) { ++cnt1[s1.charAt(i) - 'a']; ++cnt2[s2.charAt(i) - 'a']; } if (Arrays.equals(cnt1, cnt2)) { return true; } for (int i = n; i < m; ++i) { ++cnt2[s2.charAt(i) - 'a']; --cnt2[s2.charAt(i - n) - 'a']; if (Arrays.equals(cnt1, cnt2)) { return true; } } return false; }
|
找到字符串中所有字母异位词
438. 找到字符串中所有字母异位词
问题
给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)
1 2 3 4 5
| 输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
|
算法思想
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public List<Integer> findAnagrams(String s, String p) { int sLen = s.length(), pLen = p.length();
if (sLen < pLen) { return new ArrayList<Integer>(); }
List<Integer> ans = new ArrayList<Integer>(); int[] sCount = new int[26]; int[] pCount = new int[26]; for (int i = 0; i < pLen; ++i) { ++sCount[s.charAt(i) - 'a']; ++pCount[p.charAt(i) - 'a']; }
if (Arrays.equals(sCount, pCount)) { ans.add(0); }
for (int i = 0; i < sLen - pLen; ++i) { --sCount[s.charAt(i) - 'a']; ++sCount[s.charAt(i + pLen) - 'a'];
if (Arrays.equals(sCount, pCount)) { ans.add(i + 1); } }
return ans; }
|
滑动窗口最大值
239. 滑动窗口最大值
问题
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值
1 2 3 4 5 6 7 8 9 10 11
| 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 输出:[3,3,5,5,6,7] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
|
优先队列
算法思想
本题初始时,我们将数组 nums 的前 k个元素放入优先队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums 中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。
我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素num 在数组中的下标为index。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() { public int compare(int[] pair1, int[] pair2) { return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1]; } }); for (int i = 0; i < k; ++i) { pq.offer(new int[]{nums[i], i}); } int[] ans = new int[n - k + 1]; ans[0] = pq.peek()[0]; for (int i = k; i < n; ++i) { pq.offer(new int[]{nums[i], i}); while (pq.peek()[1] <= i - k) { pq.poll(); } ans[i - k + 1] = pq.peek()[0]; } return ans; }
|
单调队列
算法思想
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; Deque<Integer> deque = new LinkedList<Integer>(); for (int i = 0; i < k; ++i) { while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) { deque.pollLast(); } deque.offerLast(i); }
int[] ans = new int[n - k + 1]; ans[0] = nums[deque.peekFirst()]; for (int i = k; i < n; ++i) { while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) { deque.pollLast(); } deque.offerLast(i); while (deque.peekFirst() <= i - k) { deque.pollFirst(); } ans[i - k + 1] = nums[deque.peekFirst()]; } return ans; }
|