【问题标题】:Number of Distinct Subarrays不同子阵列的数量
【发布时间】:2013-07-05 00:02:19
【问题描述】:

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

例如,在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] 不不同。

【问题讨论】:

  • 你可以在这里查看stackoverflow.com/questions/2710713/…
  • @user93353:这不是数学。这是一个算法问题
  • 你的例子是错误的。有 8 个子数组。你忘了[],它是每个数组的子数组。否则,您必须将 sub-array 定义为 非空 连续序列...

标签: arrays algorithm


【解决方案1】:

为此数组构造后缀树。然后将这棵树中所有边的长度相加。

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

【讨论】:

  • 如何为整数数组实现后缀树,方法的时间复杂度是多少?
  • @Mod:作为具有大字母大小的普通后缀树。每个节点都可以实现为一个映射(key=数组中的数字,value=指向后代节点的链接+“子字符串”)。
  • 能否提供一个清晰的实现或参考以及复杂性?
  • 您可以创建一个与后缀树具有相同结果的结构,该结构更容易实现(但效率可能较低),使用后缀的排序列表并取消相邻的前缀。我在python中找到了一个解决问题的实现;虽然,它使用字符串而不是列表:mmhs.ca/ccc/2003/S4Substringscl.txt
  • @Mod:实现会有点冗长。恐怕我无法在这里描述它。至于参考,请获取任何字符串处理书籍或阅读此 pdf:"Suffix Trees and Suffix Arrays" by Srinivas Aluru
【解决方案2】:

您可以简单地制作一组子序列并计算它们,但我不确定这是最有效的方法,因为它是 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。哎呀仍然无法投票。
  • 我不明白 O(N^2) 是怎么回事。您制作了一组 O(n^2) 的子序列,并将每个子序列与另一个子序列进行比较。然后变成 O(N^4)。
  • 我会说这将是 O(n^2 log n),因为插入一个元素需要 O(log n) 在一个集合中。
  • @Mod 这里的比较不是 O(1) 需要 O(n) 时间来检查两个列表是否相同。这使得算法 O(n^3 log(n))
  • erf 这比我的解决方案要好^^ +1
【解决方案3】:

编辑:我考虑如何减少迭代/比较次数。 我找到了一种方法:如果你检索一个大小为 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 次迭代
  • 这里的问题是 List.contains 的复杂性,可以用 HashSet 代替(contains 会变成 o(1) 而不是 o(n))。
【解决方案4】:

是的,我的第一个答案有点像金发女郎。

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

【讨论】:

  • OP 想要 distinctarrays 的数量,而不是子 sets 的数量。 (顺便说一句,上限为 (N-1)*N/2, IICC)
  • subarray != 子集,正如您的回答所暗示的那样。子集是来自初始集合(集合或数组)的一组项目。 subarray 是一个保持顺序和连续性的子组。
  • 对不起,我误解了这个问题
【解决方案5】:

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

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

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

【讨论】:

    【解决方案6】:

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

    pair[i] = (A[i],i);
    

    A[i] 的升序和i 的降序对这对进行排序。

    考虑示例A = [1,3,6,3,6,3,1,3];
    排序后的pair数组将是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)。这是我能得到的最好的。

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

    【讨论】:

      猜你喜欢
      • 2014-09-25
      • 2021-04-23
      • 2023-03-21
      • 2014-01-10
      • 2013-12-30
      • 1970-01-01
      • 1970-01-01
      • 2013-11-30
      相关资源
      最近更新 更多