【问题标题】:Total numbers having frequency k in a given range在给定范围内具有频率 k 的总数
【发布时间】:2019-05-01 11:14:53
【问题描述】:

如何在给定数组的特定范围内(l,r)查找频率=k 的总数。共有 10^5 个格式为 l,r 的查询,每个查询都是在前一个查询的答案的基础上构建的。特别是,在每次查询之后,我们将查询结果增加l,如果l > r,则交换lr。请注意0<=a[i]<=10^9。数组中的总元素为n=10^5

我的尝试:

n,k,q = map(int,input().split())
a = list(map(int,input().split()))
ans = 0
for _ in range(q):
    l,r = map(int,input().split())
    l+=ans
    l%=n
    r+=ans
    r%=n
    if l>r:
        l,r = r,l
    d = {}
    for i in a[l:r+1]:
        try:
            d[i]+=1
        except:
            d[i] = 1
    curr_ans = 0
    for i in d.keys():
        if d[i]==k:
            curr_ans+=1
    ans = curr_ans
    print(ans)

示例输入:
5 2 3
7 6 6 5 5
0 4
3 0
4 1

样本输出:
2
1
1

【问题讨论】:

  • 您的尝试有什么问题?
  • 我不能比 O(n^2) 做得更好。
  • 欢迎来到 Stack Overflow。请包含您尝试的代码(或算法描述),并解释它的问题所在。此外,如果您可以添加一些输入和预期输出示例,这对于完全理解您要解决的问题非常有帮助。
  • @jdehesa 完成..!
  • 变化不大,但如果在开头添加from collections import Counter 并执行d = Counter(a[l:r+1]); ans = sum(1 for v in d.values() if v == k),则可以简化算法的后半部分

标签: arrays algorithm data-structures range-query


【解决方案1】:

如果数组中不同值的个数不太大,可以考虑存储数组只要输入数组,每个唯一值一个,统计每个点出现的值的次数。然后你只需要从开始值中减去结束值来找出有多少频率匹配:

def range_freq_queries(seq, k, queries):
    n = len(seq)
    c = freq_counts(seq)
    result = [0] * len(queries)
    offset = 0
    for i, (l, r) in enumerate(queries):
        result[i] = range_freq_matches(c, offset, l, r, k, n)
        offset = result[i]
    return result

def freq_counts(seq):
    s = {v: i for i, v in enumerate(set(seq))}
    counts = [None] * (len(seq) + 1)
    counts[0] = [0] * len(s)
    for i, v in enumerate(seq, 1):
        counts[i] = list(counts[i - 1])
        j = s[v]
        counts[i][j] += 1
    return counts

def range_freq_matches(counts, offset, start, end, k, n):
    start, end = sorted(((start + offset) % n, (end + offset) % n))
    num = 0
    return sum(1 for cs, ce in zip(counts[start], counts[end + 1]) if ce - cs == k)

seq = [7, 6, 6, 5, 5]
k = 2
queries = [(0, 4), (3, 0), (4, 1)]
print(range_freq_queries(seq, k, queries))
# [2, 1, 1]

您也可以使用 NumPy 更快地做到这一点。由于每个结果都取决于前一个结果,因此无论如何您都必须循环,但您可以使用 Numba 真正加快速度:

import numpy as np
import numba as nb

def range_freq_queries_np(seq, k, queries):
    seq = np.asarray(seq)
    c = freq_counts_np(seq)
    return _range_freq_queries_np_nb(seq, k, queries, c)

@nb.njit  # This is not necessary but will make things faster
def _range_freq_queries_np_nb(seq, k, queries, c):
    n = len(seq)
    offset = np.int32(0)
    out = np.empty(len(queries), dtype=np.int32)
    for i, (l, r) in enumerate(queries):
        l = (l + offset) % n
        r = (r + offset) % n
        l, r = min(l, r), max(l, r)
        out[i] = np.sum(c[r + 1] - c[l] == k)
        offset = out[i]
    return out

