【问题标题】:How can I cut down the complexity of O(n^3) ? (Code is provided)如何降低 O(n^3) 的复杂性? (提供代码)
【发布时间】:2023-03-31 07:01:01
【问题描述】:

我正在编写这个方法来查找 3 个数组中的公共数字的数量(允许重复,例如 if A=[1,3,3,3,6], B=[3,3,1,5], C=[3,3,1,5,2] 然后方法应该返回 3;两个 3 + 一个 1)。我使用了 3 个 for 循环,但我觉得应该有更好的方法来做到这一点。

这是我的代码:

private static int common(int[] A, int[] B, int[] C)
{
    int c=0;
    List<Integer> visitedBs=new ArrayList<Integer>(), visitedCs=new ArrayList<Integer>();

    for (int i = 0; i < A.length; i++)
        outerloop:
        for (int j = 0; j <B.length ; j++)
            if (A[i]==B[j] && !visitedBs.contains(j))
                for (int k = 0; k < C.length; k++)
                    if (B[j] == C[k] && !visitedCs.contains(k)) {
                        c++;
                        visitedBs.add(j);
                        visitedCs.add(k);
                        break outerloop;
                    }

    return c;
}

有人知道我应该如何降低时间复杂度吗?有没有办法使用 2 个 for 循环来代替?

【问题讨论】:

  • 这真的是三次方,而不是 4 次方,这要归功于通过 visitedCs 进行的线性搜索?
  • 你可以在 O(n log n) 内完成。首先对每个数组进行排序,然后使用一个循环,在这些数组中只增加三个索引。注释太短,无法给出算法。你自己可能会发现。
  • @harold 有趣!我没有考虑到这一点。我认为这可能是第四次幂。有没有办法避免使用'''visitedCs'''?
  • @Seelenvirtuose 我认为这可行。我会试一试。谢谢!
  • Java 有没有类似于 Python 的 collections.Counter 的东西?这将为您提供线性时间解决方案。 (本质上,sum((Counter(A) &amp; Counter(B) &amp; Counter(C)).values())。)

标签: java algorithm performance time-complexity


【解决方案1】:

我想复杂性会更糟,因为 contains() 也执行 for 循环。

你可以创建3个包含A、B、C内容的集合,我称它们为X_remaining。对于 A_remaining 中的每个元素,检查它是否包含在 B_remaining 和 C_remaining 中。如果是这样,增加 c 并删除所有集合中找到的元素。删除时尽量重用搜索结果不再搜索。

要比线性更快地查找元素,您可以使用 TreeSet。也许 HashSet 也是你的一个选择。

【讨论】:

    【解决方案2】:

    这是一种方法,可以实现此任务的O(n^2) 复杂性:

    private static int common(int[] A, int[] B, int[] C) { 
      List<Integer> listA = Arrays.stream(A).boxed().collect(Collectors.toList());
      List<Integer> listB = Arrays.stream(B).boxed().collect(Collectors.toList());
      List<Integer> listC = Arrays.stream(C).boxed().collect(Collectors.toList());
    
      listA.retainAll(listB);
      listA.retainAll(listC);
    
      listB.retainAll(listA);
      listB.retainAll(listC);
    
      listC.retainAll(listA);
      listC.retainAll(listB);
    
      return Math.min(listA.size(), Math.min(listB.size(), listC.size()));
    }
    

    另外,IMO 你可以得到O(n) 解决方案,例如:

    private static long common(int[] A, int[] B, int[] C) {
      Map<Integer, Long> frequencyA = findFrequencies(A);
      Map<Integer, Long> frequencyB = findFrequencies(B);
      Map<Integer, Long> frequencyC = findFrequencies(C);
    
      Set<Integer> common = frequencyA.keySet();
      common.retainAll(frequencyB.keySet());
      common.retainAll(frequencyC.keySet());
    
      return frequencyA.entrySet().stream()
        .filter(e -> common.contains(e.getKey()))
        .mapToLong(e -> Math.min(e.getValue(), Math.min(frequencyB.get(e.getKey()), frequencyC.get(e.getKey()))))
        .sum();
    }
    
    private static Map<Integer, Long> findFrequencies(int[] A) {
      return Arrays.stream(A)
        .boxed()
        .collect(Collectors.groupingBy(Integer::intValue, Collectors.counting()));
    }
    

    【讨论】:

      【解决方案3】:

      将每个数组转换为一个 Map,将每个出现的数字映射到该数字的出现次数 (map.compute( (key,prev) -&gt; (prev==null ? 1 : prev+1) ))。这需要 O(N)

      计算所有映射中每个键的最小计数。也是 O(N)

      将所有最小计数相加即可得到答案。也是 O(N)。

      【讨论】:

        【解决方案4】:

        您可以在此处使用HashMap,它会为每个子列表计算其在子列表中出现的次数。一个简单的 Haskell 程序如下所示:

        import Data.Hashable(Hashable)
        import Data.HashMap.Strict(HashMap, alter, elems, empty, intersectionWith)
        import Data.Maybe(maybe)
        
        toCounter :: (Eq a, Hashable a, Integral i) => [a] -> HashMap a i
        toCounter = foldr (alter (Just . maybe 1 (1+))) empty
        
        mergeCount :: (Eq a, Hashable a, Integral i) => HashMap a i -> HashMap a i -> HashMap a i
        mergeCount = intersectionWith min
        

        然后我们可以计算结果:

        calculateMinOverlap :: (Eq a, Hashable a, Integral i, Functor f, Foldable f) => f [a] -> i
        calculateMinOverlap = sum . elems . foldr1 mergeCount . fmap toCounter
        

        然后我们可以计算最小重叠:

        Main> calculateMinOverlap [[1,3,3,3,6], [3,3,1,5], [3,3,1,5,2]]
        3
        

        元素总数需要线性时间,并且该函数可以处理任意数量的子列表(假设至少有一个子列表)。

        toCounter 在子列表的大小上花费线性时间O(m)。当我们执行fmap toCounter 时,我们将因此处理 O(n) 中的所有列表,其中 n 是元素的总数。

        接下来mergeCount 工作在 O(m1+m2)m1 sub>m2 分别是两个HashMaps 中的元素个数。它每次都会返回一个HashMap,其中包含最多两个元素中最小的元素数量。因此这意味着我们的foldr1 mergeCount 也可以在 O(n) 中工作。

        最后我们在O(m)中检索结果的elemsm是最终HashMap中的元素个数,sumO(m) 也是。

        请注意,严格来说,对于大数,两个任意大数中的最小值等需要 O(log v),而 v 是该数字的值。递增等也是如此。因此,如果对象的数量很大,那么严格来说,它可以缩放 O(n log n)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-10-08
          • 2019-04-08
          • 2021-03-29
          • 2022-12-07
          • 1970-01-01
          • 2021-11-26
          相关资源
          最近更新 更多