如何从分区问题的动态规划二维表回溯得到等和子集?
如何从分区问题的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("该集合无法分成两个等和子集"); } } }
代码说明
- 把DP表构建逻辑抽成
buildDPTable方法,让判断和回溯逻辑复用同一份表,避免重复计算。 getPartitions方法从DP表右下角开始回溯,通过dpTable[currentIndex-1][currentSum]的状态,判断当前元素属于哪个子集。- 回溯过程中直接完成两个子集的元素收集,最后返回结果。
运行你的测试用例,会输出类似这样的结果(可能存在多个可行解,回溯路径不同会得到不同的正确子集):
子集1: [6, 1, 1, 5] 子集2: [2, 5, 3, 1]
内容的提问来源于stack exchange,提问作者sahil mehta
相关产品推荐
相关产品推荐

