从数字中减去数字的数字,直到达到0

时间:2014-12-29 22:23:41

标签: algorithm bignum

任何人都可以帮我解决这个问题吗?

我们有一个很大的数字(19位),在一个循环中,我们从数字本身中减去该数字的一个数字。

我们继续这样做,直到数字达到零。我们想要计算使给定数字达到零的最小减法数。

算法必须在两秒内快速响应19位数字(10 ^ 19)。例如,提供36的输入将提供7

1. 36 - 6 = 30
2. 30 - 3 = 27
3. 27 - 7 = 20
4. 20 - 2 = 18
5. 18 - 8 = 10
6. 10 - 1 =  9
7.  9 - 9 =  0

谢谢。

3 个答案:

答案 0 :(得分:3)

最小次数达到零的减法数量使我怀疑这是一个非常棘手的问题,需要大量的回溯潜在解决方案,这可能会让您的时间限制太贵

首先你应该做的是健全检查。由于最大数字是9,因此19位数字将需要大约1018个减数才能达到零。对一个简单的程序进行编码,以便从1019连续减去9,直到它变为小于10。如果你在两秒钟内无法,那你就麻烦了。

举例来说,以下程序(a)

#include <stdio.h>

int main (int argc, char *argv[]) {
    unsigned long long x = strtoull(argv[1], NULL, 10);
    x /= 1000000000;
    while (x > 9)
        x -= 9;
    return x;
}

使用参数100000000000000000001019)运行时,即使在gcc疯狂的情况下,也需要一个二十五个时钟时间(以及自所有计算以来的CPU时间)优化级别为-O3

real    0m1.531s
user    0m1.528s
sys     0m0.000s

那就是在while循环之前的十亿除数,这意味着完整的迭代次数大约需要48年。

所以蛮力方法在这里没有帮助,你需要的是一些严肃的数学分析,这可能意味着你应该在https://math.stackexchange.com/发布一个类似的问题让数学天才有一个机会


(a)如果您想知道为什么我从用户那里获得价值而不是使用10000000000000000000ULL的常数,那么它就是阻止gcc在编译时计算它并将其转换为:

mov  $1, %eax

同样适用于return x,这会阻止它注意到我不会使用 x的最终值,因此完全无法优化循环。< / p>

答案 1 :(得分:1)

正如已经在评论中所说,并且同意@ paxdiablo的另一个答案,我不确定是否有一种算法可以在没有回溯的情况下找到理想的解决方案;并且数量和时间限制的大小也可能很难。

一般的考虑因素:您可能想要找到一种方法来决定总是减去最高位数(这会显着减少当前数量,显然),并查看当前数字并减去当前数字那些将给你最大的“新”数字。

说,您当前的号码只包含05之间的数字 - 然后您可能会想要减去5以减少最高可能值,并继续下一步。如果当前号码的最后一位数字为3,那么您可能希望减去4 - 因为这会在号码末尾为您提供9作为新数字,而是如果你减去8,你将获得“5

然而,如果您的数字中已经有2两个 9,而最后一位数字是1 - 那么您可能需要减去无论如何9,因为你将在结果中留下第二个9(至少在大多数情况下;在某些边缘情况下,它也可能会从结果中删除),因此减去{ {1}}相反,它没有给你一个“高”2的优势,否则你将不会在下一步中拥有它,并且有一个缺点,就是不要将数量减少到减去的数量。 9会...

但是你减去的每一个数字不仅会直接影响下一步,而且间接地影响下面的步骤 - 所以我再次怀疑有没有办法在没有任何回溯或类似措施的情况下为当前步骤选择理想数字。< / p>

答案 2 :(得分:0)

我没有可以在2秒内解决19位数字的解决方案。差远了。但我确实实现了几种算法(包括一种能够解决最佳问题的动态编程算法),并获得了一些我认为很有趣的见解。

贪婪算法

作为基线,我实施了一个贪心算法,只需在每一步中选取最大数字:

