在数组中找到具有相同总和的两个子集

Find the two subsets with the same sum in an array

我尝试解决一个编码问题,该问题要求我给出数组中具有相同总和的两个子集。

例如,我们的输入可能是[3,1,2,4]。我对这个问题的预期解决方案是 [[3,2],[1,4]][[2,3],[4,1]](两者都可以接受。但没有重复的答案被接受,例如 [[3,2],[1,4],[2,3],[4,1]])因为 1 + 4 = 2 + 3。如果我的输入不能产生这样的组合,我的代码只能输出 Null[[]].

我尝试使用 DFS(深度优先搜索)解决这个问题,我当前的代码如下所示:

public static List<List<Integer>> twoPartSum(int[] arr){

    // corner case

    List<List<Integer>> ret = new ArrayList<>();
    List<Integer> part = new ArrayList<>();

    if(arr == null || arr.length <= 1){
        ret.add(part);
        return ret;
    }

    // usual case

    int sum = 0;
    for(int i : arr){
        sum += i;
    }

    helper(arr, 0, sum/2, 0, ret, part);

    return ret;

}

private static void helper(int[] arr, int curSum ,int target, int index, List<List<Integer>> ret, List<Integer> part){

    //base case
    if(curSum == target){

        ret.add(new ArrayList<>(part));
        return;
    }

    // such a pair does not exits
    if(curSum > target ){
        return;
    }


    for(int i = index; i < arr.length; i++){

        swap(arr, i, index);
        curSum += arr[index];
        part.add(arr[index]);

        helper(arr, curSum, target, index + 1, ret, part);

        curSum -= arr[index];
        part.remove(index);
        swap(arr, i, index);

    }


}

private static void swap(int[] arr, int i, int j){

    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;

}

我当前的结果是 [[3,2],[1,4],[2,3],[4,1]],输入 [3,1,2,4] 不幸的是,我不知道如何删除重复结果。谁能提供一些想法?提前致谢!

输入中可能有重复的数字,例如 [3,1,1,1,2,4]。而且,显然,我目前的解决方案无法涵盖这种情况。如果您也能提供这样的通用算法,我将不胜感激。但如果它太难了,我会很高兴现在知道一个不同数组的解决方案。

为了对结果进行去重,您可以对子集进行排序,即每个子集中的元素必须按递增顺序排列(或者递减排列,如果您愿意的话)。这意味着 [3,2] 将被重写为 [2,3],因此您将在示例中检索 [2,3],[2,3],[1,4],[1,4]。如果您将子集存储在 Set 而不是 ArrayList 中,则不会首先添加重复项,您将拥有 [2,3], [1,4].

我终于想出了一个更好的方法来解决这个问题。我想与对这个问题感兴趣的人分享。该代码还可以处理输入数组中的重复元素。

我改变了这个问题的逻辑。此代码的递归树如下所示。基本上,它是一棵二叉树,每个分支代表是否向数组中添加元素。数组的索引是这棵树的深度。

                       /            \
  arr[0] = 3          3(add 3)       0(don't add 3)     depth(index of the arr) == 0  
                   /      \       /      \
  arr[1] = 1    1+3      0+3     1+0      0+0           depth == 1
               /  \     /   \   /   \    /   \
  arr[2] = 2 2+4  0+4  2+3 0+3 2+1  0+1 2+0  0+0        depth == 2
             /\   /\   /\  /\   /\   /\  /\   /\ 
            ......

上面的树是对数组所有子集求和的完整解。当然,我们可以通过为这个特定问题指定停止标准来停止任何分支,即

// base case 1: found the answer
if(curSum == target){
    ret.add(new ArrayList<>(part));
    return;
}

我的问题的有效代码如下:

public static List<List<Integer>> twoPartSum2(int[] arr){

    // corner case

    List<List<Integer>> ret = new ArrayList<>();
    List<Integer> part = new ArrayList<>();

    if(arr == null || arr.length <= 1){
        ret.add(part);
        return ret;
    }

    // usual case

    Arrays.sort(arr); // make sure the same numbers will be lined together

    int sum = 0;
    for(int i : arr){
        sum += i;
    }

    helper2(arr, 0, sum/2, 0, ret, part);

    return ret;

}

private static void helper2(int[] arr, int curSum ,int target, int index, List<List<Integer>> ret, List<Integer> part){

    // base case 1: found the answer
    if(curSum == target){
        ret.add(new ArrayList<>(part));
        return;
    }

    // base case 2: solution not found
    if(index == arr.length || curSum > target){
        return;
    }

    // recursion case 1: adding current element in the candidate list ("part")
    part.add(arr[index]);
    helper2(arr, curSum + arr[index], target,index + 1,ret, part);
    part.remove(part.size()-1);

    // deduplicate the same elements
    while(index + 1 < arr.length && arr[index] == arr[index+1]){
        index++;
    }

    // recursion case 2: not adding current element in the candidate list ("part")
    helper2(arr, curSum, target, index + 1,ret,part);

}

请注意,为了对我们的解决方案进行重复数据删除,我们必须跳过数组中的相同元素,这正是数组在最开始排序的原因 Arrays.sort(arr); 然后我们有以下内容在每一层跳过相同的元素。

// deduplicate the same elements
while(index + 1 < arr.length && arr[index] == arr[index+1]){
    index++;
}

例如,如果我们的数组是[1,1,1,3]。那么我们将得到一个递归树,如下所示:

                           /             \
  arr[0] = 1              1               0
                     /        \           |    (the other two '1's are skipped)
  arr[1] = 1       1+1        0+1         |     
                  /   \        |          |     (the other one '1' is skipped)   
  arr[2] = 1    1+2   0+2      |          | 
               / \    / \     / \        / \
  arr[3] = 3 3+3 0+3 3+2 0+2 3+1 0+1   3+0 0+0

答案是:6,3,5,2,4,1,3,0

有些人可能会问为什么我们有两个 3? 好吧,实际上,它们在这个问题中是不同的 3 因为第一个 3 是通过 1+1+1 获得的,而第二个是来自 3,即数组中的最后一个元素 [1,1,1,3].结果,他们对这个问题还是有不同的解决方案。

我之前在这个问题中使用的逻辑仍然有效,但我现在不想 post 它,因为它更令人困惑。但是,如果有人仍然对此问题感兴趣,请发表评论,我会在有空时更新。谢谢!