解法1:暴力枚举(超时)
「从前往后」枚举数组中的任意⼀个元素,把它当成起始位置。然后从这个「起始位置开始,然后寻找⼀段最短的区间,使得这段区间的和「⼤于等于」⽬标值。将所有元素作为起始位置所得的结果中,找到「最⼩值」即可。
解法2:滑动窗口
由于此问题分析的对象是「⼀段连续的区间」,因此可以考虑「滑动窗⼝」的思想来解这道题。让滑动窗⼝满⾜:从 i 位置开始,窗⼝内所有元素的和⼩于 target (那么当窗⼝内元素之和第⼀次⼤于等于⽬标值的时候,就是 i 位置开始,满⾜条件的最⼩⻓度)。做法:将右端元素划⼊窗⼝中,统计出此时窗⼝内元素的和:
时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者最多都往后移动 n 次。因此时间复杂度是 O(N)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length, sum = 0, len = Integer.MAX_VALUE;
for(int left = 0, right = 0; right < n; right++){
sum += nums[right]; // 进窗⼝
while(sum >= target) // 判断,并且要循环,因为左边元素可能很小,还符合条件
{
len = Math.min(len, right - left + 1); // 更新结果
sum -= nums[left++]; // 出窗⼝
}
}
return len == Integer.MAX_VALUE ? 0 : len;
}
}
研究的对象依旧是⼀段连续的区间,因此继续使⽤「滑动窗⼝」思想来优化。 让滑动窗⼝满⾜:窗⼝内所有元素都是不重复的。
做法:右端元素 ch 进⼊窗⼝的时候,哈希表统计这个字符的频次:
class Solution {
public int lengthOfLongestSubstring(String s) {
int[] hash = new int [128]; // 用来去重
int result = 0;
int len = 0;
for(int left = 0, right = 0; right < s.length(); right++) {
int tmp = s.charAt(right);
len++;// 进窗口
hash[tmp]++;
while(hash[tmp] > 1) {
// 出现重复的字符,出窗口
hash[s.charAt(left) ]--;
left++;
len--;
}
// 更新结果
result = Math.max(result, len);
}
return result;
}
}
不要去想怎么翻转,不要把问题想的很复杂,这道题的结果⽆⾮就是⼀段连续的 1 中间塞了 k个 0 嘛。
因此,我们可以把问题转化成:求数组中⼀段最⻓的连续区间,要求这段区间内 0 的个数不超过 k 个。
既然是连续区间,可以考虑使⽤「滑动窗⼝」来解决问题。
class Solution {
public int longestOnes(int[] nums, int k) {
int result = 0;
for(int left = 0, right = 0, zero = 0; right < nums.length; right++) {
// 进窗口
if(nums[right] == 0) {
zero++;
}
// 判断0的个数是否大于k
while(zero > k) {
if(nums[left] == 0) {
zero--;
}
// 出窗口
left++;
}
// 更新结果
result = Math.max(result, right - left + 1);
}
return result;
}
}
题⽬要求的是数组「左端+右端」两段连续的、和为 x 的最短数组,信息量稍微多⼀些,不易理清思路;我们可以转化成求数组内⼀段连续的、和为 sum(nums) - x 的最⻓数组。此时,就是熟悉的滑动窗口问题了。
class Solution {
public int minOperations(int[] nums, int x) {
int sum = 0;
for(int i = 0; i < nums.length; i++) {
sum += nums[i];
}
sum -= x;
// 细节处理
if(sum < 0) {
return - 1;
}
int result = -1;
for(int left = 0, right = 0; right < nums.length; right++) {
// 进窗口
sum -= nums[right];
// 判断
while(sum < 0) {
sum += nums[left];
left++; // 出窗口
}
// 更新
if(sum == 0) {
result = Math.max(result, right - left + 1);
}
}
return result == -1 ? -1 : nums.length - result;
}
}
研究的对象是⼀段连续的区间,可以使⽤「滑动窗⼝」思想来解决问题。
让滑动窗⼝满⾜:窗⼝内⽔果的种类只有两种。
做法:右端⽔果进⼊窗⼝的时候,⽤哈希表统计这个⽔果的频次。这个⽔果进来后,判断哈希表的大小:
class Solution {
public int totalFruit(int[] fruits) {
Map<Integer,Integer> hash = new HashMap<>(2);
int result = 0;
for(int left = 0, right = 0; right < fruits.length; right++) {
// 进窗口
hash.put(fruits[right], hash.getOrDefault(fruits[right], 0) + 1);
// 判断
while(hash.size() > 2) {
// 出窗口
hash.put(fruits[left], hash.get(fruits[left]) - 1);
if(hash.get(fruits[left]) == 0) {
hash.remove(fruits[left]);
}
left++;
}
// 更新结果
result = Math.max(result, right - left + 1);
}
return result;
}
}
class Solution {
public List<Integer> findAnagrams(String s, String p) {
char[] ss = s.toCharArray();
char[] pp = p.toCharArray();
int len = 0;
int[] hash1 = new int[26];
for(int i = 0; i < pp.length; i++) {
hash1[pp[i] - 'a']++;
}
int[] hash2 = new int[26];
List<Integer> result = new ArrayList<>();
for(int left = 0, right = 0; right < ss.length; right++) {
// 进窗口
hash2[ss[right] - 'a']++;
len++;
// 判断
while(len == p.length()) {
// 更新结果
if(isEqual(hash1, hash2)) {
result.add(left);
}
// 出窗口
hash2[ss[left] - 'a']--;
left++;
len--;
}
}
return result;
}
private boolean isEqual(int[] hash1, int[] hash2) {
for(int i = 0; i < hash1.length; i++) {
if(hash1[i] != hash2[i]) {
return false;
}
}
return true;
}
}
class Solution
{
public List<Integer> findAnagrams(String ss, String pp)
{
List<Integer> ret = new ArrayList<Integer>();
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
int[] hash1 = new int[26]; // 统计字符串 p 中每⼀个字符出现的个数
for(char ch : p) hash1[ch - 'a']++;
int[] hash2 = new int[26]; // 统计窗⼝中每⼀个字符出现的个数
int m = p.length;
for(int left = 0, right = 0, count = 0; right < s.length; right++)
{
char in = s[right];
// 进窗⼝ + 维护 count
if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;
if(right - left + 1 > m) // 判断
{
char out = s[left++];
// 出窗⼝ + 维护 count
if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;
}
// 更新结果
if(count == m) ret.add(left);
}
return ret;
}
}
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> result = new ArrayList<>();
HashMap<String, Integer> hash1 = new HashMap<>();
for(String x : words) {
hash1.put(x, hash1.getOrDefault(x, 0) + 1);
}
int len = words[0].length();
int num = words.length;
// 执行几次滑动窗口
for(int i = 0; i < len; i++) {
HashMap<String, Integer> hash2 = new HashMap<>();
// 注意边界
for(int left = i, right = i, count = 0; right + len <= s.length(); right += len) {
// 进窗口
String in = s.substring(right, right + len);
hash2.put(in , hash2.getOrDefault(in, 0) + 1);
// count用来维护有效个数
if(hash2.get(in) <= hash1.getOrDefault(in, 0)) count++;
// 判断
if(right - left + 1 > len * num) {
// 出窗口
String out = s.substring(left, left + len);
if(hash2.get(out) <= hash1.getOrDefault(out, 0)) count--;
hash2.put(out, hash2.get(out) - 1);
left += len;
}
// 更新结果
if(count == num) {
result.add(left);
}
}
}
return result;
}
}
class Solution {
public String minWindow(String s, String t) {
StringBuilder sb = new StringBuilder();
char[] tt = t.toCharArray();
int[] hash1 = new int[128];
int kind = 0;// 统计有效字符有多少种
for(char x : tt) {
if(hash1[x]++ == 0) {
kind++;
}
}
char[] ss = s.toCharArray();
int[] hash2 = new int[128];
int minlen = Integer.MAX_VALUE, begin = -1;
for(int left = 0, right = 0, count = 0; right < ss.length; right++) {
// 进窗口
char in = ss[right];
hash2[in]++;
if(hash2[in] == hash1[in]) count++;
while(count == kind) {
// 更新结果
if(right - left + 1 < minlen ) {
minlen = right - left + 1;
begin = left;
}
// 出窗口
char out = ss[left++];
if(hash2[out]-- == hash1[out]) count--;
}
}
if(begin == -1) return new String();
else return s.substring(begin, begin + minlen);
}
}