【问题标题】:Find the sum of digits of n+1,n+2.... when sum of digits of n is given求 n+1,n+2.... 的数字之和,当 n 的数字之和给出时
【发布时间】:2012-09-11 06:53:58
【问题描述】:

我们可以很容易地计算给定数字的位数之和,但是是否有任何数学公式或模式可以用来确定下一个数字的总和,而不必一次又一次地对所有数字求和?

例如

Sum of 1234 = 1+2+3+4 = 10
Sum of 1235 = 1+2+3+5 = 11
Sum of 1236 = 1+2+3+6 = 12

我可以在这里看到某种模式,但无法提出任何有效的数学算法。

我使用下面的方法来计算数字的总和:

public int sum(long n) {  
    int sum = 0;   
    while (n != 0) {    
        sum += n % 10;
        n /= 10;  
    }  
    return sum;  
}

这工作正常,但它是 CPU 密集型的。我想更快地做到这一点。如果我有一个数字序列,比如10->19,我应该只需要数出 10 的数字,然后为每个数字加一个,直到 19。

如果我已经有了以前数字的总和,有没有什么有效的方法来计算数字的总和?

【问题讨论】:

  • 也许这是Mathematics问答的问题。
  • 这不是家庭作业。我在处理另一个问题时遇到了这个问题。@Sujay 我已经用我目前使用的一些方法更新了我的问题。 @mathematician1975 抱歉对问题的描述不好,我认为这是直截了当的问题。我已经更新了这个问题,并提供了更多详细信息来解释它。

标签: java algorithm math sum digits


【解决方案1】:
DigitSum(n+1) = DigitSum(n) + 1 - (9 * NumberOfEndingZeros(n+1))

如果你想找到的不是t连续数字的数字和,而是t连续数字的数字和(n+1,n+2,...,n+t),那就更简单了。

Sum(DigitSum(i)) for i = n+1 to n+t = a(n+t) - a(n)

其中a(i) 是来自整数序列百科全书的A037123 序列,它有几个公式。我认为这会很快:

a(n) = (1/2) * ( (n+1) * (n - 18 * sum{k>0, floor(n/10^k)} )
               + 9 * sum{k>0, (1+floor(n/10^k))*floor(n/10^k)*10^k}
               )

【讨论】:

  • 这是我要建议的解决方案。
  • 不错的答案。而且我相信可以在O(loglogn) 最坏的情况下找到结束零的数量,使用二进制搜索找到第一个非零数字
  • 虽然线性搜索也是 O(loglogn) average 的情况,但我相信。
  • 感谢大家的回复。@ypercube、@Peter 和 @amit 你们真的很棒!
【解决方案2】:

最快的方法是提取每个数字并随时添加。

public static void main(String... args) {
    // check values
    int runs = 1000000;
    for (int i = 100; i < runs; i++) {
        int sum = sumDigits(i - 1);
        int sum1 = sumDigits(i);
        int sum2 = sumDigits(sum, i);
        if (sum1 != sum2) throw new AssertionError(i + ": " + sum1 + " != " + sum2);
    }
    long start = System.nanoTime();
    for (int i = 0; i < runs; i++) {
        int sum = sumDigits(i);
        // prevent optimising away.
        if (sum < 0) throw new AssertionError();
    }
    long time = System.nanoTime() - start;
    System.out.printf("sumDigits took an average of %,d ns%n", time / runs);
    long start2 = System.nanoTime();
    int lastSum = 0;
    for (int i = 0; i < runs; i++) {
        int sum = sumDigits(lastSum, i);
        lastSum = sum;
        // prevent optimising away.
        if (sum < 0) throw new AssertionError();
    }
    long time2 = System.nanoTime() - start2;
    System.out.printf("sumDigits using previous value took an average of %,d ns%n", time2 / runs);

    long large = Long.MAX_VALUE - runs - 1;

    long start3 = System.nanoTime();
    for (int i = 0; i < runs; i++) {
        int sum = sumDigits(large + i);
        // prevent optimising away.
        if (sum < 0) throw new AssertionError();
    }
    long time3 = System.nanoTime() - start3;
    System.out.printf("sumDigits took an average of %,d ns%n", time3 / runs);
    long start4 = System.nanoTime();
    int lastSum2 = sumDigits(large);
    for (int i = 0; i < runs; i++) {
        int sum = sumDigits(lastSum2, large + i);
        lastSum2 = sum;
        // prevent optimising away.
        if (sum < 0) throw new AssertionError();
    }
    long time4 = System.nanoTime() - start4;
    System.out.printf("sumDigits using previous value took an average of %,d ns%n", time4 / runs);

}

