import java.util.*;
/**
* NC401 K 个不同整数子数组
* @author d3y1
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 相似 -> NC343 和大于等于K的最短子数组 [nowcoder]
* 相似 -> NC41 最长无重复子数组 [nowcoder]
* 相似 -> NC170 最长不含重复字符的子字符串 [nowcoder]
* 相似 -> NC356 至多包含K种字符的子串 [nowcoder]
* 相似 -> NC387 找到字符串中的异位词 [nowcoder]
*
* @param nums int整型ArrayList
* @param k int整型
* @return int整型
*/
public int nowsubarray (ArrayList<Integer> nums, int k) {
// return solution1(nums, k);
return solution2(nums, k);
// return solution3(nums, k);
}
/**
* 双指针
*
* 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
*
* @param nums
* @param k
* @return
*/
private int solution1(ArrayList<Integer> nums, int k){
return atMostKDistinctKinds(nums, k)-atMostKDistinctKinds(nums, k-1);
}
/**
* 获取连续子数组个数: 最多存在K个不同整数
* @param nums
* @param k
* @return
*/
private int atMostKDistinctKinds(ArrayList<Integer> nums, int k){
int n = nums.size();
// 统计滑动窗口中不同整数的个数
int[] cnt = new int[n+1];
int result = 0;
// 双指针 毛毛虫
int left = 0;
int right = 0;
// 滑动窗口[left, right)中整数种数
int kind = 0;
int numL,numR;
while(right < n){
numR = nums.get(right);
if(cnt[numR] == 0){
kind++;
}
cnt[numR]++;
right++;
while(kind > k){
numL = nums.get(left);
cnt[numL]--;
if(cnt[numL] == 0){
kind--;
}
left++;
}
// 以nums[right-1]为右边界(固定nums[right-1]) 向左统计最多存在K个不同整数的连续子数组的个数
result += right-left;
}
return result;
}
/**
* 双指针
*
* 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
*
* 简化
*
* @param nums
* @param k
* @return
*/
private int solution2(ArrayList<Integer> nums, int k){
return getSubsAtMostKDistinctKinds(nums, k)-getSubsAtMostKDistinctKinds(nums, k-1);
}
/**
* 获取连续子数组个数: 最多存在K个不同整数
* @param nums
* @param k
* @return
*/
private int getSubsAtMostKDistinctKinds(ArrayList<Integer> nums, int k){
int n = nums.size();
// 统计滑动窗口中不同整数的个数
int[] cnt = new int[n+1];
int result = 0;
// 双指针 毛毛虫
int left=0,right=0;
// 滑动窗口[left, right)中整数种数
int kind = 0;
int numL,numR;
while(right < n){
numR = nums.get(right++);
if(cnt[numR]++ == 0){
kind++;
}
while(kind > k){
numL = nums.get(left++);
cnt[numL]--;
if(cnt[numL] == 0){
kind--;
}
}
// 以nums[right-1]为右边界(固定nums[right-1]) 向左统计最多存在K个不同整数的连续子数组的个数
result += right-left;
}
return result;
}
/**
* 双指针
*
* 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
*
* 合并
*
* @param nums
* @param k
* @return
*/
private int solution3(ArrayList<Integer> nums, int k){
int n = nums.size();
// 统计滑动窗口中不同整数的个数
int[] cnt1 = new int[n+1];
int[] cnt2 = new int[n+1];
int result = 0;
// 双指针 毛毛虫
int left1=0,left2=0,right=0;
// 滑动窗口[left1, right)和[left2, right)中整数种数
int kind1=0,kind2=0;
int numL1,numL2,numR;
while(right < n){
numR = nums.get(right++);
if(cnt1[numR]++ == 0){
kind1++;
}
if(cnt2[numR]++ == 0){
kind2++;
}
while(kind1 > k){
numL1 = nums.get(left1++);
cnt1[numL1]--;
if(cnt1[numL1] == 0){
kind1--;
}
}
while(kind2 > k-1){
numL2 = nums.get(left2++);
cnt2[numL2]--;
if(cnt2[numL2] == 0){
kind2--;
}
}
// 以nums[right-1]为右边界(固定nums[right-1]) 向左统计 最多存在K个不同整数的连续子数组的个数与最多存在K-1个不同整数的连续子数组的个数之差
// (right-left1)-(right-left2) = left2-left1
result += left2-left1;
}
return result;
}
}