问题描述: 小U拿到了一组排列,她想知道有多少个子区间满足区间内部的数构成一个排列。一个区间的排列是指:该区间的数包含从 1到 k的每个数,并且每个数恰好出现一次,这个区间的长度为 k。
例如,对于数组 [2, 1, 5, 3, 4],其中区间 [2, 2],[1,2] 和 [1, 5] 都是排列。
测试样例
样例1:
输入:
n = 5 ,a = [2, 1, 5, 3, 4]
输出:3
样例2:
输入:
n = 5 ,a = [1, 2, 3, 4, 5]
输出:5
样例3:
输入:
n = 4 ,a = [4, 3, 2, 1]
输出:4
解决思路:
题目的a数组没出现元素重复的情况,我们就a元素不重复进行讨论。主要从这句该区间的数包含从 1到 k的每个数,并且每个数恰好出现一次,这个区间的长度为 k。 我们可以知道这个区间无论如何都存在1这个数。因为他包含1到k一共k个数,其他数暂时确认不了,但是k可以直接确定区间的数的数量。
就像 {9,6,2,1,8,5,4,7,3}里面,我们可以取k是第一个数即9,由此我们可以确定k=9,然后从9到1一共是9,6,2,1 四个元素,可以看成是答案区间的一个子区间,缺的5个元素肯定在这个子区间延伸。我们可以向右延伸5个元素,刚好可以得到1~9的区间。实际可以向左右延伸。
我们只要判断子区间内的数和延伸过程中的数是否合法就行,还有就是子区间的数如果超过k可以直接跳过,就像 {……1 ,8,……,6,7, 2……}区间1和2之间的数超过答案区间的。
public class Main {
public static int solution(int n, int[] a) {
// write code here
//就是不知道在a数组里面数字会不会重复!
//注意每个区间都是从1到k
//找出1下标
int index=0;
int i;
for(i=0;i<n;i++){
if(a[i]==1){
index=i;
break;
}
}
//计算这个数若和1组成区间,还差几个数,差的数要在两边取
int[] num=new int[n];
int middleNum;
for(i=0;i<n;i++){
middleNum=Math.abs(i-index);
//如果中间的数比这个数还大,肯定没戏,比如在1和2之间又10个数
if(middleNum>=a[i]){
num[i]=-1;
continue;
}
num[i]=a[i]-middleNum-1;
}
//差的数在和1到这个数的区间相连的数找,如果是负数直接跳过
int count=0,res=0,j,start,end,que;//que表示缺的数
boolean repeat;//标记重复和超出范围
for(i=0;i<n;i++){
repeat=false;
count=0;
que=num[i];
if(que<0){
continue;
}
//创建一个数组标记找到的数
int[] mark=new int[a[i]+1];//创建后的数组初始值全部是0!
start=Math.min(index,i);
end=Math.max(index,i);
for(j=start;j<=end;j++){
//里面的数不合法
if(a[j]<1||a[j]>a[i]){
repeat=true;
break;
}
//是否重复
if(mark[a[j]]==0){
mark[a[j]]=1;
}else{
repeat=true;
break;
}
count++;
}
if(repeat){
continue;
}
//下面的if可以解决1和长度刚好的情况!
if(count==a[i]){
res++;
continue;
}
//向左扩展
repeat=false;
for(j=start-1;j>=0&&count<a[i];j--){
//里面的数不合法
if(a[j]<1||a[j]>a[i]){
repeat=true;
break;
}
//是否重复
if(mark[a[j]]==0){
mark[a[j]]=1;
}else{
repeat=true;
break;
}
count++;
}
if(count==a[i]){
res++;
continue;
}
/*左扩展出现问题不用那么快结束,还有右扩展
if(repeat){
continue;
}*/
//向右扩展
repeat=false;
for(j=end+1;j<n&&count<a[i];j++){
//里面的数不合法
if(a[j]<1||a[j]>a[i]){
repeat=true;
break;
}
//是否重复
if(mark[a[j]]==0){
mark[a[j]]=1;
}else{
repeat=true;
break;
}
count++;
}
if(count==a[i]){
res++;
continue;
}
if(repeat){
continue;
}
}
return res; // placeholder return
}
public static void main(String[] args) {
/*int[] test=new int[2];
System.out.println(Arrays.toString(test));*/
/* System.out.println(solution(5, new int[]{2, 1, 5, 3, 4}) == 3);
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}) == 5);
System.out.println(solution(4, new int[]{4, 3, 2, 1}) == 4);*/
System.out.println(solution(9, new int[]{9,6,2,1,8,5,4,7,3}) == 4);
}
}
总结:
本题的目标是找出数组中所有可能的子区间,这些子区间内的数构成一个从1到k的排列,其中k是子区间的长度。以下是代码的主要逻辑和特点:
- 寻找数字1的位置:由于每个排列都包含数字1,因此首先遍历数组找到数字1的位置。
- 计算缺失数字:对于数组中的每个元素,计算如果以该元素为k,那么从1到k的排列中还缺少多少个数字,并记录在数组
num中。 - 标记和扩展:对于每个可能的k,使用一个标记数组
mark来记录从1到k的数字是否已经出现过。首先检查以1为中心的子区间,然后根据需要向左和向右扩展,检查是否可以构成一个完整的排列。 - 合法性检查:在扩展过程中,检查区间内的数字是否合法(即在1到k之间),以及是否有重复数字。
- 计数和结果:如果找到一个合法的排列,则计数器
res加一