【问题标题】:Order (a,b) pairs by result of a*b按 a*b 的结果对 (a,b) 对进行排序
【发布时间】:2014-08-07 04:58:10
【问题描述】:

我想找到满足某个条件 C(m) 的最大值 m = a*b,其中

1 <= a <= b <= 1,000,000.

为了做到这一点,我想以 a*b 的降序迭代所有 a,b 对。

例如,对于最大为 5 的值,顺序为:

5 x 5 = 25
4 x 5 = 20
4 x 4 = 16
3 x 5 = 15
3 x 4 = 12
2 x 5 = 10
3 x 3 = 9
2 x 4 = 8
2 x 3 = 6
1 x 5 = 5
1 x 4 = 4
2 x 2 = 4
1 x 3 = 3
1 x 2 = 2
1 x 1 = 1

到目前为止,我已经提出了一个类似 BFS 的树搜索,我从当前的“访问过”集合中生成候选者并选择最高值的候选者,但它是一团糟,我不确定正确性.我想知道我是否缺少某种技巧。

我也对任何单调函数 f(a,b) 排序的更一般情况感兴趣,如果存在这样的事情的话。

为了说明,C(m) 可能是“如果 m2+m+41 是素数则返回 true,否则返回 false”,但我真的在寻找一种通用的方法。

【问题讨论】:

  • 条件C(m)是什么? (为什么你的列表中漏掉了 7 的倍数)?
  • @user1990169 因为 7 大于 5,这不是“最多 5”吗?只是猜测。
  • 我不明白。你说属性 C 只取决于数字 m (而不是它是如何分解的)。然后你应该搜索可能的 m 测试 C。搜索因式分解是浪费的,更不用说复杂了。您断言 m 可以写成小数的乘积只是另一个要测试的条件 D。
  • @ColonelPanic 我非常同意。这就是nmore答案背后的想法。我希望有一个数学技巧可以在不使用堆的情况下解决 ab 的特定情况。无论如何,如果条件 D 足够稀疏,使用堆可能仍然更快,例如如果我使用 a^3*b^3 而不是 ab.

标签: algorithm sorting


【解决方案1】:

如果C(m) 如此神奇以至于您无法使用任何更好的技术直接找到您的解决方案,因此您确实需要按降序遍历所有a*b,这就是我要做的:

使用所有对 (a, b) 初始化一个最大堆,使得 a = b。这意味着堆包含(0, 0), (1, 1), ... , (1.000.000, 1.000.000)。堆应该基于a * b 值。

现在连续:

  1. 从堆中获取最大对 (a, b)
  2. 验证(a, b) 是否满足C(a * b)。如果是这样,你就完成了。
  3. 否则,将(a, b-1) 添加到堆中(提供b &gt; 0,否则什么都不做)。

这是一个非常简单的O(n log n) 时间和O(n) 空间算法,前提是您可以快速找到答案(在几次迭代中)。这当然取决于C


如果您遇到空间问题,您当然可以通过将问题拆分为多个子问题来轻松降低空间复杂度,例如 2:

  1. 仅将 (500.000, 500.000), (500.001, 500.001), ... , (1.000.000, 1.000.000) 添加到堆中并找到最佳配对 (a, b)
  2. (0, 0), (1, 1), ... (499.999, 499.999) 执行相同操作。
  3. 充分利用这两种解决方案。

【讨论】:

  • 第一部分的时间复杂度是多少O(n logn)?有 n 对,它们中的每一个都从 'x' 减少到 0,所以这将是 'n' + 'n - 1' + ... + '0' 这是 O(n^2) 倍堆操作O(log(n^2)),所以我觉得应该是O(n^2 log(n^2))。
  • @PhamTrung 我的意思是数据结构的开销是O(n log n)。当然,在最坏的情况下,您需要遍历所有 O(n^2) 对,然后才能找到 C 持有的一对。数据结构的开销是每对O(log n),因为堆的大小只有O(n)。不过O(log n^2) = O(2 log n) = O(log n),所以你的分析也是正确的。
  • 顺便说一句,看起来这个解决方案确实不仅适用于 a*b,而且适用于 f(a-ε,b)≤f(a, b) 和 f(a,b-ε)≤f(a,b)。
  • @itsadok 或者,如果您对所有b 初始化(1.000.000, b),您甚至可以拥有任何f,例如f(a-1, b)≤f(a, b)。另外,不需要f(a, b) = f(b, a)
【解决方案2】:

这是在 Python 中使用堆执行此操作的一种不是特别有效的方法。这可能与您提到的 BFS 相同,但它相当干净。 (如果有人想出一个直接的算法,那当然会更好。)

import heapq  # <-this module's API is gross. why no PriorityQueue class?

def pairs_by_reverse_prod(n):
    # put n things in heap, since of course i*j > i*(j-1); only do i <= j
    # first entry is negative of product, since this is a min heap
    to_do = [(-i * n, i, n) for i in xrange(1, n+1)]
    heapq.heapify(to_do)

    while to_do:
        # first elt of heap has the highest product
        _, i, j = to_do[0]
        yield i, j

        # remove it from the heap, replacing if we want to replace
        if j > i:
            heapq.heapreplace(to_do, (-i * (j-1), i, j-1))
        else:
            heapq.heappop(to_do)

【讨论】:

  • @Heuster 击败了我,获得了基本相同的解决方案(有一段时间,因为我在写这篇文章时分心了),但由于这里有代码,我想我会留下它。
