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

31 阅读6分钟

问题描述

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

例如,对于数组 [2, 1, 5, 3, 4],其中区间 [2, 2][1,2] 和 [1, 5] 都是排列。

思路解析

一、首先分析题目,题目有些绕

题目是说给了一个数字的排列(也就是一组按顺序排好的数,在这道题里是放在一个列表里呈现的),然后需要找出这个排列中有多少个子区间,这些子区间里的数字能构成一个新的 “小排列”。

那这里所说的 “区间内部的数构成一个排列” 具体是什么意思呢?

就是说在这个子区间里的数字,要包含从 1 开始到某个数的每一个数,并且每个数都恰好只出现一次。

拿数组 [1, 2, 3, 4, 5] 来说,整个数组本身就是一个从 1 到 5 每个数都有且只出现一次的排列,那以每一个位置作为区间起点,到最后一个位置结束的区间都是符合要求的排列区间呀,像 [1, 1] 、[1, 2] 、[1, 3] 、[1, 4] 、[1, 5] 等等,一共就有 5 个这样满足条件的区间,所以答案就是 5 咯。

对于数组 [4, 3, 2, 1] ,类似的道理,[4, 4] 是长度为 1 的排列区间,[3, 4] 是包含从 3 到 4 的排列区间,[2, 4] 包含从 2 到 4 每个数且只出现一次的排列区间,[1, 4] 同样满足,这样算下来一共就有 4 个满足条件的子区间呢。

二、整体思路

通过遍历不同可能的子区间长度(也就是排列长度),针对每个长度利用滑动窗口在给定数组上滑动,同时借助哈希表来统计窗口内各数字出现的次数,进而判断每个窗口对应的子区间是否构成符合要求的排列,最后累计满足条件的子区间数量。

三、具体编程思路步骤

  1. 确定要考察的子区间长度范围(即排列长度范围)

使用循环 for window_size in range(1, n + 1): 来遍历所有可能的子区间长度,这里的 n 是给定数组的长度。因为子区间长度最小可以是 1(只包含一个数字也算一种排列情况),最大就是整个数组的长度,所以要把从 1 到 n 的所有长度值都考虑进来,依次去查看是否存在满足条件的对应长度的子区间。

  1. 针对每个子区间长度进行滑动窗口操作与相关统计

初始化窗口相关参数: 首先设置窗口的左边界 left = 0,它用于标记当前滑动窗口在数组中的起始位置,初始时从数组开头(索引为 0 的位置)开始。 然后创建一个哈希表(代码中使用 defaultdict(int) 来实现,这是 Python 里方便的一种字典类型,访问不存在的键时会自动返回默认值 0),命名为 num_count,用来记录当前窗口内每个数字出现的次数。例如,num_count[3] 表示数字 3 在当前窗口内出现的次数,初始时所有数字的出现次数都为 0。

移动窗口并更新数字出现次数统计: 使用循环 for right in range(len(a)): 来移动窗口的右端点,right 从 0 开始逐步增加,一直到数组的最后一个索引位置。随着右端点的移动,新进入窗口的数字需要在 num_count 哈希表中更新其出现次数,也就是执行 num_count[a[right]] += 1,意思是对应数字 a[right] 在窗口内出现的次数加 1。例如,数组是 [2, 1, 5, 3, 4],当 right = 2 时,a[right] = 5,就会将 num_count[5] 的值从 0 变为 1,表示数字 5 在当前窗口内出现了 1 次。

根据子区间长度调整窗口(收缩窗口) : 当窗口内元素个数(也就是 right - left + 1,即当前窗口的长度)超过了当前正在考察的子区间长度(window_size)时,需要收缩窗口,通过循环 while right - left + 1 > window_size: 来实现。在这个循环里,首先要把窗口左边界对应的数字在 num_count 哈希表中的计数减 1,即 num_count[a[left]] -= 1,因为这个数字即将移出当前窗口了,其出现次数要相应减少。如果减完后这个数字的出现次数变为 0 了(if num_count[a[left]] == 0:),那就说明它已经完全不在当前窗口内了,需要从哈希表中删除这个键值对(del num_count[a[left]]),然后将窗口左边界向右移动一位(left += 1),以此来保证窗口大小始终符合当前考察的子区间长度要求。

  1. 判断当前窗口是否构成符合要求的排列并计数:

当窗口大小刚好等于当前考察的子区间长度(right - left + 1 == window_size),并且哈希表中不同数字的个数(通过 len(num_count) 获取)也刚好等于这个子区间长度时,此时只是初步满足了构成排列的条件,还需要进一步确认每个数字的出现次数是否恰好为 1。

通过循环 for num in range(1, window_size + 1): 来遍历从 1 到当前子区间长度的所有数字,检查在 num_count 哈希表中每个数字的计数是否为 1。如果发现有数字的计数不为 1,就说明不符合排列定义,该窗口对应的子区间不是满足条件的排列;如果所有数字的计数都为 1,那就说明当前窗口内的数字确实构成了一个符合要求的排列,此时将计数器(代码中的 count 变量)加 1,表示找到了一个满足条件的子区间。

  1. 返回最终结果:
    经过上述步骤,对所有可能的子区间长度都进行了考察,也统计出了满足条件的子区间个数,最后函数返回 count 的值,这个值就是整个给定数组中满足 “区间内部的数构成一个排列” 条件的子区间的总数量。

代码实现

from collections import defaultdict

def solution(n: int, a: list) -> int:
    count = 0
    for window_size in range(1, n + 1):  # 遍历所有可能的窗口大小(即排列长度)
        left = 0
        num_count = defaultdict(int)
        for right in range(len(a)):
            num_count[a[right]] += 1
            while right - left + 1 > window_size:
                num_count[a[left]] -= 1
                if num_count[a[left]] == 0:
                    del num_count[a[left]]
                left += 1
            if right - left + 1 == window_size and len(num_count) == window_size:
                flag = True
                for num in range(1, window_size + 1):
                    if num_count[num]!= 1:
                        flag = False
                        break
                if flag:
                    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)