滑动窗口也为快慢指针。两个指针,fast指针在前面探路,slow指针负责在移动前做判断条件,然后再移动,两个指针覆盖的长度即为要找的元素空间。
常见的快慢指针题目:找满足条件的最长子串、删除排序的重复元素等。
滑动窗口算法框架
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 void slidingWindow(String s, String t) { HashMap<Character, Integer> need = new HashMap<>(); HashMap<Character, Integer> window = new HashMap<>(); for (char c : t.toCharArray()) need.put(c, need.getOrDefault(c, 0) + 1); int left = 0, right = 0; int valid = 0; while (right < s.length()) { char c = s.charAt(right); right++; if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) valid++; }
while (window needs shrink) { char d = s.charAt(left); left++; if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) valid--; window.put(d, window.getOrDefault(d, 0) - 1); } } } }
|
2760. 最长奇偶子数组
Problem: 2760. 最长奇偶子数组
给你一个下标从 0 开始的整数数组 nums 和一个整数 threshold 。
请你从 nums 的子数组中找出以下标 l 开头、下标 r 结尾 (0 <= l <= r < nums.length) 且满足以下条件的 最长子数组 :
nums[l] % 2 == 0
- 对于范围
[l, r - 1] 内的所有下标 i ,nums[i] % 2 != nums[i + 1] % 2
- 对于范围
[l, r] 内的所有下标 i ,nums[i] <= threshold
以整数形式返回满足题目要求的最长子数组的长度。
注意:子数组 是数组中的一个连续非空元素序列。
示例 1:
输入:nums = [3,2,5,4], threshold = 5
输出:3
解释:在这个示例中,我们选择从 l = 1 开始、到 r = 3 结束的子数组 => [2,5,4] ,满足上述条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。
示例 2:
输入:nums = [1,2], threshold = 2
输出:1
解释:
在这个示例中,我们选择从 l = 1 开始、到 r = 1 结束的子数组 => [2] 。
该子数组满足上述全部条件。可以证明 1 是满足题目要求的最大长度。
示例 3:
输入:nums = [2,3,4,5], threshold = 4
输出:3
解释:
在这个示例中,我们选择从 l = 0 开始、到 r = 2 结束的子数组 => [2,3,4] 。
该子数组满足上述全部条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。
提示:
1 <= nums.length <= 100
1 <= nums[i] <= 100
1 <= threshold <= 100
思路
题目求区间长度,区间设定了规则,那么用双指针,结合if匹配规则找出最大的区间长度即可。
解题方法
滑动窗口,在条件里满足所有设定规则即可
复杂度
时间复杂度:
$O(n)$
空间复杂度:
$O(1)$
- Code
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 31 32 33 34
| class Solution { public int longestAlternatingSubarray(int[] nums, int threshold) { int n = nums.length; int ans = Integer.MIN_VALUE; int left = 0, right = 0; while(right < n) { if (nums[left] % 2 != 0) { while(left < n && nums[left] %2 != 0) { left++; } right = left; } if (right >= n) break; if (left == right && nums[right] <= threshold) { ans = Math.max(ans, 1); } if(left == right && nums[right] > threshold) { right++; left = right; continue; } right++; if(right < n && (nums[right] % 2 != nums[right-1] % 2) && nums[right] <= threshold) { ans = Math.max(ans, right - left + 1); } else { left = right; } } return ans != Integer.MIN_VALUE ? ans: 0; } }
|
632.最小区间
Problem: 632. 最小区间
你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。
示例 1:
输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
输出:[20,24]
解释:
列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。
列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。
列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。
示例 2:
输入:nums = [[1,2,3],[1,2,3],[1,2,3]]
输出:[1,1]
提示:
nums.length == k
1 <= k <= 3500
1 <= nums[i].length <= 50
-105 <= nums[i][j] <= 105
nums[i] 按非递减顺序排列
思路
要求满足区间,可以用左右双指针去找;求每个列表都有一个数满足在区间内,可以用memo数组存储是否满足,当need满足列表长度时为满足所求,然后算出最小区间即可。
解题方法
思路在于把列表内的值都转化为hash映射,把列表都转化为index求是否所有的index都在区间内即可。
Code
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| class Solution { public int[] smallestRange(List<List<Integer>> nums) { Map<Integer, List<Integer>> map = new HashMap<>(); int size = nums.size(); int minVal = Integer.MAX_VALUE, maxVal = Integer.MIN_VALUE; for(int i = 0; i < size; i++) { for(int x: nums.get(i)) { List<Integer> list = map.getOrDefault(x, new ArrayList<Integer>()); list.add(i); map.put(x, list); minVal = Math.min(minVal, x); maxVal = Math.max(maxVal, x); } }
int left = minVal, right = minVal; int[] memo = new int[size]; int need = 0; int bestL = minVal, bestR = maxVal; while(right <= maxVal) { if(map.containsKey(right)) { for(int x: map.get(right)) { memo[x]++; if (memo[x] == 1) need++; } } while(need == size) { if(right - left < bestR - bestL) { bestL = left; bestR = right; } if(map.containsKey(left)) { for(int x: map.get(left)) { if (memo[x] == 1) need--; memo[x]--; } } left++; } right++; } return new int[]{bestL, bestR}; } }
|
中间向两端扩散指针
中间向两端扩散,即左右指针分别朝左右两边扩散,常见为寻找回文子串,判断回文子串的重点为判断子串的长度是奇数还是偶数。
从给定位置寻找回文子串的算法:
1 2 3 4 5 6 7 8 9 10 11
| String palindrome(String s, int l, int r) { while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { l--; r++; } return s.substring(l + 1, r); }
|
然后寻找给定字符串的最长回文子串:
1 2 3 4 5 6 7 8 9 10 11 12 13
| String longestPalindrome(String s) { String res = ""; for (int i = 0; i < s.length(); i++) { String s1 = palindrome(s, i, i); String s2 = palindrome(s, i, i + 1); res = res.length() > s1.length() ? res : s1; res = res.length() > s2.length() ? res : s2; } return res; }
|