public static int sumDigits(long n) {
    int sum = 0;
    do {
        sum += n % 10;
        n /= 10;
    } while (n > 0);
    return sum;
}

public static int sumDigits(int prevSum, long n) {
    while (n > 0 && n % 10 == 0) {
        prevSum -= 9;
        n /= 10;
    }
    return prevSum + 1;
}

打印

sumDigits took an average of 32 ns
sumDigits using previous value took an average of 10 ns
sumDigits took an average of 79 ns
sumDigits using previous value took an average of 7 ns

对于较大的值,它可以节省大约 70 ns。它给你的代码增加了一些复杂性。您必须使用第一个 sumDigit 来引导总和,因为您无法从 1 一直数到 10^18。

【讨论】:

  • 很好的答案(我 +1),但你确定这是 最快的方法吗? :-)
  • 它并不总是最快的方式,但它是最直接且最有可能奏效的。当你开始使用这样的小方法时,唯一确定的方法是在微基准中进行比较,我怀疑这种差异不值得担心。
  • @amit true 他也在努力寻找最快的解决方案。我怀疑尝试重用旧值比它的价值更麻烦,即它不会快得多,但会复杂得多。
  • @PeterLawrey:我不确定。 ypercube 建议的解决方案可以更好地扩展(O(logn)vs O(loglogn)),并且非常简单恕我直言。我也相信他的方法的常数很小。当然,如果它不会发生在大量和/或紧密循环中并不重要 - 但我假设如果不是这种情况 - OP会坚持天真的解决方案(他也在问题中提出)
  • @amit 非常正确。实际上,我试图求和的数字很大(最多 10^18),所以天真的方法没有机会。我也可以将第一个数字读取为字符串,因此我计划使用更有效的 int sum(String num ) 方法(可以通过一次读取一个字符轻松计算总和)来计算第一个数字的数字总和。
【解决方案3】:

让我们表示数字n的数字总和是s(n)

s(49) = 13

s(94) = 13

但是49+1 = 50 哪个s(50) = 5,而94+1 = 95 哪个s(95) = 14。因此,如果您只知道n 的数字总和是 13,那么对于 n+1 的数字总和,您至少有两个不同的可能答案。 你需要一些关于 n 的额外信息

我确实认为关键是知道如果 n 以 9 结尾,s(n+1) 最多为 s(n) - 9。(啊,这就是 ypercube 的答案所在。)所以如果你有 xxxx9(其中最后一个 x 不是 9),s(xxxx9 + 1) = s(xxxx9) - 9 + 1。如果你有xxx99、s(xxx99 + 1) = s(xxx99) - 18 + 1

因此,如果您计算范围内的所有 10、100、数千等,可能会加快这一速度。

(我再次看到 ypercube 击败了我)。看起来这正是 A037123 的公式所做的(但从 0 到 n)。 (让我们称之为a(n)

所以最后,既然你想要从 n 到 n+r 的数字总和的总和,而 不是从 1 到 n 的总和,我们需要看看我们是否可以推导出一个范围ss(n,n+r)的数字总和的公式@

看起来很简单

ss(n,n+r) = a(n+r) - a(n-1)

【讨论】:

    【解决方案4】:

    这是来自 wikipedia 的公式:


    这是我在 Java 中的实现:

    public static int digitSum(int x) {
        return IntStream.rangeClosed(0, (int) Math.log10(x)) // From zero to the number of digits of x in base 10...
                   .map(n -> (x % (int) Math.pow(10, n + 1) -  x % (int) Math.pow(10, n)) / (int) Math.pow(10, n)) // ...yield each digit...
                   .sum() // and sum;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-02
      • 2022-11-17
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多