You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从分区问题的动态规划二维表回溯得到等和子集?

如何从分区问题的DP表中回溯得到等和子集

好问题!你已经用动态规划搞定了分区问题的可行性判断,现在要从二维DP表里挖出具体的子集其实很直观,咱们一步步来拆解怎么做。

首先明确你代码里DP表T的含义:T[i][j]代表前i个元素能否组成和为j的子集。回溯的核心就是从表的右下角(T[arr.length][sum],sum是总和的一半)倒推,判断每个元素是否被选入了目标子集。

回溯的核心逻辑

咱们从最后一个元素开始倒着分析:

  • 如果T[i][j]为true,且T[i-1][j]也为true:说明不用选第i个元素(也就是arr[i-1],数组索引从0开始),前i-1个元素已经能凑出和为j的子集,直接把i减1继续。
  • 如果T[i][j]为true,但T[i-1][j]是false:说明必须选第i个元素才能凑出和为j的子集,把这个元素加入其中一个子集,然后让j减去该元素的值,再把i减1继续。
  • 重复这个过程,直到i减到0或者j减到0,剩下的元素自然就是另一个子集。

完整实现代码

我给你修改了原代码,添加了getPartitions方法来获取具体的两个子集:

import java.util.ArrayList;
import java.util.List;

public class SubsetSum {
    // 抽离DP表构建逻辑,供判断和回溯复用
    private boolean[][] buildDPTable(int arr[]) {
        int sum = 0;
        for (int num : arr) {
            sum += num;
        }
        // 总和为奇数,直接返回空表表示无法分区
        if (sum % 2 != 0) {
            return null;
        }
        sum = sum / 2;
        boolean[][] T = new boolean[arr.length + 1][sum + 1];
        // 初始化:和为0的情况,任何元素组合都能实现
        for (int i = 0; i <= arr.length; i++) {
            T[i][0] = true;
        }
        // 填充DP表
        for (int i = 1; i <= arr.length; i++) {
            for (int j = 1; j <= sum; j++) {
                if (j - arr[i - 1] >= 0) {
                    T[i][j] = T[i - 1][j - arr[i - 1]] || T[i - 1][j];
                } else {
                    T[i][j] = T[i - 1][j];
                }
            }
        }
        return T;
    }

    // 获取两个等和子集的方法
    public List<List<Integer>> getPartitions(int arr[]) {
        List<List<Integer>> result = new ArrayList<>();
        boolean[][] dpTable = buildDPTable(arr);
        // 无法分区时返回空结果
        if (dpTable == null || !dpTable[arr.length][dpTable[0].length - 1]) {
            return result;
        }

        int targetSum = dpTable[0].length - 1;
        int currentIndex = arr.length;
        int currentSum = targetSum;
        List<Integer> subset1 = new ArrayList<>();
        List<Integer> subset2 = new ArrayList<>();

        // 回溯DP表,收集第一个子集的元素
        while (currentIndex > 0 && currentSum > 0) {
            // 不选当前元素也能凑出目标和,说明当前元素属于子集2
            if (dpTable[currentIndex-1][currentSum]) {
                currentIndex--;
                subset2.add(arr[currentIndex]);
            } else {
                // 必须选当前元素才能凑出目标和,加入子集1
                subset1.add(arr[currentIndex-1]);
                currentSum -= arr[currentIndex-1];
                currentIndex--;
            }
        }
        // 处理剩余元素(当前和已为0,剩下的都加入子集2)
        while (currentIndex > 0) {
            subset2.add(arr[currentIndex-1]);
            currentIndex--;
        }

        result.add(subset1);
        result.add(subset2);
        return result;
    }

    // 保留原有的分区判断方法
    public boolean partition(int arr[]) {
        boolean[][] dpTable = buildDPTable(arr);
        return dpTable != null && dpTable[arr.length][dpTable[0].length - 1];
    }

    public static void main(String args[]) {
        SubsetSum ss = new SubsetSum();
        int arr[] = {1, 3, 5, 5, 2, 1, 1, 6};
        
        if (ss.partition(arr)) {
            List<List<Integer>> partitions = ss.getPartitions(arr);
            System.out.println("子集1: " + partitions.get(0));
            System.out.println("子集2: " + partitions.get(1));
        } else {
            System.out.println("该集合无法分成两个等和子集");
        }
    }
}

代码说明

  1. 把DP表构建逻辑抽成buildDPTable方法,让判断和回溯逻辑复用同一份表,避免重复计算。
  2. getPartitions方法从DP表右下角开始回溯,通过dpTable[currentIndex-1][currentSum]的状态,判断当前元素属于哪个子集。
  3. 回溯过程中直接完成两个子集的元素收集,最后返回结果。

运行你的测试用例,会输出类似这样的结果(可能存在多个可行解,回溯路径不同会得到不同的正确子集):

子集1: [6, 1, 1, 5]
子集2: [2, 5, 3, 1]

内容的提问来源于stack exchange,提问作者sahil mehta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:08:07