List集合存入int类型值1,remove(1)方法按下标还是按对象删除信息

ArrayList的remove方法删除元素时,如果集合中存储的是int类型,`list.remove(1)`会按照下标删除,因为int值会被转化为Integer对象存储。若要按对象删除,需使用Integer对象。

ArrayList的Remove方法有两种传值方法删除集合中的元素

1. remove(下标)

2. remove(对象)

问题来了,如果集合中添加的值都是int类型,那么list.remove(1);删除的是下标1的还是值是1的?

        //添加值
        List list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add("张三");

        list.remove(1);

答:是按下标删除

解析:

        因为所有存入集合中的基本数据类型,如 int,全部都会转化为包装类型, Integer 类型。然后再以Object类型存储,所以,通过 list.remove(1); 是按照下标删除而不是对象删除,如果想按照对象删除,同理对象为Integer即可,如:

list.remove(new Integer(1));

参考文章:集合如何存储基本数据类型_chen404897439的博客-优快云博客_集合存储基本数据类型

<think>题目要求:找出数组 records 中所有长度在 [minLen, maxLen] 范围内,且平均大于等于 threshold 的子数组的个数。 注意:区间长度指的是子数组的元素个数(即连续天数),而平均是子数组内所有元素的和除以长度(元素个数)。 由于数组长度最大为10000,而允许的区间长度范围 maxLen 最大为1000(且maxLen<=records.length),我们可以考虑使用滑动窗口结合前缀和的方法。 思路: 1. 计算前缀和数组 prefix,其中 prefix[0]=0, prefix[i]=records[0]+...+records[i-1] 这样,子数组 records[i:j](左闭右开,即从下标i到j-1)的和为 prefix[j]-prefix[i],长度为 j-i。 2. 条件:要求子数组的长度在 [minLen, maxLen] 范围内,且平均 >= threshold,即: (prefix[j]-prefix[i]) / (j-i) >= threshold 等价于:prefix[j]-prefix[i] >= threshold * (j-i) 3. 我们可以固定子数组的右端点 j,然后寻找满足条件的左端点 i。其中,子数组长度 L = j-i 必须满足 minLen<=L<=maxLen,即: j - maxLen <= i <= j - minLen 4. 因此,对于每个右端点 j,我们需要检查的范围是 i 从 max(0, j-maxLen) 到 j-minLen(注意:左端点i要满足i>=0且i<=j-minLen)。 5. 但是直接遍历每个 j 和对应的 i 范围,时间复杂度是 O(n*(maxLen-minLen)),最大为 10000*1000=10e6,在Python中可能勉强通过,但可能超时。 6. 我们需要优化:将条件转换一下: prefix[j] - prefix[i] >= threshold * (j-i) => prefix[j] - threshold * j >= prefix[i] - threshold * i 7. 令 B[i] = prefix[i] - threshold * i 则条件变为:B[j] >= B[i] 8. 因此,对于每个右端点 j,我们需要在 i 属于 [j-maxLen, j-minLen] 的范围内(注意左闭右闭)统计满足 B[i] <= B[j] 的 i 的个数。 9. 注意:i 的取范围是 [j-maxLen, j-minLen](因为长度L=j-i,要求minLen<=L<=maxLen,所以i在[j-maxLen, j-minLen]之间,注意i是下标,且i>=0)。 10. 我们可以使用树状数组或线段树来维护 B[i] 的,并在窗口滑动时动态查询。但是考虑到 j 每次移动一位,且 i 的取范围是一个固定长度的区间(长度为 maxLen-minLen+1,最大1000),我们可以直接在这个区间内维护一个有序数组(或者使用双端队列,但这里需要查询区间内小于等于某个的个数,所以考虑维护一个有序列表,然后二分查找)。 11. 具体步骤: - 初始化一个空列表(用于存放当前窗口内的B[i],并保持有序) - 遍历 j 从 minLen 到 n(因为子数组长度至少minLen,所以j至少从minLen开始) - 每次 j 增加时,需要加入的 i 是 i0 = j - minLen(因为此时这个左端点第一次进入窗口?注意我们的i范围是[j-maxLen, j-minLen]) 但实际上,随着 j 增大,窗口的左边界 j-maxLen 和右边界 j-minLen 都在增大。我们维护一个窗口,窗口内的索引 i 满足 i 在 [L, R] 其中 L=j-maxLen, R=j-minLen。 - 当 j 增大1时,窗口左边界 L 增大1(移除 i = j-1-maxLen),右边界 R 增大1(加入 i = j-minLen)。 注意:j 从 minLen 开始,初始时,i 的取范围为 [0, minLen]?不对,应该是从0到minLen-1?实际我们要的是左端点i,且子数组长度为L,则i=j-L,所以当j=minLen时,i的取范围为 [0, j-minLen=0] 到 [j-maxLen, ...] 但此时j-minLen=0,j-maxLen可能为负数(如果maxLen>minLen,则j-maxLen=minLen-maxLen<0),所以左端点要取max(0, j-maxLen)12. 实际上,我们维护的窗口是:左指针 left = max(0, j - maxLen),右指针 right = j - minLen(注意:j 从0开始计数?但我们的右端点j表示子数组的最后一个元素?这里我们使用前缀和,子数组 [i, j-1] 的和为 prefix[j]-prefix[i],长度为 j-i。所以子数组右边界为 j-1,左边界为 i。那么子数组长度范围要求 minLen<= (j-1-i+1)<=maxLen,即 minLen<= j-i <= maxLen,所以 i 的范围为 j-maxLen <= i <= j-minLen。 因此,当 j 从 minLen 到 n(注意j最大为n,因为子数组的右边界最多到n-1,所以j从minLen到n): 对于当前j,需要加入的左端点 i = j-minLen(注意:此时这个i对应的是子数组 [i, j-1] 的左边界,其中右边界为j-1,所以子数组长度为 j-i,满足最小长度minLen,因为j-i=j-(j-minLen)=minLen) 同时,需要移除的左端点 i = j-1-maxLen(注意:上一个j-1对应的窗口左边界最小(j-1)-maxLen,但当前j对应的左边界最小是j-maxLen,所以移除 i = j-maxLen-1?不对,应该是移除已经不在当前窗口的左端点,即 i = (j-1) - maxLen 可能小于当前窗口的最小左边界 j-maxLen,所以当j增大时,窗口左边界从 (j-1)-maxLen 增大到 j-maxLen,因此需要移除 i = (j-1)-maxLen 如果它存在的话?实际上每次移除的是 i = j - maxLen - 1?不对,因为我们窗口左边界是 j-maxLen,所以索引小于 j-maxLen 的左端点都应该被移除。因此,当 j 增大1,窗口左边界变为 j-maxLen,那么原来在窗口中的 i = j-1-maxLen 应该被移除(因为它等于 j-maxLen-1,小于新的左边界 j-maxLen)。 因此,在 j 的循环中: - 当 j>=minLen 时,将 i0 = j-minLen 加入窗口(因为此时这个i0是当前窗口的右边界,即最大的左端点下标)。 - 同时,如果 j>maxLen(注意:这样 j-maxLen-1 才非负),则将 i_remove = j-maxLen-1 从窗口中移除(因为新的窗口左边界是 j-maxLen,所以小于 j-maxLen 的索引要移除,但是我们的窗口维护的是 [j-maxLen, j-minLen] 区间,所以只需要移除 j-maxLen-1 这个点,因为它是上一个窗口的最小左边界?实际上,每次j增加,窗口左边界右移一个单位,所以只需要移除一个点:i = j - maxLen - 1)。 - 然而,注意初始时 j 从 minLen 开始,此时 j-minLen=0,而 j-maxLen 可能为负数(如果minLen<maxLen),所以如果j-maxLen-1为负数,则不需要移除。 13. 但是上面的窗口维护并不包含所有的左端点?因为对于每个 j,窗口内的索引 i 应该是从 j-maxLen 到 j-minLen。我们需要将当前 j 对应的左端点 i 全部加入?实际上,随着 j 增加,窗口在向右滑动,每次加入一个新的左端点(即 j-minLen),同时移除一个旧的左端点(即 j-maxLen-1)。 14. 因此,我们维护一个有序列表 sorted_list,初始为空。 遍历 j 从 minLen 到 n(包括n,因为子数组右边界可以到n-1,则j=n时,子数组为 [i, n-1]): - 计算当前左端点 i_new = j - minLen。如果 i_new >=0 且 i_new <= n(注意i_new最大为n-minLen<=n),那么将 B[i_new] 插入有序列表(并保持有序)。 - 计算需要移除的左端点 i_old = j - maxLen - 1。如果 i_old >=0 且 i_old<=n,那么从有序列表中移除 B[i_old](注意可能有重复?但每个i对应一个B[i],且i唯一,所以直接按移除,但注意可能有相同的B[i]?所以需要记录索引?实际上我们按移除可能移除多个相同的?因此,我们需要使用二分查找找到该的位置,但可能有多个相同的,而我们只想移除一个(对应索引i_old)。所以更好的办法是,我们维护一个列表,同时记录索引?或者我们直接按删除一次?但是如果有重复,可能会误删。所以我们可以维护一个列表,只存储,并在删除删除最早加入的那个?但这里我们每次删除都是确定的索引对应的,所以我们可以在加入时记录每个索引对应的,然后删除时直接根据删除,但注意相同的可能来自不同的索引,所以我们不能简单删除。因此,我们使用二分查找位置,然后删除那个位置的(因为我们知道索引,但有序列表里只存,所以当我们加入一个时,我们不知道它来自哪个索引?但是实际上,同一个在不同索引出现是允许的,所以删除时我们只需要删除一个?但是删除哪一个呢?我们只删除一个?实际上,我们每次加入和删除都是成对的,每个索引加入一次,删除一次。所以我们可以用平衡二叉树?但Python的bisect可以处理重复,我们直接按照删除,但删除删除第一个出现的?不行,因为删除的是特定索引对应的,而不同索引可能有相同的。所以我们需要在有序列表中存储(B[i], i)?然后根据i来删除?但是这样插入和删除都是O(窗口大小),因为窗口大小最大为maxLen-minLen+1(约1000),所以我们可以线性扫描删除?或者我们只存,然后删除删除指定(不管位置)?但这样会删除所有该,不合理。 另一种方法是,我们不维护整个窗口的有序列表,而是对于每个 j,在窗口 [j-maxLen, j-minLen] 内遍历?窗口大小最多1000,然后我们在这个窗口内遍历计数,这样总的时间复杂度是O(n*(maxLen-minLen)),最大10000*1000=10e6,在Python中可能勉强通过(10e6次操作在Pyton中可能1秒左右?但题目数组长度10000,maxLen-minLen最大1000,所以最坏10^7,可能会超时,但实际可能不会达到最坏,因为minLen和maxLen可能接近?或者我们可以优化)。 15. 由于窗口大小固定为 maxLen-minLen+1(最大1000),我们可以不用有序列表,而是每次遍历窗口内的所有索引进行计数。这样总操作次数为 n * (maxLen-minLen+1),最多10000*1000=10e6,在C++中没问题,在Python中应该也能接受(Pyton一般在1e6~1e7次操作)。 16. 因此,我们采用直接遍历窗口的方法: for j in range(minLen, n+1): # j从minLen到n(包括n) left_bound = max(0, j - maxLen) # 当前左端点最小 right_bound = j - minLen # 当前左端点最大 # 遍历 i 从 left_bound 到 right_bound count_j = 0 for i in range(left_bound, right_bound+1): if B[i] <= B[j]: # 注意:这里B[j]=prefix[j]-threshold*j count_j += 1 count += count_j 但是注意:这里的 j 是前缀和数组的下标,对应子数组的右边界是 j(子数组为 [i, j-1]),所以 j 的范围是 minLen 到 n(因为子数组长度至少minLen,则需要j>=minLen)。 17. 但是这样会超时吗?最坏情况:minLen=1, maxLen=1000,那么窗口大小是1000,然后j从110000,总操作10000*1000=10e6,在Python中可能1秒内完成?但是题目数组长度最大10000,10e6次操作在Pyton中可能达到1秒(CPython可能更快?),但还要考虑其他操作。所以我们可以尝试。 18. 但是,我们还可以优化内层循环:我们可以在窗口内维护一个有序数组,然后二分查找。这样内层循环的复杂度是O(log(窗口大小)),总复杂度O(n*log(窗口大小)),最多10000*log2(1000)10000*10=10e4,这样更快。 19. 因此,我们选择使用有序列表(用bisect模块)来维护窗口内的B[i](只存,不存索引)。注意:我们不需要考虑索引,因为我们只关心,并且我们每次加入和删除都是按照左端点i的位置(即按照j的变化加入和删除)。但是,同一个可能出现多次,所以我们要用列表,并且用bisect来插入和删除删除时需要删除指定,但注意可能多个相同,我们只删除一个?但因为我们知道要删除是多少,而且删除的是最早加入的那个(实际上,我们加入和删除都是按照左端点i的顺序,而每个i对应一个B[i],所以每个加入一次删除一次)。但是,在有序列表中删除一个元素需要O(窗口大小)的时间?我们可以用bisect找到位置,然后用pop或remove?但是remove也是O(n)的。所以整体复杂度还是O(n*窗口大小),和直接遍历一样。 20. 我们可以使用平衡二叉树结构吗?Python中有sorted list,但是标准库没有。我们可以用bisect来插入和删除,但是删除一个元素需要O(窗口大小)的时间。所以如果窗口大小1000,那么删除和插入都是O(1000),总复杂度O(n*1000)=10e6。 21. 实际上,我们不需要删除,因为窗口大小固定,我们可以用双指针,但是我们需要查询窗口内小于等于B[j]的元素个数,所以需要动态维护一个数据结构,支持插入、删除和查询小于等于某个的元素个数。我们可以用树状数组,但是域可能很大(B[i]的范围?prefix[i]最大为10000*10000=1e8,threshold最大10000,i最大10000,所以B[i]的范围约为-1e8到1e8,域很大,需要离散化)。 22. 考虑到窗口大小只有1000,我们选择直接遍历窗口(1000次)来统计,总操作10000*1000=10e6,在Python中可能运行时间在1秒左右(实际题目中数组长度10000,1000的窗口,10e6次操作在Pyton中可能1秒内,可以接受)。 23. 步骤总结: n = len(records) minL, maxL = length_range # minL=minLen, maxL=maxLen # 构建前缀和数组,长度为n+1 prefix = [0] * (n+1) for i in range(1, n+1): prefix[i] = prefix[i-1] + records[i-1] # 构建数组 B,长度为n+1,B[i] = prefix[i] - threshold * i B = [0] * (n+1) for i in range(1, n+1): B[i] = prefix[i] - threshold * i # 注意:B[0]=prefix[0]-threshold*0=0 count = 0 # 遍历右端点 j(前缀和数组下标),j从minL到n(因为子数组长度至少为minL,所以j>=minL) # 注意:子数组对应原数组的区间 [i, j-1](左闭右闭),其中左端点i从0到n-1,右端点j-1从minL-1到n-1。 # 对于每个j,左端点i的取范围:满足 minL <= j-i <= maxL,且0<=i<=n # 所以 i 在 [max(0, j - maxL), j - minL] 闭区间内。 # 我们预先用一个列表来维护当前窗口内的B[i](窗口为当前j对应的左端点i的范围),但我们可以不预先维护,而是每次遍历窗口。 # 直接遍历窗口: for j in range(minL, n+1): low_i = max(0, j - maxL) high_i = j - minL # 注意这里是闭区间,所以i可以从low_i到high_i # 统计在区间[low_i, high_i]内,有多少个i满足 B[i] <= B[j] for i in range(low_i, high_i+1): if B[i] <= B[j]: count += 1 return count 24. 但是,这样写会超时吗?我们计算一下:j从minL到n(n最大10000),内层循环次数为窗口大小(maxL-minL+1,最大1000),那么总操作次数为 (n-minL+1)*(maxL-minL+1) <= 10000*1000=10e6,在Python中可能1秒内完成(C++肯定没问题,Python需要测试,但题目要求返回整数,且数据范围如此,我们假设可以通过)。 25. 但是,我们需要注意:当minL和maxL很接近时,窗口很小,效率就会很高;当maxL-minL很大(接近1000)且n很大(10000)时,10e6次操作在Pyton中可能耗时1秒左右。我们尝试优化内层循环? 26. 我们可以预先计算窗口内的B[i],然后排序?但是窗口随着j在滑动,所以不好预先排序。 27. 另一种思路:将条件转换,我们要求的是:对于每个j,在区间 [low_i, high_i] 内,满足 B[i]<=B[j] 的i的个数。我们可以用树状数组,但是树状数组需要离散化整个B数组(因为域很大)。离散化后,我们可以在滑动窗口的同时更新树状数组。具体: - 离散化:将B数组的所有进行离散化(因为窗口大小最多1000,但是我们整个B数组有n+1个点,所以离散化整个B数组需要O(n log n),然后对于每个j,我们需要加入B[j-minL](当j>=minL时)和移除B[j-maxL-1](当j>maxL时),然后查询窗口内(窗口内的所有B[i])小于等于B[j]的个数。 - 但是窗口内的元素个数最多1000,所以我们也可以不用树状数组,而是维护一个树状数组只针对窗口内的元素?但离散化整个B数组太大,而窗口内只有1000个元素,所以我们可以对窗口内的元素离散化?然后树状数组大小1000,然后每次移动窗口时更新树状数组(加入一个元素和删除一个元素,然后查询)?这样每次操作O(log1000)10,总操作O(n*10)=100000,很快。 步骤: 维护一个窗口(队列)存储当前的B[i](i在[j-maxL, j-minL])。 同时,为了快速查询,我们维护一个树状数组(或线段树)用于求区间和(按域)。但是我们需要离散化窗口内的?然而窗口内的会变化,所以每次加入新元素和删除旧元素都需要更新离散化映射?这样不可行。 28. 因此,我们还是选择直接遍历窗口。考虑到最大10e6次操作,在Python中我们可以接受。 29. 但是,我们还需要考虑边界:当minL>n时,不可能有子数组,但题目保证minLen<=maxLen<=records.length,所以minL<=n。 30. 还有一个边界:当阈threshold很大时,可能满足条件的区间很少,但我们的内层循环次数只与窗口大小有关,与条件无关,所以无法跳过。 31. 因此,我们按照直接遍历窗口的方法实现。 32. 但是,我们忽略了一个问题:条件转换后的B[j]>=B[i]等价于平均条件吗? 原条件:prefix[j]-prefix[i] >= threshold*(j-i) 转换:prefix[j]-threshold*j >= prefix[i]-threshold*i -> B[j]>=B[i] 所以正确。 33. 但是注意:我们的子数组是从i到j-1(因为前缀和的定义:prefix[j]-prefix[i] = records[i]+...+records[j-1]),所以子数组长度为 j-i(即元素个数)。所以条件正确。 34. 代码实现: 注意:我们的窗口是左端点i的范围 [j-maxL, j-minL](闭区间),且j从minL到n(包括n)。 特殊情况:当j-minL<0?不会,因为j>=minL,所以j-minL>=0。 当j-maxL可能为负数,所以用max(0, j-maxL)。 35. 测试:用一个简单例子。 records = [1,2,3,4,5], length_range=[2,3], threshold=3 要求:子数组长度在[2,3]内,平均>=3。 子数组: [1,2]:avg=1.5 -> 不满足 [2,3]:avg=2.5 -> 不满足 [3,4]:avg=3.5 -> 满足 [4,5]:avg=4.5 -> 满足 [1,2,3]:avg=2 -> 不满足 [2,3,4]:avg=3 -> 满足 [3,4,5]:avg=4 -> 满足 所以满足的区间有:[3,4], [4,5], [2,3,4], [3,4,5] -> 4个。 用我们的方法: n=5 prefix = [0,1,3,6,10,15] B = [0, 1-3*1=-2, 3-3*2=-3, 6-3*3=-3, 10-3*4=-2, 15-3*5=0] j从minL=2到5(包括5): j=2: 左端点i的范围:max(0,2-3)=0, 到 2-2=0 -> i=0 条件:B[0]=0, B[2]=-3 -> 0>=-3,满足,所以count+1 -> 1。这个对应子数组[0,1]即原数组的[0:2) -> [0,1]也就是[1,2],但实际上这个子数组平均1.5<3,不应该被计数!错误。 问题在哪里? 我们的子数组:i和j的定义?子数组为 records[i:j] 即从i到j-1,长度为j-i。 当j=2时,i=0:子数组为 records[0:2] -> [1,2](下标0和1),平均1.5。 条件:B[0]<=B[2] -> 0<=-3 不成立!所以不计数。我们上面的代码写的是 if B[i]<=B[j] 才计数,而这里0<=-3不成立,所以不计。所以上面例子中j=2时计数0。 重新计算: j=2: i=0 -> B[0]=0, B[2]=-3 -> 0<=-3? 不成立 -> 不计数。 j=3: i的范围:max(0,3-3)=0, 到 j-minL=3-2=1 -> i=0,1 i=0: B[0]=0<=B[3]=-3? 0<=-3 ->不成立。 i=1: B[1]=-2<=B[3]=-3? -> -2<=-3 ->不成立。 j=4: i的范围:max(0,4-3)=1, 到 j-minL=4-2=2 -> i=1,2 i=1: B[1]=-2<=B[4]=-2? 成立 ->计数1 i=2: B[2]=-3<=B[4]=-2? 成立 ->计数2 (当前总计数3) j=5: i的范围:max(0,5-3)=2, 到 5-2=3 -> i=2,3 i=2: B[2]=-3<=B[5]=0? 成立 ->计数3 i=3: B[3]=-3<=B[5]=0? 成立 ->计数4 所以总计数4,正确。 但是,我们看j=3时,应该对应哪些子数组? j=3:子数组右边界是j-1=2,即原数组下标为0,1,2(对应元素1,2,3)?但实际上我们考虑的子数组是 [i, j-1],当j=3时,子数组为 [0,2](长度为3)和 [1,2](长度为2)。我们要求长度在[2,3]内,所以两个都考虑。 [0,2]:对应i=0,长度为3,平均(1+2+3)/3=2<3 ->不应该计入。 [1,2]:对应i=1,长度为2,平均(2+3)/2=2.5<3 ->不应该计入。 所以j=3时计数0正确。 再看j=4:对应子数组: i=1:子数组[1,3](长度为3)即元素[2,3,4],平均3,满足。 i=2:子数组[2,3](长度为2)即元素[3,4],平均3.5,满足。 j=5:对应子数组: i=2:子数组[2,4](长度为3)即[3,4,5],平均4,满足。 i=3:子数组[3,4](长度为2)即[4,5],平均4.5,满足。 所以计数4。 36. 但是,再检查一下j=2: 对应的子数组只有i=0,即[0,1](元素1,2),平均1.5,不满足。所以不计。 37. 因此,代码正确。 38. 但是,我们还要注意:我们的B[0]对应的是prefix[0](即没有元素时),我们允许i=0,因为prefix[j]-prefix[0]是前j个元素的和(即原数组[0,j-1])。所以合理。 39. 但是,我们上面的例子中,当j=5的时候,i=2和3,对应的子数组是[2,4]和[3,4](即原数组下标2,3,4和3,4),正确。 40. 因此,代码实现如下: 注意:由于内层循环可能达到10e6,我们尽量优化内层循环(比如用局部变量替代全局变量)。 41. 优化:将B[j]和B[i]的访问用局部变量存储。 42. 完整代码: from typing import List, Tuple class Solution: def count_intervals(self, records: List[int], length_range: Tuple[int, int], threshold: int) -> int: n = len(records) minL, maxL = length_range # 特殊情况:如果没有记录,返回0 if n == 0: return 0 # 构建前缀和 prefix = [0] * (n+1) for i in range(1, n+1): prefix[i] = prefix[i-1] + records[i-1] # 构建数组B B = [0] * (n+1) # B[0]=0 for i in range(1, n+1): B[i] = prefix[i] - threshold * i count = 0 # 遍历j从minL到n # 注意:当j<minL时,没有满足长度>=minL的子数组,所以跳过。 for j in range(minL, n+1): low_i = j - maxL if low_i < 0: low_i = 0 high_i = j - minL # 注意:high_i必须>=low_i,否则跳过 if high_i < low_i: continue # 遍历i从low_i到high_i # 优化:将B[j]存入局部变量 b_j = B[j] for i in range(low_i, high_i+1): if B[i] <= b_j: count += 1 return count 43. 但是,我们可以进一步优化:如果内层循环能使用二分查找,则可以降低复杂度。我们可以维护窗口内B[i]的有序数组,然后二分查找窗口内有多少个数<=B[j]。但是窗口是滑动的,我们需要动态维护一个有序列表,并支持添加和删除元素。 由于窗口大小最多为maxL-minL+1(最大1000),我们可以用二分查找来定位插入位置,但是删除的时候需要线性时间(因为列表的删除是O(n)的)。这样总复杂度O(n*window_size)和遍历一样。 所以如果直接遍历窗口内元素的时间可以接受,就不需要优化。 44. 但是,我们尝试另一种优化:将窗口内B[i]排序,然后二分查找B[j]的位置,这样内层循环复杂度O(log(window_size)),但是由于每次添加和删除元素需要O(window_size)的时间,所以总时间复杂度仍然是O(n*window_size)。所以没有改善。 45. 因此,我们保留直接遍历窗口内元素的方法。 46. 然而,最坏情况10e6次操作,在Python中可能会超时(10000*1000=10e6,在Pyton中大约1秒?但实际运行环境可能不同,我们最好优化)。 47. 我们可以考虑用归并排序的思想?但是窗口滑动,很难。 48. 或者,我们可以预先计算所有B,然后对于每个j,我们只考虑左端点i的范围,然后利用之前计算的结果?但似乎不行。 49. 我们尝试另一种方法:将左端点的B[i]按照i从小到大存储,同时窗口是连续的一段下标(i是连续的),所以我们可以用一个队列维护窗口内的B[i],然后维护这个队列的升序排列(类似单调队列?),但是这里我们每次需要查询队列内小于等于B[j]的元素个数。我们可以用平衡二叉搜索树,但Python没有。 50. 因此,我们回到直接遍历。提交后如果超时再考虑优化。 51. 实际上,题目中n最大10000,maxL-minL最大1000,那么总操作数10000*1000=10e6,在Pyton中应该能通过(在Pyton3中,10e6次简单操作大约0.5~1秒)。 52. 我们编写代码并进行测试。 53. 注意:有可能出现浮点数问题?但B[i]=prefix[i]-threshold*i,两者都是整数,所以整数比较不会有浮点问题。 54. 最终代码: from typing import List, Tuple class Solution: def count_intervals(self, records: List[int], length_range: Tuple[int, int], threshold: int) -> int: n = len(records) minL, maxL = length_range if n == 0: return 0 # 前缀和 prefix = [0] * (n+1) for idx in range(n): prefix[idx+1] = prefix[idx] + records[idx] # 构建B数组 B = [0] * (n+1) # B[0]=0 for i in range(1, n+1): B[i] = prefix[i] - threshold * i cnt = 0 # 枚举j,从minL到n(包括n) # 注意:j表示子数组的右边界+1,即子数组为[i, j-1] for j in range(minL, n+1): low_i = j - maxL high_i = j - minL if low_i < 0: low_i = 0 if high_i < low_i: continue bj = B[j] # 遍历左端点i for i in range(low_i, high_i+1): if B[i] <= bj: cnt += 1 return cnt 55. 测试:用之前的小例子 [1,2,3,4,5], [2,3], 3 -> 4,通过。 56. 再测试一个边界:records=[0], length_range=[1,1], threshold=0 n=1, minL=1, maxL=1 j从minL=11 prefix=[0,0] B[0]=0, B[1]=0-0*1=0 j=1: low_i=1-1=0, high_i=1-1=0 -> i=0 B[0]=0<=B[1]=0 ->计数1 子数组:长度为1,即[0],平均0>=0,满足。所以返回1,正确。 57. 另一个边界:records=[1,0], length_range=[1,2], threshold=1 n=2 prefix=[0,1,1] B[0]=0, B[1]=1-1*1=0, B[2]=1-1*2=-1 j=1: low_i=1-2=-1->0, high_i=1-1=0 -> i=0: 0<=B[1]=0 ->计数1(子数组[0]:平均1>=1) j=2: low_i=2-2=0, high_i=2-1=1 -> i=0:0<=B[2]=-1? 0<=-1不成立;i=1:0<=-1不成立 -> 计数0 另外,子数组[0,1]:平均(1+0)/2=0.5<1,所以不满足。 所以总计数1,正确。 58. 因此,我们提交这个代码。 但是,我们担心最坏情况会超时。如果超时,我们可以使用树状数组+离散化(对整个B数组离散化)来优化内层循环为O(log n),同时用滑动窗口维护当前窗口内的B[i](用树状数组维护窗口内每个离散化出现的次数)。 优化版本(如果上面版本超时,则用这个): 步骤: n = len(records) minL, maxL = length_range # 计算前缀和和B数组(长度n+1) prefix = [0]*(n+1) for i in range(1, n+1): prefix[i] = prefix[i-1] + records[i-1] B = [prefix[i] - threshold * i for i in range(n+1)] # 离散化B数组:注意,我们只需要离散化B数组,然后树状数组的下标是离散化后的。 # 但是窗口大小最多maxL-minL+1,所以实际上我们只需要窗口内的元素?不对,我们要查询整个B数组(离散化整个B数组)?因为树状数组需要整个域。 # 离散化整个B数组(有n+1个数据) sorted_B = sorted(set(B)) dic = {} for idx, val in enumerate(sorted_B): dic[val] = idx+1 # 离散化后的1-indexed) size = len(sorted_B) # 初始化树状数组(大小为size) tree = [0]*(size+1) # 1-indexed # 树状数组函数 def lowbit(x): return x & (-x) def update(pos, delta): while pos <= size: tree[pos] += delta pos += lowbit(pos) def query(pos): # 查询[1,pos]的区间和 s = 0 while pos: s += tree[pos] pos -= lowbit(pos) return s # 滑动窗口:左端点i的范围,随着j变化 cnt = 0 # 首先,将左端点i在[minL, maxL]?实际上,对于j=minL,我们需要加入的左端点i的范围是 [0, minL-minL=0] 即i=0,但是我们的树状数组需要提前加入这些点吗? # 我们需要在j从minL到n的过程中,动态加入左端点 i=j-minL,并移除左端点 i=j-maxL-1(当j>maxL时) # 初始化:在j=minL之前,没有任何左端点加入。然后j从minL开始,每次加入一个左端点(即i=j-minL),当j>maxL时,移除一个左端点(即i=j-maxL-1)。 # 注意:左端点i的取范围:对于当前的j,左端点i在区间 [j-maxL, j-minL] 内。 # 因此,在j=minL时,我们需要加入的左端点是 i=j-minL=0(因为minL-minL=0)?但是j=minL时,左端点i的范围是 [minL-maxL, 0](因为j-minL=0,j-maxL=minL-maxL<=0,所以左端点范围是[max(0,minL-maxL)=0,0])。 # 所以,我们这样: # for j in range(minL, n+1): # 新的左端点:i_new = j - minL -> 加入树状数组:update(dic[B[i_new]], 1) # 如果j>maxL:那么需要移除左端点 i_old = j - maxL - 1,更新:update(dic[B[i_old]], -1) # 然后,查询当前树状数组中有多少个数小于等于B[j](即query(dic[B[j]]),注意离散化后的),然后累加这个数量 # 但是注意:当前树状数组中存储的是当前窗口内(即左端点i在[j-maxL, j-minL])的B[i]。我们要求满足B[i]<=B[j]的个数,就是query(dic[B[j]])。 for j in range(minL, n+1): i_new = j - minL # 加入这个左端点 pos_new = dic[B[i_new]] update(pos_new, 1) if j > maxL: i_old = j - maxL - 1 pos_old = dic[B[i_old]] update(pos_old, -1) # 查询当前树状数组中小于等于B[j]的元素个数 # 注意:当前树状数组中存储的是左端点i在 [j-maxL, j-minL] 区间的B[i] # 而我们要查询的是这些B[i]中有多少个<=B[j] # 由于树状数组维护的是离散化后的,我们直接查询query(dic[B[j]])?不对,因为离散化后,相同会被压缩,但是树状数组的query(dic[B[j]])返回的就是<=B[j]的个数(因为离散化映射后,比B[j]小的数对应的离散小于等于dic[B[j]])。 cnt += query(dic[B[j]]) return cnt 然而,这个版本需要验证正确性。而且,注意树状数组在查询时,我们查询的是离散化后的,因为离散化映射后,我们保证了从小到大的顺序,所以树状数组的query(dic[B[j]])就是窗口内所有小于等于B[j]的个数。 但是,我们注意:离散化时,相同的B[i]会被映射到同一个离散,所以树状数组计数正确。 但是,这个版本的复杂度为O(n log(window_size)),但离散化整个B数组需要O(n log n),然后树状数组每次操作O(log(size))(size最大为n+1,即10000),所以总复杂度O(n log n),比直接遍历的O(n*(maxL-minL))10e6)可能更快。 考虑到题目中n最大10000,log n≈14,所以O(n log n)140000,很快。 因此,我们用树状数组的方法来实现。 但是,注意:树状数组查询的是整个域中小于等于B[j]的个数,但是我们只加入了当前窗口内的左端点。所以正确。 但是,我们的离散化是针对整个B数组的,所以树状数组的大小是离散化后的域大小(最大10001)。 实现步骤: 1. 计算前缀和和B数组。 2. 离散化整个B数组(包括0~n)。 3. 初始化树状数组(全0)。 4. 遍历j从minL到n: 将左端点 i = j-minL 加入树状数组(即更新树状数组在位置dic[B[i]]的+1)。 如果j>maxL,则移除左端点 i = j-maxL-1(树状数组更新-1)。 查询树状数组中小于等于B[j]的个数(即query(dic[B[j]])),并累加到答案。 5. 返回答案。 注意:树状数组的query(pos)返回的是小于等于当前离散化pos(对应的原为B[j])的个数。 测试例子:records=[1,2,3,4,5], length_range=[2,3], threshold=3 B = [0, -2, -3, -3, -2, 0] 离散化:sorted_B = sorted(set([0,-2,-3,-3,-2,0])) = [-3,-2,0] dic: -3->1, -2->2, 0->3 树状数组初始化:tree = [0]*(4) -> 索引0不用,1~3。 j=minL=2: i_new=2-2=0 -> B[0]=0 -> 离散化3 -> update(3,1): tree[3]+=1, 然后tree[3]=1(同时更新父节点?树状数组更新) 然后查询:B[2]=-3 -> 离散化1 -> query(1) = tree[1](但是树状数组还没有下标1?) 树状数组:tree[1]=0, tree[2]=0, tree[3]=1 -> 查询1:位置1: tree[1]=0,然后lowbit(1)=1,所以结束,返回0?不对,query(1)应该返回tree[1]的(因为1的二进制001,所以只加tree[1])->0。 所以cnt=0。 j=3: i_new=3-2=1 -> B[1]=-2 -> update(2,1) j>maxL? 3>3? 否,不删除。 查询:B[3]=-3 -> 离散化1 -> query(1)=0 -> cnt=0。 j=4: i_new=4-2=2 -> B[2]=-3 -> update(1,1) -> tree[1]=1, tree[2]=1? 树状数组: 更新:位置1:tree[1]+=1,然后更新父节点:位置2(1+lowbit(1)=2)-> tree[2]+=1;位置4(2+2=4,超出范围,结束)-> tree[4]不存在,所以停止。 然后,j>maxL? 4>3 -> 移除 i_old=4-3-1=0 -> B[0]=0 -> 离散化3 -> update(3,-1): tree[3]=0(原来tree[3]=1,现在0),并更新父节点:位置4(3+1=4,跳出)-> 所以tree[3]=0, tree[2]不用更新?不对,树状数组更新:更新位置3:tree[3]-=1,然后位置4(3+1=4>size=3,结束)。 然后,查询:B[4]=-2 -> 离散化2 -> query(2)=tree[2](tree[2]在更新位置1的时候已经更新为1了?)-> 树状数组的query(2): s=0 pos=2: s+=tree[2]=1, 然后pos=2-lowbit(2)=0 -> 结束。所以返回1。 然后cnt=0+0+1=1。 j=5: i_new=5-2=3 -> B[3]=-3 -> update(1,1) -> 此时树状数组位置1:tree[1]原来是1,现在变为2?不对,tree[1]原本为1,更新后为2;然后更新父节点:位置2(1+1=2)-> tree[2]=1+1=2;位置4(2+2=4>3,结束)。 j>maxL: 5>3 -> 移除 i_old=5-3-1=1 -> B[1]=-2 -> update(2,-1): tree[2]从2->1,并更新父节点:位置4(2+2=4>3,结束)。 查询:B[5]=0 -> 离散化3 -> query(3)= tree[3]+tree[2](因为3的二进制011,先加tree[3]=0,然后3-lowbit(3)=011-001=010=2,加tree[2]=1) -> 一共1。 cnt=1+1=2。 最终计数2,但正确答案是4。所以错误。 错误分析: 在j=4时,窗口内的左端点i:j-minL=4-2=2, j-maxL=4-3=1,所以左端点i在[1,2](注意:闭区间,包括1和2)?不对,我们的窗口左端点i的范围是:[j-maxL, j-minL] -> [4-3=1, 4-2=2]。 但是,在树状数组中,我们加入了哪些点? j=2: 加入i=0 -> 此时窗口内[0](但此时对应的左端点i范围应该是[0,0]) j=3: 加入i=1 -> 窗口内[0,1](但此时j=3,左端点范围应该是[0,1]?不对,j=3时,左端点范围:[3-3=0, 3-2=1] -> [0,1],所以加入i=1后,窗口内应该有i=0(在j=2加入)和i=1(在j=3加入))。 j=4: 加入i=2 -> 窗口内应该变成[0,1,2]?但是j=4时,左端点范围应该是[4-3=1,4-2=2] -> [1,2]。所以当j>maxL时(j>=4>3),我们会在j=4时移除i_old=4-3-1=0(即移除i=0),所以窗口内为[1,2]。 j=5: 加入i=3 -> 窗口内[1,2,3],然后移除i_old=5-3-1=1 -> 移除i=1,所以窗口内[2,3]。 因此,树状数组在j=4时,窗口内是i=1和2,而我们加入树状数组的点:i=1(B[1]=-2)和i=2(B[2]=-3) -> 在树状数组中,离散化后,位置2(-2)有1,位置1(-3)有1。 查询B[4]=-2,离散化2,query(2)返回的是树状数组中小于等于-2的个数:包括-2(1个)和-3(1个) -> 2个。所以j=4时应计数2。 在j=5时,窗口内是i=2和3:B[2]=-3, B[3]=-3 -> 树状数组:位置1(-3)有2个(注意之前加入i=2时在位置11,加入i=3时也在位置11,然后移除i=1(位置2减1)后,位置1还是2?不对,我们加入i=2(j=4)时在位置11,加入i=3(j=5)时在位置11,然后移除i=1(j=5)时在位置2减1,所以树状数组位置1有2,位置2有0?不对,位置2在移除i=1(B[1]=-2)时减1,所以位置2从1变为0。所以树状数组只有位置1有2。 查询B[5]=0,离散化3,query(3)返回树状数组所有元素个数(因为0是离散化最大),即2。所以j=5计数2。 总计数:j=2:0, j=3:0, j=4:2, j=5:2 -> 4,正确。 因此,代码修改:在树状数组版本中,我们加入和移除的索引是: j: 从minL到n 加入:i = j - minL (必须确保j>=minL,所以i>=0) 移除:i = j - maxL - 1 (当j>maxL时,即j>=maxL+1时,移除 i = j-maxL-1。注意j-maxL-1要>=0,所以当j>maxL时,j-maxL-1>=0?不一定,如果j-maxL-1<0则不移除) 所以,在移除时,需要判断 i_old = j-maxL-1 >=0 才移除。 修正后的树状数组版本代码: from typing import List, Tuple class Solution: def count_intervals(self, records: List[int], length_range: Tuple[int, int], threshold: int) -> int: n = len(records) minL, maxL = length_range if n == 0: return 0 # 前缀和 prefix = [0] * (n+1) for i in range(1, n+1): prefix[i] = prefix[i-1] + records[i-1] # B数组 B = [0] * (n+1) for i in range(1, n+1): B[i] = prefix[i] - threshold * i # 离散化 sorted_B = sorted(set(B)) size_b = len(sorted_B) dic = {val: idx+1 for idx, val in enumerate(sorted_B)} # 离散化映射到1..size_b # 树状数组 tree = [0] * (size_b+1) # 1-indexed def lowbit(x): return x & -x def update(pos, delta): while pos <= size_b: tree[pos] += delta pos += lowbit(pos) def query(pos): s = 0 while pos: s += tree[pos] pos -= lowbit(pos) return s total_count = 0 # 遍历j from minL to n for j in range(minL, n+1): # 加入新的左端点 i = j - minL i_new = j - minL # 离散化位置 pos_new = dic[B[i_new]] update(pos_new, 1) # 当 j > maxL 时,移除 i_old = j - maxL - 1 (如果>=0) if j > maxL: i_old = j - maxL - 1 if i_old >= 0: # 确保不越界 pos_old = dic[B[i_old]] update(pos_old, -1) # 查询 # 注意:B[j]可能不在离散化字典中?但B[j] in set(B) -> 所以dic[B[j]]存在。 pos_j = dic[B[j]] total_count += query(pos_j) return total_count 59. 测试:records=[1,2,3,4,5], [2,3], 3 -> 4 records=[0], [1,1], 0 -> 1 records=[1,0], [1,2], 1 -> 1 60. 这个版本的时间复杂度为O(n log(size_b)),size_b<=n+1,所以O(n log n),效率更高。 61. 因此,我们选择树状数组版本。 注意:树状数组下标1开始。 由于题目要求,我们提交树状数组版本(即使直接暴力也能过,但树状数组更优)。 62. 但是,离散化时,我们用了set(B)然后排序,注意B[0]也在内。 63. 编写代码并测试。 64. 边界:n=0 -> return 0。 65. 提交。 </think>```python from typing import List, Tuple class Solution: def count_intervals(self, records: List[int], length_range: Tuple[int, int], threshold: int) -> int: n = len(records) minL, maxL = length_range if n == 0: return 0 # 计算前缀和 prefix = [0] * (n + 1) for i in range(1, n + 1): prefix[i] = prefix[i - 1] + records[i - 1] # 构建B数组:B[i] = prefix[i] - threshold * i B = [0] * (n + 1) for i in range(1, n + 1): B[i] = prefix[i] - threshold * i # 离散化B数组 unique_b = sorted(set(B)) val_to_idx = {val: idx + 1 for idx, val in enumerate(unique_b)} size = len(unique_b) # 树状数组实现 tree = [0] * (size + 1) def lowbit(x): return x & -x def update(pos, delta): while pos <= size: tree[pos] += delta pos += lowbit(pos) def query(pos): s = 0 while pos > 0: s += tree[pos] pos -= lowbit(pos) return s count = 0 # 遍历右端点j从minL到n for j in range(minL, n + 1): # 加入新左端点:i = j - minL new_i = j - minL new_pos = val_to_idx[B[new_i]] update(new_pos, 1) # 移除过期左端点:当j>maxL时,移除i = j - maxL - 1 if j > maxL: old_i = j - maxL - 1 # 确保索引有效 if old_i >= 0: old_pos = val_to_idx[B[old_i]] update(old_pos, -1) # 查询当前窗口内满足B[i] <= B[j]的个数 j_pos = val_to_idx[B[j]] count += query(j_pos) return count ``` ### 算法说明: 1. **前缀和与转换**:计算数组前缀和,并构建转换数组 `B`,其中 `B[i] = prefix[i] - threshold * i`。这步将平均条件转化为 `B[j] >= B[i]`。 2. **离散化**:对 `B` 数组离散化处理,将大范围映射到紧凑区间,便于树状数组操作。 3. **树状数组**:用于高效维护滑动窗口内的 `B[i]` ,支持动态添加/删除及范围查询。 4. **滑动窗口**:遍历右端点 `j`(子数组结束位置+1),动态维护左端点 `i` 的范围 `[j-maxL, j-minL]`: - 加入新左端点 `j-minL` - 当 `j > maxL` 时移除过期左端点 `j-maxL-1` - 查询窗口内满足 `B[i] <= B[j]` 的数量 5. **复杂度**: - 时间复杂度:$O(n \log n)$(离散化 + 树状数组操作) - 空间复杂度:$O(n)$(存储前缀和、离散化映射等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾欲乘风归去,又恐琼楼玉宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值