uint64_t countGreedy(uint64_t inputVal) {
    uint64_t remVal = inputVal;
    uint64_t nStep = 0;
    while (remVal > 0) {
        uint64_t digitVal = remVal;
        uint_fast8_t maxDigit = 0;
        while (digitVal > 0) {
            uint64_t nextDigitVal = digitVal / 10;
            uint_fast8_t digit = digitVal - nextDigitVal * 10;
            if (digit > maxDigit) {
                maxDigit = digit;
            }
            digitVal = nextDigitVal;
        }
        remVal -= maxDigit;
        ++nStep;
    }
    return nStep;
}

动态编程算法

这个想法是我们可以逐步计算最佳值。对于给定的值,我们选择一个数字,该数字为减去数字的值的最佳步数增加一步。

对于名为optSteps(val)的给定值的目标函数(最佳步骤数),以及名为d_i的值的数字,以下关系成立:

optSteps(val) = 1 + min(optSteps(val - d_i))

这可以使用动态编程算法实现。由于d_i最多为9,因此我们只需要建立之前的9个值。在我的实现中,我保留了10个值的循环缓冲区:

static uint64_t countDynamic(uint64_t inputVal) {
    uint64_t minSteps[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
    uint_fast8_t digit0 = 0;
    for (uint64_t val = 10; val <= inputVal; ++val) {
        digit0 = val % 10;
        uint64_t digitVal = val;
        uint64_t minPrevStep = 0;
        bool prevStepSet = false;
        while (digitVal > 0) {
            uint64_t nextDigitVal = digitVal / 10;
            uint_fast8_t digit = digitVal - nextDigitVal * 10;
            if (digit > 0) {
                uint64_t prevStep = 0;
                if (digit > digit0) {
                    prevStep = minSteps[10 + digit0 - digit];
                } else {
                    prevStep = minSteps[digit0 - digit];
                }
                if (!prevStepSet || prevStep < minPrevStep) {
                    minPrevStep = prevStep;
                    prevStepSet = true;
                }
            }
            digitVal = nextDigitVal;
        }
        minSteps[digit0] = minPrevStep + 1;
    }
    return minSteps[digit0];
}

结果比较

这可能被认为是一个惊喜:我在所有值上运行了两种算法,最高可达1,000,000。结果完全相同。这表明贪婪算法实际上计算了最优值。

我没有正式的证据证明这对所有可能的值都是如此。它直观地对我有意义。如果在任何给定的步骤中,您选择的数字小于最大值,则会影响即时进度,目标是进入更有利的情况,使您能够赶上并通过贪婪的方法。但在我想到的所有场景中,采取次优步骤之后的情况并没有明显更有利。它可能会使下一步更大,但这至多足以让它再次成为现实。

复杂性

虽然两种算法在值的大小上看起来都是线性的,但它们也会遍历值中的所有数字。由于位数对应log(n),我相信复杂度为O(n * log(n))。

我认为可以通过保持每个数字的频率计数并逐步修改它来使其成为线性的。但我怀疑它实际上会更快。它需要更多的逻辑,并将值的所有数字(我们正在查看的值在2-19范围内)的循环转换为10个可能数字的固定循环。

运行时

毫不奇怪,贪心算法计算单个值的速度更快。例如,对于值1,000,000,000,我的MacBook Pro上的运行时间为:

  • 贪婪:3秒
  • 动态:36秒

另一方面,动态编程方法在计算所有值时显然要快得多,因为它的增量方法无论如何都需要它们作为中间结果。用于计算10到1,000,000之间的所有值:

  • 贪婪:19分钟
  • 动态:0.03秒

正如上面的运行时所示,贪婪算法在2秒的目标运行时间内获得高达9位数的输入值。这些实现并没有真正调整,并且肯定有可能挤出更多的时间,但这将是部分改进。

正如在另一个答案中已经探讨的那样,通过逐个减去数字,没有机会在2秒内获得19位数字的结果。由于我们在每个步骤中减去最多9个,因此以10 ^ 19的值完成此操作需要超过10 ^ 18个步骤。我们大多使用的计算机在10 ^ 9次操作/秒的粗略范围内执行,这表明它需要大约10 ^ 9秒。

因此,我们需要一些可以采取捷径的东西。我可以想到可能的情况,但到目前为止还未能将其概括为完整的策略。

例如,如果你的当前值是9999,你知道你可以减去9直到你达到9000.所以你可以计算你将做出112步((9999 - 9000)/ 9 + 1)你减去9 ,可以在几个操作中完成。