不同子数组的数量

13

我希望找到一个算法来计算数组的不同子数组数量。

例如,在 A=[1,2,1,2] 的情况下,不同子数组的数量是7:

{ [1] , [2] , [1,2] , [2,1] , [1,2,1] , [2,1,2], [1,2,1,2]}  

B = [1,1,1] 时,不同子数组的数量为3:

{ [1] , [1,1] , [1,1,1] }

子数组是一个连续的子序列或切片。 不同 表示不同的内容; 例如:

在A中,[1]来自A [0:1]和[1]来自A [2:3]不是不同的。

同样地:

B [0:1],B [1:2],B [2:3]也不是不同的。


你可以在这里查看 https://dev59.com/MXE85IYBdhLWcg3wkkfK - Ozan Deniz
@user93353:这不是数学问题,而是算法问题。 - Fallen
你的例子是错误的。有8个子数组。你忘记了[],它是每个数组的子数组。否则,你必须将sub-array定义为非空连续序列... - Bakuriu
6个回答

10

构建该数组的后缀树,然后将该树中所有边的长度相加。

使用适当的算法(Ukkonen或McCreight算法),构建后缀树所需的时间为O(n)。遍历树并将长度相加所需的时间也是O(n)。


1
请您提供一个清晰的实现或参考文献以及复杂度信息。 - Mod
您可以创建一个结构,其结果与后缀树相同,但使用已排序的后缀列表并取消相邻前缀更容易实现(但可能不太有效)。我在Python中找到了一个解决该问题的实现;尽管它使用字符串而不是列表:http://mmhs.ca/ccc/2003/S4Substringscl.txt - Ryan
@Mod:实现可能会有点冗长。恐怕我无法在这里进行描述。至于参考资料,请获取任何字符串处理书籍或阅读此PDF:"Suffix Trees and Suffix Arrays" by Srinivas Aluru - Evgeny Kluev
我不理解边长的含义。 - Mod
我不认为后缀树足够好。在给定的例子中(A = [1, 2, 1, 2]),[2, 1] 在后缀树中并没有出现。 - njzk2
显示剩余11条评论

1
你可以轻松地制作一个子序列集并对其进行计数,但我不确定这是最有效的方法,因为它的时间复杂度为O(n^2)。在Python中,代码可能如下所示:
subs = [tuple(A[i:j]) for i in range(0, len(A)) for j in range(i + 1, len(A) + 1)]

uniqSubs = set(subs)

这会给你:

set([(1, 2), (1, 2, 1), (1,), (1, 2, 1, 2), (2,), (2, 1), (2, 1, 2)])

在推导式的双重循环中,明显表示了复杂度为O(n²)

编辑

显然,有一些关于复杂度的讨论。创建子集的复杂度为O(n^2),因为有n^2个项目。

从列表创建一个集合的复杂度为O(m),其中m是列表的大小,在这种情况下mn^2,因为添加到一个集合的摊销时间是O(1)

因此总体复杂度为O(n^2)


谢谢你,njxk2,但我想要更好的复杂度,但仍然+1。 哎呀,还是无法点赞。 - Mod
2
我不明白为什么是O(N^2)。你创建了一个子序列的集合,这是O(n^2),并将每个子序列与另一个进行比较。那么它就变成了O(N^4)。 - Shashwat Kumar
1
@Mod 这里的比较不是O(1),而是需要O(n)时间来检查两个列表是否相同。这使得算法的时间复杂度为O(n^3 log(n))。 - banarun
问题不在于相等比较的数量(因为set只使用哈希,因此实际上只比较了少量序列;大多数比较都被避免了),而在于计算哈希所需的时间,其复杂度为O(n)。这应该导致此解决方案的平均复杂度为O(n^3) - Bakuriu
"subs数组的创建显然不是O(n^2)。在subs的定义中,操作A[i:j]需要O(j-i),而不是O(1)。实际上,subs的总内存使用量为O(n^3),因此创建它所需的时间不可能更快。" - xuanji
显示剩余3条评论