【解决方案3】:

以下代码将生成(并打印):

[(5, 5), (4, 5), (4, 4), (3, 5), (3, 4), (2, 5), (3, 3), (2, 4), (2, 3), (1, 5), (1, 4), (2, 2), (1, 3), (1, 2), (1, 1)]

这基本上是您想要的,因为如果您的条件满足,代码可能会提前中断。我认为这个问题的重点不是生成(a, b) 的所有可能组合。

算法的关键在于,在每次迭代中,我们需要考虑(a - 1, b)(a, b - 1)。如果a == b,但是,由于a &lt;= b,我们只需要考虑(a - 1, b)。剩下的就是维护元组队列中的顺序,Q,基于他们的产品,m

在效率方面,当插入Q时,代码从索引0进行线性搜索。对于较大的ab 值,执行二分搜索而不是这种线性搜索可能会或可能不会使事情变得更快。

另外为了进一步优化代码,我们可以将m(a, b)一起存储在Q中,这样我们就不用多次计算a * b了。同样使用以m为关键的一维桶结构来实现Q会很有趣。

#!/usr/bin/python

def insert_into_Q((a, b), Q):

    if (a == 0) or (b == 0):
        return

    pos = 0
    for (x, y) in Q:
        if (x == a) and (y == b):
            return
        if x * y < a * b:
            break
        pos = pos + 1
    Q.insert(pos, (a, b))


def main(a, b):

    Q = [(a, b)]
    L = []

    while True:

        if len(Q) == 0:
            break

        (a, b) = Q.pop(0)
        L.append((a, b)) # Replace this with C(a * b) and break if satisfied.

        a1 = a - 1
        b1 = b - 1

        if (a == b):
            insert_into_Q((a1, b), Q)
        else:
            insert_into_Q((a1, b), Q)
            insert_into_Q((a, b1), Q)

    print(L)


if __name__ == "__main__":
    main(5, 5)

【讨论】:

    【解决方案4】:

    注意:这是对函数 C(m) 的测试,其中 m

    首先找到满足 C 的最大数,然后找到与该高数匹配的对。查找初始目标编号几乎不需要时间,因为它是从 1 到 1E12 的二进制搜索。找到匹配的对有点困难,但仍然没有因式分解那么糟糕。

    代码:

    public class TargetPractice {
    
        private static final long MAX = 1000000L;
    
        private long target;
    
        public static void main(String[] args) {
            Random r = new Random();
            for (int i = 0; i < 5; i++) {
                TargetPractice tp = new TargetPractice(r.nextInt((int) MAX), r.nextInt((int) MAX));
                System.out.println("Trying to find " + tp.target);
                System.gc();
                long start = System.currentTimeMillis();
                long foundTarget = tp.findTarget();
                long end = System.currentTimeMillis();
                System.out.println("Found " + foundTarget);
                System.out.println("Elapsed time " + (end - start) + "\n");
            }
        }
    
        public TargetPractice(long a, long b) {
            target = a * b + 1;
        }
    
        private long binSearch() {
            double delta = MAX * MAX / 2;
            double target = delta;
    
            while (delta != 0) {
                if (hit((long) target)) {
                    target = target + delta / 2;
                } else {
                    target = target - delta / 2;
                }
                delta = delta / 2;
            }
    
            long longTarget = (long) target;
            for (int i = 10; i >= -10; i--) {
                if (hit(longTarget + i)) {
                    return longTarget + i;
                }
            }
            return -1;
        }
    
        private long findTarget() {
            long target = binSearch();
            long b = MAX;
            while (target / b * b != target || target / b > MAX) {
                b--;
                if (b == 0 || target / b > MAX) {
                    b = MAX;
                    target--;
                }
            }
            System.out.println("Found the pair " + (target/b) + ", " + b);
            return target;
        }
    
        public boolean hit(long n) { 
            return n <= target;
        }
    }
    

    打印出来:

    试图找到 210990777760
    找到了对 255976, 824260
    已找到 210990777760
    已用时间 5

    正在寻找 414698196925
    找到这对 428076, 968749
    找到 414698196924
    经过时间 27

    试图找到 75280777586
    找到了 78673, 956882
    找到了 75280777586
    经过时间1

    试图找到 75327435877
    找到了 82236, 915991
    找到了 75327435876
    已用时间 19

    试图找到 187413015763
    找到了对 243306, 770277
    找到187413015762
    经过时间23

    【讨论】:

    • 这很简洁,但请注意,找到满足 C 的最大数字仍然需要一万亿次尝试。并且不能保证我们找到的数字是合法的——它可能是质数或具有高于 1M 的因数,因此我们必须继续搜索。我同意在实践中 f(a,b)=a*b 这可能更快。
    • 好的,通过小检查更新了代码。现在这个解决方案将永远不会进行一万亿次尝试。此外,如果有一个,它会找到一个合法的数字。
    • 抱歉,我并没有真正了解你在那里所做的事情。我的观点是,C(m) 可以是任何东西,因此它可能对任何超过 2 的数字返回“假”,因此您必须在找到目标之前检查从 1 万亿到 2 的所有数字。您不能对此使用二分搜索。
    • 啊,我明白你在说什么,我认为 C 是连续的.... 将编辑帖子。
    猜你喜欢
    • 2015-03-05
    • 2013-08-27
    • 1970-01-01
    • 1970-01-01
    • 2020-07-10
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    • 2020-04-13
    相关资源
    最近更新 更多