循环中递归函数的O复杂度

时间:2020-05-26 16:14:48

标签: algorithm time-complexity big-o depth-first-search

我遇到了一个Leetcode问题的解决方案,该问题发现了不断增加的子序列。我认为该解决方案的复杂度为O(N!),可能无法扩展到大型阵列。

能否请您详细说明一下如何为此计算复杂度?

public class IncreasingSubsequences {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] a = new int[]{4, 6, 7, 8};
        findSubsequences(a);
    }

    public static List<List<Integer>> findSubsequences(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        helper(new LinkedList<Integer>(), 0, nums, res);
        return res;
    }

    // [4, 6, 7, 7]
    private static void helper(LinkedList<Integer> list, int index, int[] nums, List<List<Integer>> res) {

        if (list.size() > 1){
            res.add(new LinkedList<Integer>(list));
            System.out.println(Arrays.toString(list.toArray()));
        }

        Set<Integer> used = new HashSet<>();

        for (int i = index; i < nums.length; i++) {
            if (used.contains(nums[i]))
                continue;
            if (list.size() == 0 || nums[i] >= list.peekLast()) {
                used.add(nums[i]);
                list.add(nums[i]);
                helper(list, i + 1, nums, res);
                System.out.println("Will remove" + list.get(list.size() - 1));
                list.remove(list.size() - 1);
                //System.out.println(">>" + Arrays.toString(list.toArray()));
            }
        }
    }

}

1 个答案:

答案 0 :(得分:0)

您怀疑时间和内存的复杂度几乎是O(N!)。更准确地说,附加内存为O(N * 2 N ),时间为O(M * 2 N )。其中M是原始列表nums的长度,N是其中唯一值的数量(s.t. N <= M)。

当您意识到递归函数对于结果列表中的每个值都被调用一次之后,就很容易得出复杂度表达式。当输入nums包含递增顺序的项目时,结果列表将包含nums值的所有可能子集。实际上,该程序将打印并返回原始集的幂集。如果还有不增加的项目,则结果可能会较小但不会较大。

如果原始集合包含N个(唯一的)值(按nums的升序排列),则幂集合包含2个 N 个唯一集合。这也是结果列表的长度,以及对递归函数的调用次数。

可能显示原始集中的每个元素恰好出现在子集的一半中(假设升序)。这意味着,如果我们写入结果的所有2 N 个列表(代表所有子集),并将它们连接起来,则每一项将出现2 N / 2次。由于存在N个唯一值,所以连接列表的长度将为2 N * N / 2,即O(N * 2 N )。

我们可以放心地忽略包含所有子集的顶级列表的长度,因为它比并置在一起的所有列表都短。因此,最后,额外的空间复杂度为O(N * 2 N )。这也是打印输出(其长度)的复杂性。如果M> N,那么在最坏的情况下,所有重复项都在末尾。这导致所有递归调用必须在每个调用结束时执行其他M-N操作。由于有2 N 个调用,因此时间复杂度变为O(N * 2 N )+ O((M-N)* 2 N )= O(M * 2 N )。

请注意,时间复杂度几乎是最佳的,因为时间复杂度不能低于附加内存复杂度。但是,不可能有比这更好的扩展。唯一效率低下的地方是处理非唯一值。如果在递归之前从nums中除去重复值,则M几乎从整个算法的O()中消除了。在这种情况下,时间复杂度变为O(M + N * 2 N ),其中对于合理的Ms.t。 M <= N * 2 N ,则可以从表达式中删除M。