1
编辑:我考虑如何减少迭代/比较次数。 我找到了一个方法:如果你检索一个大小为n的子数组,那么每个小于n的子数组都已经被添加了。
这是更新后的代码。
    List<Integer> A = new ArrayList<Integer>();
    A.add(1);
    A.add(2);
    A.add(1);
    A.add(2);

    System.out.println("global list to study: " + A);

    //global list
    List<List<Integer>> listOfUniqueList = new ArrayList<List<Integer>>();      

    // iterate on 1st position in list, start at 0
    for (int initialPos=0; initialPos<A.size(); initialPos++) {

        // iterate on liste size, start on full list and then decrease size
        for (int currentListSize=A.size()-initialPos; currentListSize>0; currentListSize--) {

            //initialize current list.
            List<Integer> currentList = new ArrayList<Integer>();

            // iterate on each (corresponding) int of global list
            for ( int i = 0; i<currentListSize; i++) {
                currentList.add(A.get(initialPos+i));
            }

            // insure unicity
            if (!listOfUniqueList.contains(currentList)){
                listOfUniqueList.add(currentList);                      
            } else {
                continue;
            }
        }
    }

System.out.println("list retrieved: " + listOfUniqueList);
System.out.println("size of list retrieved: " + listOfUniqueList.size());

全局研究列表: [1, 2, 1, 2]

检索到的列表: [[1, 2, 1, 2], [1, 2, 1], [1, 2], [1], [2, 1, 2], [2, 1], [2]]

检索到的列表大小: 7

如果列表中包含相同的模式,迭代和比较的次数将非常低。对于你的例子[1, 2, 1, 2],行if (!listOfUniqueList.contains(currentList)){将执行10次。仅在包含15个不同子数组的输入[1, 2, 1, 2, 1, 2, 1, 2]中才会增加到36。


为了帮助优化,我应该明确这个算法对于一个包含36个元素的数组进行了8436次迭代。 - skoll
问题在于 List.contains 的复杂度很高,可以用 HashSet 替代(contains 时间复杂度从 o(n) 变为 o(1))。 - njzk2

0
创建一个二元组数组,每个二元组存储子数组元素的值和其索引。
pair[i] = (A[i],i);

A[i]递增顺序和i递减顺序对成对数据进行排序。

考虑示例A = [1,3,6,3,6,3,1,3];
排序后的成对数组为pair = [(1,6),(1,0),(3,7),(3,5),(3,3),(3,1),(6,4),(6,2)]

pair[0] 元素的索引为 6。从索引 6 开始,我们可以得到两个子数组 [1][1,3]。因此,ANS = 2
现在逐一取每个连续的对。
pair[0]pair[1]
pair[1] 的索引为 0。我们可以从索引 0 开始得到 8 个子数组。但是已经计算了两个子数组 [1] 和 [1,3]。因此,为了去除它们,我们需要比较 pair[0]pair[1] 的子数组的最长公共前缀长度。因此,从索引 0 和 6 开始的最长公共前缀长度为 2,即 [1,3]
因此,现在新的不同子数组将是 [1,3,6] .. 到 [1,3,6,3,6,3,1,3],即 6 个子数组。 因此,ANS 的新值为 2+6 = 8;

对于 pair[i]pair[i+1]
ANS = ANS + 以 pair[i+1] 开头的子数组数目 - 最长公共前缀的长度

排序部分需要 O(n logn) 时间。
迭代每个连续的一对需要 O(n),每次迭代查找最长公共前缀需要 O(n),整个迭代部分的时间复杂度为 O(n^2)。这是我能获得的最好结果。

您可以看到,我们不需要对此使用 pair。第一个值对于元素的值并不是必需的。我使用它只是为了更好理解。您可以随时跳过它。


0

我的第一个答案有点愚蠢。

我猜答案是生成所有的数组,然后删除重复项。或者如果你使用像Java这样带有set对象的语言,可以将所有的数组都添加到int[]的set中。Set只包含每个元素的一个实例,并自动删除重复项,因此您只需在最后获取set的大小即可。


OP想要的是不同子数组的数量,而不是子集。(顺便说一下,子集的上限为(N-1)*N/2,如果我没记错的话) - wildplasser
子数组不等于子集,正如您的答案所示。子集是从初始集合(集或数组)中选择的一组项目。子数组是保留顺序和连续性的子组。 - njzk2
我错了,我误解了问题。 - user1646196

0

我可以想到两种方法...

第一种是计算某种哈希值,然后添加到一个集合中。 如果在添加时哈希值与现有数组相同...那么进行详细比较...并记录下来,以便知道您的哈希算法不够好...

第二种是使用某种可能匹配,然后从那里深入... 如果元素数量相同且所有元素的总和相同,则进行详细检查。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接