AI 刷题 134. 区间内排列的数量 题解 | 豆包MarsCode AI刷题

72 阅读5分钟

问题描述

小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

问题分析

题目给定一个整数数组,要求我们找出其中有多少个子区间,它们是一个排列。一个排列区间是指该区间内的元素包含了从 1 到 k 的所有整数,且每个数恰好出现一次。并且,区间的长度正好是 k

举个例子

  • 对于数组 [2, 1, 5, 3, 4],区间 [2, 2][1, 2][1, 5] 都是有效的排列。

解题思路

  1. 排列的定义

    • 一个长度为 k 的区间是排列,意味着它包含从 1k 的每个数,且没有重复。
  2. 滑动窗口技巧

    • 这个问题可以用滑动窗口来解决。具体地,我们在遍历数组的过程中,不断维护一个窗口,这个窗口的长度是从 1k,并且窗口内的数字必须是 1k 的所有整数,且每个数恰好出现一次。
    • 通过一个哈希表或计数器来检查窗口内的数字是否为一个完整的排列。
  3. 步骤

    • 我们可以遍历所有可能的区间,长度从 k = 1n。对于每一个区间,判断它是否是一个排列。
    • 使用一个滑动窗口的策略来高效地检查每个区间是否为排列:只要窗口内包含从 1 到 k 的数,且每个数只出现一次,说明这是一个有效的排列。
  4. 具体做法

    • 维护一个频次计数器,当一个区间内的所有数的频次都为 1,且最大值为 k,则该区间是一个排列。
    • 扩展窗口:每次扩展窗口时,检查当前窗口的数字是否构成一个排列。
    • 滑动窗口技巧:通过移动左端点,判断当前窗口是否满足条件。

代码实现

from collections import defaultdict

def solution(n: int, a: list) -> int:
    count = 0  # 记录排列子区间的数量
    for start in range(n):
        freq = defaultdict(int)  # 记录当前区间每个数字的出现频次
        max_num = 0  # 当前区间的最大值
        for end in range(start, n):
            num = a[end]
            freq[num] += 1
            max_num = max(max_num, num)
            
            # 当前区间的长度
            length = end - start + 1
            
            # 判断区间是否构成排列:1.区间长度 == 最大值 2.每个数字出现次数为1
            if length == max_num and all(v == 1 for v in freq.values()):
                count += 1

    return count

# 测试样例
if __name__ == '__main__':
    print(solution(5, [2, 1, 5, 3, 4]) == 3)
    print(solution(5, [1, 2, 3, 4, 5]) == 5)
    print(solution(4, [4, 3, 2, 1]) == 4)

代码解释

  1. 外层循环:遍历所有可能的子区间的起始位置 start

  2. 内层循环:遍历从 startend 的子区间,记录当前区间内的每个数字的出现频次。

  3. 条件判断

    • 判断当前子区间的长度是否等于当前区间中的最大值(即 max_num)。
    • 如果区间的长度等于最大值,并且每个数字的出现频次都为 1,那么该区间就是一个有效的排列。
  4. 计数:每当找到一个有效的排列子区间时,count 加 1。

时间复杂度分析

  • 外层循环遍历所有的起始位置 start,有 n 个位置。
  • 内层循环对于每个 start,遍历 endstartn,最坏情况下每次都遍历一次所有后面的元素。
  • 在每次检查时,我们需要判断当前窗口内的数字是否构成一个排列,检查所有频次是否为 1,因此每次检查的时间复杂度为 O(k),其中 k 为当前区间长度。
  • 因此,总的时间复杂度是 O(n2)O(n^2),因为我们最多会遍历 n2n^2 次区间。

空间复杂度分析

  • 我们使用了一个字典来记录每个数字的出现频次,最多需要存储 n 个不同的数字(最坏情况下每个数字都是唯一的),所以空间复杂度为 O(n)。

优化思路

目前的解法已经很直观,但在大多数情况下,它的时间复杂度为 O(n^2),这对于大规模数据来说可能不够高效。

一种可能的优化方法是通过利用哈希集合(或频次数组)来降低检查的时间复杂度,或利用更高效的滑动窗口技巧,在检查区间是否为排列时减少不必要的重复计算。不过,当前方案对于中小规模数据已经是合理的解法。

总结

  1. 解题思路:通过滑动窗口维护每个子区间的频次计数器,判断当前窗口是否为一个排列。
  2. 时间复杂度:最坏情况为 O(n^2),适合小规模数据。
  3. 空间复杂度:O(n),主要用于存储频次计数器。