刷题练习——134.区间内排列的数量

80 阅读3分钟

以下为题目描述

问题描述

小U拿到了一组排列,她想知道有多少个子区间满足区间内部的数构成一个排列。一个区间的排列是指:该区间的数包含从 11 到 kk 的每个数,并且每个数恰好出现一次,这个区间的长度为 kk。

例如,对于数组 [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

我的解答如下

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static int solution(int n, int[] a) {
        int count = 0;
        // 遍历所有可能的起始位置
        for (int l = 0; l < n; l++) {
            Set<Integer> set = new HashSet<>();
            int minVal = Integer.MAX_VALUE;
            int maxVal = Integer.MIN_VALUE;
            // 从起始位置开始,尝试扩展区间
            for (int r = l; r < n; r++) {
                // 如果出现重复元素,区间不可能构成排列,退出当前循环
                if (set.contains(a[r])) {
                    break;
                }
                set.add(a[r]);
                minVal = Math.min(minVal, a[r]);
                maxVal = Math.max(maxVal, a[r]);
                int cnt = set.size();
                int k = r - l + 1;
                // 检查是否满足排列条件
                if (cnt == k && minVal == 1 && maxVal == k) {
                    count++;
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        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);
    }
}

代码解释

这道题要求统计数组中有多少个子区间满足以下条件:

• 子区间的长度为 k。

• 子区间内的数字恰好包含从 1 到 k 的所有整数,且每个数只出现一次。

思路:

我们需要遍历所有可能的子区间,检查每个子区间是否满足条件。

为了优化时间复杂度,我们在遍历的过程中,维护以下信息:

• 一个集合 set,用于存储当前子区间内的唯一元素。

• 当前子区间内元素的最小值 minVal 和最大值 maxVal。

• 对于每个子区间 [l, r],我们检查以下条件:

    1. 元素个数等于子区间长度: cnt == k,其中 cnt 是集合中元素的数量,k 是子区间长度 r - l + 1。

    2. 最大值和最小值的差满足连续性: maxVal - minVal + 1 == k。

    3. 这意味着子区间内的元素是连续的整数序列,没有缺失的数字。

    4. 如果上述条件都满足,则当前子区间是一个符合要求的排列,计数器 count 增加。

代码详解:

外层循环:

for (int l = 0; l < n; l++)

遍历所有可能的起始位置 l

内层循环:

for (int r = l; r < n; r++)

从起始位置 l 开始,尝试扩展到位置 r,形成子区间 [l, r]

集合判断:

if (set.contains(a[r])) break;

如果当前元素已经在集合中出现过,说明存在重复元素,子区间不可能构成排列,退出当前循环

更新集合和最值:

set.add(a[r]);

将当前元素加入集合。

minVal = Math.min(minVal, a[r]);

更新最小值

maxVal = Math.max(maxVal, a[r]);

• 更新最大值。

计算子区间长度和元素数量:

int cnt = set.size();

当前子区间内的唯一元素数量。

int k = r - l + 1;

当前子区间的长度。

检查排列条件:

if (cnt == k && maxVal - minVal + 1 == k)

如果元素数量等于子区间长度,且最大值和最小值的差满足连续性,则计数器 count 增加。

但是,我一开始在写的时候,在检查排列条件时,没有判断好,仅检查了以下两点:

• 子区间内的元素个数等于子区间长度(cnt == k)。

• 最大值和最小值的差加一等于子区间长度(maxVal - minVal + 1 == k)。

但是,这无法保证子区间内的数字是从 1 到 k,因此会导致错误的结果。为了满足题目要求,需要修改判断条件,确保子区间内的数字是从 1 到 k,且每个数字恰好出现一次。修改后如下:

if (cnt == k && minVal == 1 && maxVal == k) {
    count++;
}