【问题标题】:Java Recursion - Output from several recursive callsJava 递归 - 多个递归调用的输出
【发布时间】:2016-06-02 16:22:21
【问题描述】:

我的代码将生成candidates 中值的所有组合(包括重复),以便这些值汇总为目标。这是我对https://leetcode.com/problems/combination-sum/ 的解决方案。

我有点困惑为什么我需要包含以下代码行:

currentSet = new ArrayList<>(currentSet);

这实际上使 currentSet 成为所有递归调用的私有变量。否则,currentSet 将是一个共享变量,递归调用将同时修改该变量,从而导致问题。例如,当上面的语句从代码中省略时, combinationSum({1, 2}, 4) 有以下输出:

[[1, 1, 2], [1, 1, 1, 1], [1, 2]]

数组 [1,2] 显然不等于 4。任何人都可以提供一个可靠的解释为什么会发生这种情况吗?

此外,我是否可以进行任何优化,以便我的代码可以避免放入重复但重新排序的数组,因为我当前的蛮力排序和检查是否包含在 HashSet 中的方法会导致非常糟糕的复杂性。

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Set<List<Integer>> returnSet = new HashSet<>();
        returnSet = combSum(candidates, target, 0, returnSet, new ArrayList<Integer>());
        return new ArrayList<>(returnSet);
    }

private Set<List<Integer>> combSum(int[] candidates, int target, int i, Set<List<Integer>> returnSet,
    List<Integer> currentSet) {
    currentSet = new ArrayList<>(currentSet);
    if(i == target) {
        Collections.sort(currentSet);
        if(!returnSet.contains(currentSet)) {
            returnSet.add(new ArrayList<Integer>(currentSet));
        }
    } else if(i <= target){
        System.out.println("Current set: " + returnSet.toString());
        System.out.println("Current sum: " + i + " current target: " + target);
        for(int a: candidates) {
            if(i + a <= target) {
                System.out.println("\tAdding: " + a + " so that the new sum will be: " + (i + a));
                currentSet.add(a);
                returnSet = combSum(candidates, target, i + a, returnSet, currentSet);
                currentSet.remove(currentSet.size() - 1);
            }
        }
    }

    return returnSet;
}

【问题讨论】:

    标签: java algorithm recursion


    【解决方案1】:

    线

    currentSet = new ArrayList<>(currentSet);
    

    是调用复制构造函数的 Java 方式,即您正在创建新的 ArrayList,它最初具有旧列表中的所有元素。但是,原始列表和新列表是独立的,因此新列表的任何更改都不会反映到原始列表中。由于以下几行,这在您的算法中很重要:

                currentSet.add(a);
                returnSet = combSum(candidates, target, i + a, returnSet, currentSet);
                currentSet.remove(currentSet.size() - 1);
    

    在这里,您要在列表末尾添加一个元素,查找与该元素的所有可能递归组合,然后将其删除以尝试使用另一个元素。如果您没有在递归调用中复制列表,currentSet 变量将被修改,并且行

    currentSet.remove(currentSet.size() - 1);
    

    不会删除您在递归调用之前添加的元素,而是在递归中添加的其他一些元素 - 不要忘记您正在对递归中的元素进行排序,因此不会始终保留原始顺序。当您省略复制构造函数时,这就是您的示例中发生的情况。

    可能的优化

    我很自然地想到的是在 combinationSum 方法中在一开始就对候选的初始数组进行排序。遍历已排序的数组将避免重复,并且您不需要检查重复的结果。

    【讨论】:

    • 非常有意义。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-20
    • 1970-01-01
    • 2019-10-18
    • 2011-09-16
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    相关资源
    最近更新 更多