问题 不同子阵列的数量


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

例如,在的情况下 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 [0:1]的[1]和来自A [2:3]的[1]并不明显。

同样地:

B [0:1],B [1:2],B [2:3]不明显。


4976
2017-07-07 15:13


起源

你可以在这里查看 stackoverflow.com/questions/2710713/... - Ozan Deniz
@ user93353:这不是数学。这是一个算法问题 - Fallen
你的榜样是错的。有8个子阵列。你忘了 [],这是每个阵列的子阵列。否则你必须定义 sub-array 作为一个 非空 连续序列...... - Bakuriu


答案:


构造此数组的后缀树。然后在此树中添加所有边的长度。

构造后缀树所需的时间是O(n)和适当的算法(Ukkonen或McCreight的算法)。遍历树并将长度加在一起所需的时间也是O(n)。


9
2017-07-07 16:40



如何为整数数组实现后缀树以及方法的时间复杂度是多少? - Mod
@Mod:作为大字母大小的普通后缀树。每个节点可以实现为映射(key =来自数组的数字,value =链接到后代节点+“substring”)。 - Evgeny Kluev
您能否提供明确的实施或参考以及复杂性? - Mod
您可以创建一个结构与后缀树相同的结构,后缀树使用排序的后缀列表并取消相邻的前缀更容易实现(但效率可能更低)。我在python中找到了一个解决问题的实现;虽然,它使用字符串而不是列表: mmhs.ca/ccc/2003/S4Substringscl.txt - Ryan
@Mod:实施会有点冗长。我恐怕在这里无法形容。至于参考,获取任何字符串处理书或阅读此pdf: Slinivas Aluru的“后缀树和后缀阵列”。 - Evgeny Kluev


你可以简单地制作一组子序列并计算它们,但我不确定它是最有效的方法,因为它是 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 是列表的大小, m 存在 n^2 在这种情况下,因为添加到集合中是摊销的 O(1)

因此整体而言 O(n^2)


2
2017-07-07 15:31



谢谢你,njxk2但是我想要更好的复杂性,但仍然是+1。哎呀还是不能投票。 - Mod
我不明白是怎么回事(N ^ 2)。您创建一组子序列,即O(n ^ 2),并将每个子序列与另一个子序列进行比较。然后它变为O(N ^ 4)。 - Shashwat Kumar
我会说它是O(n ^ 2 log n),因为插入一个元素需要一个集合中的O(log n)。 - Mod
@Mod这里的比较不是O(1)需要O(n)时间来检查两个列表是否相同。这使得算法O(n ^ 3 log(n)) - banarun
@banarun是的,你是对的必须是O(n ^ 3 log(n))。 - Mod


编辑:我考虑如何减少迭代/比较数。 我有办法做到这一点:如果你检索一个大小为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

使用包含相同patern的列表很多次迭代和比较的次数将非常少。 对于您的示例[1,2,1,2],行if(!listOfUniqueList.contains(currentList)){执行10次。对于包含15个不同子阵列的输入[1,2,1,2,1,2,1,2],它仅增加到36。


1
2017-07-07 16:39



为了帮助优化,我应该预先确定该算法对36个元素的数组进行了8436次迭代 - skoll
我更新了我的代码以提高效率 - skoll
这里的问题是List.contains的复杂性,它可以被HashSet替换(包含将变为o(1)而不是o(n))。 - njzk2


我的第一个答案是一个金发时刻。

我想答案是生成所有,然后删除重复。或者,如果您使用带有set对象的Java语言,请创建所有数组并将它们添加到一组int []中。集只包含每个元素的一个实例,并自动删除重复项,因此您可以在结尾处获取集的大小


0
2017-07-07 15:15



OP想要的数量 不同 子 阵列而不是分 套。 (BTW的上限为(N-1)* N / 2,IICC) - wildplasser
subarray!=子集,正如你的答案所暗示的那样。 subset是初始集合(集合或数组)中的一组项目。子阵列是保持顺序和连续性的子组。 - njzk2
我的坏,我误解了这个问题 - user1646196


我能想到两种方式......

首先是计算某种哈希然后添加到集合中。 如果添加你的哈希是相同的是一个现有的数组...然后做一个详细的比较...并记录它,以便你知道你的哈希算法不够好...

第二种是使用某种可能的匹配,然后从那里向下钻取...... 如果元素数量相同且添加在一起的元素总数相同,则请详细检查。


0
2017-07-07 16:06





创建一个pair数组,其中每个对存储子数组元素及其索引的值。

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] 有元素的 index 6。从 index 6 我们可以有两个子阵列 [1] 和 [1,3]。所以 ANS = 2;
现在逐一取每一对。
pair[0] 和 pair[1]
pair[1] 索引为0.我们可以有8个子数组 index 0。但是已经计算了两个子阵列[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 + Number of sub-arrays beginning from pair[i+1] - Length of longest common prefix

排序部分需要O(n logn)。
迭代每个连续对是O(n),并且对于每次迭代,找到最长公共前缀取O(n)使得整个迭代部分O(n ^ 2)。这是我能得到的最好的。

你可以看到我们不需要配对。对的第一个值,元素的值不是必需的。我用它来更好地理解。你总是可以跳过它。


0
2017-07-07 16:19