def freq_counts_np(seq):
    uniq = np.unique(seq)
    seq_pad = np.concatenate([[uniq.max() + 1], seq])
    comp = seq_pad[:, np.newaxis] == uniq
    return np.cumsum(comp, axis=0)

seq = np.array([7, 6, 6, 5, 5])
k = 2
queries = [(0, 4), (3, 0), (4, 1)]
print(range_freq_queries_np(seq, k, queries))
# [2 1 2]

我们对比一下原来的算法:

from collections import Counter

def range_freq_queries_orig(seq, k, queries):
    n = len(seq)
    ans = 0
    counter = Counter()
    out = [0] * len(queries)
    for i, (l, r) in enumerate(queries):
        l += ans
        l %= n
        r += ans
        r %= n
        if l > r:
            l, r = r, l
        counter.clear()
        counter.update(seq[l:r+1])
        ans = sum(1 for v in counter.values() if v == k)
        out[i] = ans
    return out

这是一个快速测试和时间安排:

import random
import numpy

# Make random input
random.seed(0)
seq = random.choices(range(1000), k=5000)
queries = [(random.choice(range(len(seq))), random.choice(range(len(seq))))
           for _ in range(20000)]
k = 20
# Input as array for NumPy version
seq_arr = np.asarray(seq)
# Check all functions return the same result
res1 = range_freq_queries_orig(seq, k, queries)
res2 = range_freq_queries(seq, k, queries)
print(all(r1 == r2 for r1, r2 in zip(res1, res2)))
# True
res3 = range_freq_queries_np(seq_arr, k, queries)
print(all(r1 == r3 for r1, r3 in zip(res1, res3)))
# True

# Timings
%timeit range_freq_queries_orig(seq, k, queries)
# 3.07 s ± 1.11 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit range_freq_queries(seq, k, queries)
# 1.1 s ± 307 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit range_freq_queries_np(seq_arr, k, queries)
# 265 ms ± 726 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

显然,这样做的有效性取决于数据的特征。特别是,如果重复值较少,则构建计数表的时间和内存成本将接近 O(n2)。

【讨论】:

  • freq_counts_np 中,您不能创建一个二维数组comp,因为它会超出内存限制,因为最大元素可以是 10^9,最大 n = 10^5。因此,大小为 (10^5)*(10^9) 的二维数组将超出内存限制。
  • @MohanSingh 但是表中“列”的数量将等于数组中 unique 值的数量,不能大于数组本身。在最坏的情况下,您将拥有一个大小为 (10^5)*(10^5) 的表格。这仍然太大,但假设会有重复的值,因此不会有那么多唯一数字。
  • @MohanSingh 您还可以“按部分”制作表格,一次获取最大数量的列并对其进行迭代。它需要更少的内存,但需要更多的迭代。但如果你真的很少重复,它可能无论如何都不能很好地工作。
  • 绝对是的。这就是为什么应该有另一种方法来解决这个问题。也许使用线段树或平方根分解。
  • 使用分段树,您可以尝试在每个节点上保留频率图。但是为此,当值不同时,时间复杂度也会很高。如果我错了,请纠正我。
【解决方案2】:

假设输入数组是A|A|=n。我将假设A 中不同元素的数量远小于 n。

我们可以将A 分成大小为 sqrt(n) 的 sqrt(n) 段。对于这些段中的每一个,我们可以计算从元素到计数的映射。构建这些地图需要 O(n) 时间。

完成预处理后,我们可以通过将完全包含在 (l,r) 中的所有映射加在一起来回答每个查询,其中最多有 sqrt(n),然后添加任何额外的元素(或遍历一个段)和减法),还有 sqrt(n)。

如果有 k 个不同的元素,这需要 O(sqrt(n) * k) 所以在最坏的情况下 O(n) 如果事实上 A 的每个元素都是不同的。

您可以在组合散列和额外元素时跟踪具有所需计数的元素。

【讨论】:

    猜你喜欢
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-27
    • 2015-08-25
    相关资源
    最近更新 更多