[英]given a set of n integers, return all subsets of k elements that sum to 0
给定一组未排序的n
整数,返回大小为k的所有子集(即每个集合具有k个唯一元素),其总和为0。
所以我给了面试官以下解决方案(我在GeekViewpoint上学习过 )。 没有使用额外的空间,一切都在适当的地方完成等等。但是当然成本是O(n ^ k)的高时间复杂度,其中k=tuple
解决方案中的k=tuple
。
public void zeroSumTripplets(int[] A, int tuple, int sum) {
int[] index = new int[tuple];
for (int i = 0; i < tuple; i++)
index[i] = i;
int total = combinationSize(A.length, tuple);
for (int i = 0; i < total; i++) {
if (0 != i)
nextCombination(index, A.length, tuple);
printMatch(A, Arrays.copyOf(index, tuple), sum);
}// for
}// zeroSumTripplets(int[], int, int)
private void printMatch(int[] A, int[] ndx, int sum) {
int calc = 0;
for (int i = 0; i < ndx.length; i++)
calc += A[ndx[i]];
if (calc == sum) {
Integer[] t = new Integer[ndx.length];
for (int i = 0; i < ndx.length; i++)
t[i] = A[ndx[i]];
System.out.println(Arrays.toString(t));
}// if
}// printMatch(int[], int[], int)
但后来她强加了以下要求:
她对时间复杂性的兴趣比什么都重要。
有谁知道满足新约束的解决方案?
编辑:
据推测,在正确的解决方案中,地图将存储输入的元素,然后地图将用作查找表,就像k=2
。
当子集的大小为2(即k=2
)时,答案是微不足道的:循环并将所有元素加载到地图中。 然后再次遍历输入,这次搜索地图的sum - input[i] where i is the index from 0 to n-1
,然后是答案。 据说这个简单的案例可以扩展到k
是什么的地方。
由于没有其他人做过尝试,我不妨投入至少部分解决方案。 正如我在之前的评论中指出的那样,这个问题是子集求和问题的变体,我在开发这个解决方案时严重依赖于记录的方法来解决这个问题。
我们正在尝试编写一个函数subsetsWithSum(A, k, s)
来计算总和为s的所有k长度的A子集。 这个问题有两个方面的递归解决方案:
当k为1时发生递归的基本情况,在这种情况下,subsetsWithSum(A,1,s)的解是所有单个元素子集的集合,其中该元素等于s。
因此,首先要解决一个问题
/**
* Return all k-length subsets of A starting at offset o that sum to s.
* @param A - an unordered list of integers.
* @param k - the length of the subsets to find.
* @param s - the sum of the subsets to find.
* @param o - the offset in A at which to search.
* @return A list of k-length subsets of A that sum to s.
*/
public static List<List<Integer>> subsetsWithSum(
List<Integer> A,
int k,
int s,
int o)
{
List<List<Integer>> results = new LinkedList<List<Integer>>();
if (k == 1)
{
if (A.get(o) == s)
results.add(Arrays.asList(o));
}
else
{
for (List<Integer> sub : subsetsWithSum(A, k-1, s-A.get(o), o+1))
{
List<Integer> newSub = new LinkedList<Integer>(sub);
newSub.add(0, o);
results.add(0, newSub);
}
}
if (o < A.size() - k)
results.addAll(subsetsWithSum(A, k, s, o+1));
return results;
}
现在,请注意,此解决方案通常会使用之前调用过的相同参数集调用subsetsWithSum(...)。 因此,subsetsWithSum只是乞求备忘 。
为了记住这个函数,我把参数k,s和o放到一个三元素列表中,它将是从这些参数到先前计算的结果(如果有的话)的映射的关键:
public static List<List<Integer>> subsetsWithSum(
List<Integer> A,
List<Integer> args,
Map<List<Integer>, List<List<Integer>>> cache)
{
if (cache.containsKey(args))
return cache.get(args);
int k = args.get(0), s = args.get(1), o = args.get(2);
List<List<Integer>> results = new LinkedList<List<Integer>>();
if (k == 1)
{
if (A.get(o) == s)
results.add(Arrays.asList(o));
}
else
{
List<Integer> newArgs = Arrays.asList(k-1, s-A.get(o), o+1);
for (List<Integer> sub : subsetsWithSum(A, newArgs, cache))
{
List<Integer> newSub = new LinkedList<Integer>(sub);
newSub.add(0, o);
results.add(0, newSub);
}
}
if (o < A.size() - k)
results.addAll(subsetsWithSum(A, Arrays.asList(k, s, o+1), cache));
cache.put(args, results);
return results;
}
要使用subsetsWithSum函数计算总和为零的所有k长度子集,可以使用以下函数:
public static List<List<Integer>> subsetsWithZeroSum(List<Integer> A, int k)
{
Map<List<Integer>, List<List<Integer>>> cache =
new HashMap<List<Integer>, List<List<Integer>>> ();
return subsetsWithSum(A, Arrays.asList(k, 0, 0), cache);
}
令人遗憾的是,我的复杂性计算技能有点(读:非常)生锈,所以希望其他人可以帮助我们计算这个解决方案的时间复杂度,但它应该是对蛮力方法的改进。
编辑:为了清楚起见,请注意上面的第一个解决方案应该在时间复杂度上与蛮力方法相当。 在许多情况下,记住函数应该有所帮助,但在最坏的情况下,缓存永远不会包含有用的结果,时间复杂度将与第一个解决方案相同。 还要注意,子集和问题是NP完全的,这意味着任何解决方案都具有指数时间复杂度。 结束编辑。
为了完整起见,我测试了以下内容:
public static void main(String[] args) {
List<Integer> data = Arrays.asList(9, 1, -3, -7, 5, -11);
for (List<Integer> sub : subsetsWithZeroSum(data, 4))
{
for (int i : sub)
{
System.out.print(data.get(i));
System.out.print(" ");
}
System.out.println();
}
}
并打印:
9 -3 5 -11
9 1 -3 -7
我认为你的答案非常接近他们所寻找的,但你可以通过注意到任何大小为k
子集可以被认为是两个大小为k/2
子集来提高复杂性。 因此,不是找到大小为k
所有子集(假设k
很小,取O(n^k)
),使用您的代码查找大小为k/2
所有子集,并将每个子集放在一个哈希表中,其总和为键。
然后用正和(遍历和S
)迭代大小为k/2
每个子集,并检查散列表中的和为-S
的子集。 如果有一个大小然后的两个子集的组合k/2
是大小的子集k
其总和是零。
因此,在他们给出的k=6
的情况下,你会发现大小为3
所有子集并计算它们的总和(这将花费O(n^3)
时间)。 然后检查哈希表将为每个子集花费O(1)
时间,因此总时间为O(n^3)
。 一般来说,这种方法将采用O(n^(k/2))
假设k
很小,并且你可以通过取大小floor(k/2)
和floor(k/2)+1
子集来推广它的奇数值k
floor(k/2)+1
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.