当前位置: 首页 > 知识库问答 >
问题:

给定一组n个整数,返回k个元素总和为0的所有子集

龙兴学
2023-03-14

给定一组未排序的整数,返回大小为k的所有子集(即每组有k个唯一元素),其总和为0。

所以我给了面试官以下解决方案(我在GeekViewpoint上研究过)。没有使用额外的空间,一切都做到位,等等。但当然成本是O(n^k)的高时间复杂度,其中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)

但随后她提出了以下要求:

  • 必须在答案中使用hashmap以降低时间复杂度
  • 必须绝对地为一般情况提供时间复杂度
  • k=6时的提示,O(n^3)

她对时间复杂性比其他任何东西都更感兴趣。

有人知道满足新约束的解决方案吗?

编辑:

假设,在正确的解决方案中,映射将存储输入的元素,然后映射将用作查找表,就像k=2的情况一样。

当子集的大小为2(即k=2)时,答案很简单:遍历所有元素并将其加载到映射中。然后再次循环输入,这次在映射中搜索求和输入[i],其中i是从0到n-1的索引,然后就是答案。假设这个微不足道的情况可以扩展到k是任何东西的地方。

共有3个答案

刘选
2023-03-14

@卡萨夫贝雷-

最近,一位朋友在Google接受了一份C编程工作的一整天的痛苦面试。他的经历与你相似。

这启发了他写这篇文章——我想你可能会喜欢:

实用主义辩护

白云
2023-03-14

我认为你的答案非常接近他们正在寻找的内容,但是你可以通过注意sizek的任何子集都可以被认为是sizek/2的两个子集来提高复杂性。因此,不是找到sizek的所有子集(假设k很小,需要O(n^k)),而是使用您的代码找到sizek/2的所有子集,并将每个子集放入哈希表中,以其总和为键。

然后用正和(称之为和)遍历大小为k/2的每个子集,并检查哈希表中求和为-S的子集。如果有一个,那么大小k/2的两个子集的组合就是大小k的一个子集,其和为零。

因此,在他们给出的k=6的情况下,你会找到大小为3的所有子集,并计算它们的和(这将花费时间)。然后,检查哈希表将花费每个子集的时间,因此总时间是0(n^3)。一般情况下,假设k很小,这种方法将采用O(n^(k/2)),您可以通过取大小floor(k/2)floor(k/2)1的子集,对k的奇数值进行推广。

林元明
2023-03-14

既然没有其他人尝试过,我不妨至少提出一个部分解决方案。正如我在前面的评论中指出的,这个问题是子集和问题的一个变体,在开发这个解决方案时,我严重依赖于有文档记录的方法来解决这个问题。

我们正在尝试编写一个函数subsetsSusSum(A, k, s),它计算A的所有k长度子集,总和为s。这个问题以两种方式适合递归解决方案:

  1. 通过计算subsetwithsum(x2…xn,k,s)并添加包含x1的所有有效子集(如果有),可以找到subsetwithsum(x1,k,s)的解;和

递归的基本情况发生在k为1时,在这种情况下,subsetsSusSum(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;
}

现在,请注意,这个解决方案通常会使用与之前调用的相同的一组参数调用subsetsWisSum(...)。因此,subsetsWisSum只是乞求被备忘录化。

为了记忆该函数,我将参数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;
}

要使用subsetsSusSum函数计算所有总和为零的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,但它是一个指数算法,列出所有子集需要O(2^n)。我能用动态规划在多项式时间内求解吗?

  • 问题内容: 我想编写一个函数,该函数以字母数组作为参数,并选择多个字母。 假设您提供8个字母的数组,并希望从中选择3个字母。然后您将获得: 返回由3个字母组成的数组(或单词)。 问题答案: 格雷码您会遇到的一个问题当然是记忆力,而且很快,您的集合中会有20个元素出现问题-20 C 3 =1140。而且,如果要遍历集合,最好使用修改后的灰色代码算法,因此您不必将所有代码都保存在内存中。这些将根据之前

  • 问题陈述 任务是检查在长度为N的数组中是否存在K个元素的非连续子数组,其总和等于给定的总和。 例如, 长度为 3 且 sum=7 的非连续子数组为 [1,2,4]。 限制条件: 输出 如果存在 sum=TargetSum 的子数组,我们必须返回 True,如果不可能,则必须返回 False。

  • 我知道这是一个背包问题,其中权重和值相等,但我认为我在编码逻辑上犯了一个错误,因为即使对于数组中元素的数量(N)为50并且所需的最大总和(M)4500。 为了澄清这个问题,我们得到了一个由N个正整数和一个正整数M组成的数组。数组元素只能使用一次。我们必须找到这个数组的子集(不一定是连续的),使得总和最接近M,但不超过它。 这是我使用动态编程的尝试: 那么在我的代码中是否有任何可能的优化可以帮助我降

  • 问题内容: 在我的工作中,我们有一个使用AngularJS创建的一页站点。 我们正在使用ui-router插件(版本0.2.0)。 最近,我注意到从一种状态切换到另一种状态时,该窗口不会滚动到顶部。 我什至尝试在每次状态更改(使用事件)时使用jQuery的功能将其手动滚动到顶部。但这没有用。 因此,我开始进行调查,并且注意到页面上的每个元素都返回0。 不仅如此,当我将其打印到控制台时,我得到0(无