区间内排列的数量

114 阅读4分钟

问题描述: 小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,因此首先遍历数组找到数字1的位置。
  2. 计算缺失数字:对于数组中的每个元素,计算如果以该元素为k,那么从1到k的排列中还缺少多少个数字,并记录在数组num中。
  3. 标记和扩展:对于每个可能的k,使用一个标记数组mark来记录从1到k的数字是否已经出现过。首先检查以1为中心的子区间,然后根据需要向左和向右扩展,检查是否可以构成一个完整的排列。
  4. 合法性检查:在扩展过程中,检查区间内的数字是否合法(即在1到k之间),以及是否有重复数字。
  5. 计数和结果:如果找到一个合法的排列,则计数器res加一