给定一组整数,找到总和为100,000,000的整数的子集。
我正在尝试构建一个包含给定集合的所有组合以及总和的树。例如,如果给定集看起来像0,1,2
,我将构建以下树,检查每个节点的总和:
{}
{} {0}
{} {1} {0} {0,1}
{} {2} {1} {1,2} {0} {2} {0,1} {0,1,2}
由于我在每个节点都保留了整数数组和总和,我应该只需要内存中树的底部(当前)级别。
我当前的实现将整个树保留在内存中,因此使用的堆空间太多了。
如何更改当前的实现,以便GC处理我的上层树级?
(目前我只是在找到目标总和时抛出RuntimeException,但这显然只是为了玩游戏)
public class RecursiveSolver {
static final int target = 100000000;
static final int[] set = new int[]{98374328, 234234123, 2341234, 123412344, etc...};
Tree initTree() {
return nextLevel(new Tree(null), 0);
}
Tree nextLevel(Tree currentLocation, int current) {
if (current == set.length) { return null; }
else if (currentLocation.sum == target) throw new RuntimeException(currentLocation.getText());
else {
currentLocation.left = nextLevel(currentLocation.copy(), current + 1);
Tree right = currentLocation.copy();
right.value = add(currentLocation.value, set[current]);
right.sum = currentLocation.sum + set[current];
currentLocation.right = nextLevel(right, current + 1);
return currentLocation;
}
}
int[] add(int[] array, int digit) {
if (array == null) {
return new int[]{digit};
}
int[] newValue = new int[array.length + 1];
for (int i = 0; i < array.length; i++) {
newValue[i] = array[i];
}
newValue[array.length] = digit;
return newValue;
}
public static void main(String[] args) {
RecursiveSolver rs = new RecursiveSolver();
Tree subsetTree = rs.initTree();
}
}
class Tree {
Tree left;
Tree right;
int[] value;
int sum;
Tree(int[] value) {
left = null;
right = null;
sum = 0;
this.value = value;
if (value != null) {
for (int i = 0; i < value.length; i++) sum += value[i];
}
}
Tree copy() {
return new Tree(this.value);
}
}
答案 0 :(得分:1)
问题是NP-complete。
如果您真的想提高性能,那么您必须忘记树的实现。您必须只生成所有子集并将它们相加或使用动态编程。
选择取决于要求的元素数量和要实现的总和。你知道它是100,000,000
的总和,bruteforce指数算法在O(2^n * n)
时运行,所以对于低于22的数字,这是有意义的。
在python中,您可以通过简单的方式实现此目的:
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
通过在中间技术中使用meet,可以显着提高这种复杂性(牺牲内存)(阅读wiki article)。这会将其降低到O(2^(n/2))
,这意味着它将比n <~ 53
答案 1 :(得分:1)
在这里构建树所需的时间和空间绝对没有任何内容。
原因是,如果你被给予
您可以使用O(1)
操作计算其父,左和右子节点。并且当您遍历树时,您可以访问这些内容,因此您不需要任何其他内容。
答案 2 :(得分:0)
在仔细考虑了erip的评论之后,我意识到他是正确的 - 我不应该使用树来实现这个算法。
暴力通常为O(n*2^n)
,因为n
子集有2^n
个补充。 因为我每个节点只做一次加法,我提出的解决方案是此外,这个算法只有O(2^n)
,其中n是给定集合的大小。O(n)
个空格复杂。由于我的特定问题中原始集合中的元素数量很少(大约25)O(2^n)
复杂性不是太大的问题。
此问题的动态解决方案是O(t*n)
,其中t
是目标总和,n
是元素数量。因为t
在我的问题中非常大,所以动态解决方案最终会有很长的运行时间和高内存使用率。
这在我的机器上大约311毫秒完成了我的特定解决方案,这比我在这类特殊问题上看到的动态编程解决方案有了很大的改进。
public class TailRecursiveSolver {
public static void main(String[] args) {
final long starttime = System.currentTimeMillis();
try {
step(new Subset(null, 0), 0);
}
catch (RuntimeException ex) {
System.out.println(ex.getMessage());
final long endtime = System.currentTimeMillis();
System.out.println(endtime - starttime);
}
}
static final int target = 100000000;
static final int[] set = new int[]{ . . . };
static void step(Subset current, int counter) {
if (current.sum == target) throw new RuntimeException(current.getText());
else if (counter == set.length) {}
else {
step(new Subset(add(current.subset, set[counter]), current.sum + set[counter]), counter + 1);
step(current, counter + 1);
}
}
static int[] add(int[] array, int digit) {
if (array == null) {
return new int[]{digit};
}
int[] newValue = new int[array.length + 1];
for (int i = 0; i < array.length; i++) {
newValue[i] = array[i];
}
newValue[array.length] = digit;
return newValue;
}
}
class Subset {
int[] subset;
int sum;
Subset(int[] subset, int sum) {
this.subset = subset;
this.sum = sum;
}
public String getText() {
String ret = "";
for (int i = 0; i < (subset == null ? 0 : subset.length); i++) {
ret += " + " + subset[i];
}
if (ret.startsWith(" ")) {
ret = ret.substring(3);
ret = ret + " = " + sum;
} else ret = "null";
return ret;
}
}
编辑 -
上述代码仍在O(n*2^n)
时间运行 - 因为add
方法在O(n)
时间内运行。以下代码将在真正的O(2^n)
时间运行,并且性能更高,在我的机器上完成大约20毫秒。
由于将当前子集存储为long
中的位,因此限制为少于64个元素。
public class SubsetSumSolver {
static boolean found = false;
static final int target = 100000000;
static final int[] set = new int[]{ . . . };
public static void main(String[] args) {
step(0,0,0);
}
static void step(long subset, int sum, int counter) {
if (sum == target) {
found = true;
System.out.println(getText(subset, sum));
}
else if (!found && counter != set.length) {
step(subset + (1 << counter), sum + set[counter], counter + 1);
step(subset, sum, counter + 1);
}
}
static String getText(long subset, int sum) {
String ret = "";
for (int i = 0; i < 64; i++) if((1 & (subset >> i)) == 1) ret += " + " + set[i];
if (ret.startsWith(" ")) ret = ret.substring(3) + " = " + sum;
else ret = "null";
return ret;
}
}
编辑2 -
这是另一个版本在中间攻击中使用一次会议,以及一点点移动,以便将复杂度从O(2^n)
降低到O(2^(n/2))
。
如果要将其用于32到64个元素之间的集合,则应将表示步骤函数中当前子集的int
更改为long
,但性能会明显显着降低设定的大小增加。如果要将其用于具有奇数个元素的集合,则应该在集合中添加0以使其成为偶数。
import java.util.ArrayList;
import java.util.List;
public class SubsetSumMiddleAttack {
static final int target = 100000000;
static final int[] set = new int[]{ ... };
static List<Subset> evens = new ArrayList<>();
static List<Subset> odds = new ArrayList<>();
static int[][] split(int[] superSet) {
int[][] ret = new int[2][superSet.length / 2];
for (int i = 0; i < superSet.length; i++) ret[i % 2][i / 2] = superSet[i];
return ret;
}
static void step(int[] superSet, List<Subset> accumulator, int subset, int sum, int counter) {
accumulator.add(new Subset(subset, sum));
if (counter != superSet.length) {
step(superSet, accumulator, subset + (1 << counter), sum + superSet[counter], counter + 1);
step(superSet, accumulator, subset, sum, counter + 1);
}
}
static void printSubset(Subset e, Subset o) {
String ret = "";
for (int i = 0; i < 32; i++) {
if (i % 2 == 0) {
if ((1 & (e.subset >> (i / 2))) == 1) ret += " + " + set[i];
}
else {
if ((1 & (o.subset >> (i / 2))) == 1) ret += " + " + set[i];
}
}
if (ret.startsWith(" ")) ret = ret.substring(3) + " = " + (e.sum + o.sum);
System.out.println(ret);
}
public static void main(String[] args) {
int[][] superSets = split(set);
step(superSets[0], evens, 0,0,0);
step(superSets[1], odds, 0,0,0);
for (Subset e : evens) {
for (Subset o : odds) {
if (e.sum + o.sum == target) printSubset(e, o);
}
}
}
}
class Subset {
int subset;
int sum;
Subset(int subset, int sum) {
this.subset = subset;
this.sum = sum;
}
}