【问题标题】:Optimize permutations优化排列
【发布时间】:2022-12-16 07:40:12
【问题描述】:

排列优化问题有很多,但每一个都不一样。

在最近的一次编码作业中,有人要求我给出一个数字列表,找出有多少对加起来是 60 的倍数。

我想出的答案是这样的:

public int getPairs(List<Integer> nums){
  int result = 0;
  for(int i=0; i<nums.size(); i++){
   for(int j=i; j<nums.size(); j++) {
     if((nums.get(i)+nums.get(j))%60 == 0){
       result++
     }
   }
  }
  return result; 
}

这段代码是正确的,但是测试软件由于“超时”而未能通过一些隐藏的测试用例,其中 nums 列表为 10000,这意味着它花费的时间太长。

我试过先将 List 转换为数组,以节省 size()get() 方法调用,但这对我没有帮助。

我很困扰。这不是遍历所有可能组合的最快方法吗?

如果问题不是问 60 的倍数,而是 60,那么我会先对数组进行排序,一旦总和大于 60,就跳过数组的其余部分,但事实并非如此。

此外,奇怪的是 10000 大小的数组应该超时。 10,000 x 10,000 是 100,000,000。当然,在现代处理器上执行两次加法、除法、比较和 compareToZero 100,000,000 应该不到一秒。

是我做错了什么,还是测试软件有问题?

【问题讨论】:

  • 您想到了询问“它们加起来等于 60 吗”的优化。用它的值 mod 60 替换数组中的每个条目是一个单一的非嵌套循环。因此,如果优化足够有用,那么执行该预处理步骤将改善整体运行时间。一般来说,这类优化问题不是“我如何以最快的速度执行蛮力尝试”的练习,而是“我如何避免做大部分工作”的练习。
  • 另一种可能的优化:只有 60 个可能的有趣值。在一个循环中,您可以计算有多少个值是 0 mod 60、1 mod 60、2 mod 60,... 59 mod 60。然后在另一个简单的循环中,您可以计算该固定大小数组的结果。
  • 我实际上也尝试了第一步,但对我没有帮助。第二个很有趣。

标签: java algorithm optimization big-o combinations


【解决方案1】:

错误解释

这段代码是正确的,但是测试软件由于“超时”而未能通过一些隐藏的测试用例,其中 nums 列表为 10000,这意味着它花费的时间太长。

我不会说代码是正确的(因为它有一个缺陷),而是你没有机会看到它在一个小的输入上失败了。

在内部for循环中,您将索引j的起始值初始化为i-int j = i。这意味着在内部循环的第一次迭代中,您要将一个元素添加到本身: nums.get(i) + nums.get(j) 然后检查它是否可以被 60 整除。

这可能会导致无效结果。内循环应以j = i + 1 开头。通过此修复,您将获得正确的暴力解决方案。

第二题——术语.

您的代码计算的是不是Permutations,Combinations成对的元素(这不是学术上的散布,结果会有所不同)。

举个简单的例子,假设输入是 [1,2,3],我们需要可被 3 整除的元素对的组合。只有一种组合 [1,2],但有两种排列:[1,2][2,1]

因为在您的蛮力解决方案中,i &lt; j 始终成立,所以它无法生成排列(因为它不会在内部循环中重新访问相同的索引,所以它只能考虑[i,j],而不是[j,i]),并且它产生组合的数量(因此问题的标题不正确,考虑修复它)。

现在当问题陈述被澄清时:“找出可被给定数字整除的组合对的数量”,让我们开始实际的解决方案。

解决方案

作为@约阿希姆绍尔在 cmets 中指出,解决此问题的第一步是创建一个数组元素频率数组 % 60(就像在计数排序算法的第一阶段)。

那么我们有两种情况:

  • 当考虑在[1,59]范围内除以60的余数时,我们需要计算Cartesian product的频率和相应的余数并将其加到总数中。 IE。 frequency(1)xfrequency(59)frequency(2)xfrequency(58)、...一直到frequency(29)xfrequency(31)笔记我们不应该接触frequency(0)frequency(30)(它们需要分开处理),我们不应该重复计算产品,即我们不应该考虑像frequency(59)xfrequency(1)这样的反向组合。

  • 组合 frequency(0)frequency(30) 是一种特殊情况,因为我们只能将它们组合在一起。对于这两种情况,我们都需要根据以下公式计算二项式系数:

其中n 是频率数(030),k 是组合中的元素数,始终等于2

这就是实现的样子(为了简化测试除数 60 不是硬编码的,而是作为方法参数提供的):

public static long getPairCount(List<Integer> list, int divisor) {
    int[] freq = new int[divisor]; // frequencies of modulus of list elements
    for (int next : list) freq[next % divisor]++;
    
    long count = 0;
    for (int left = 1, right = divisor - 1; left < divisor / 2 + divisor % 2; left++, right--) {
        count += ((long) freq[left]) * freq[right]; // a cartesian product gives a number of ways to form a pair
    }
    // Special cases: remainder 0 and remainder divisor / 2 - now with dialing with pure Combinations
    // and instead Cartesian product a Binomial coefficient needs to be calculated
    if (freq[0] > 1) count += factorial(freq[0]) / 2 * factorial(freq[0] - 2);
    if (divisor % 2 == 0 && freq[divisor / 2] > 1) count += factorial(freq[0]) / 2 * factorial(freq[0] - 2); // should be only considered if divisor is
    return count;
}

public static long factorial(int num) {
    long result = 1;
    for (int i = 1; i <= num; i++) result *= i;
    return result;
}

main()

public static void main(String[] args) {
    System.out.println(getPairCount(List.of(1, 2, 3), 3)); // [1, 2]
    System.out.println(getPairCount(List.of(1, 2, 3, 1, 2, 3, 4, 5), 7)); // [2, 5] x 2, [3, 4] x 2
    System.out.println(getPairCount(List.of(1, 2, 3, 1, 2, 3, 4, 5), 5)); // [2, 3] x 4, [1, 4] x 2
    System.out.println(getPairCount(List.of(1, 2, 3, 1, 2, 3, 4, 5, 5), 5)); // [2, 3] x 4, [1, 4] x 2, [5, 5]
}

输出:

1   // [1, 2]
4   // [2, 5] x 2, [3, 4] x 2
6   // [2, 3] x 4, [1, 4] x 2
7   // [2, 3] x 4, [1, 4] x 2, [5, 5]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 2013-07-27
    • 2021-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-06-09
    相关资源
    最近更新 更多