【问题标题】:Sum of all numbers written with particular digits in a given range在给定范围内用特定数字书写的所有数字的总和
【发布时间】:2015-07-08 06:58:04
【问题描述】:

我的目标是找到从 4 到 666554 的所有数字的总和,其中仅包含 4,5,6。

SUM = 4+5+6+44+45+46+54+55+56+64+65+66+.....................+666554.

简单的方法是运行一个循环并将仅由 4,5 和 6 组成的数字相加。

long long sum = 0;
for(int i=4;i <=666554;i++){
   /*check if number contains only 4,5 and 6.
     if condition is true then add the number to the sum*/
}

但它似乎效率低下。检查数字是否由 4,5 和 6 组成需要时间。有什么办法可以提高效率。我尝试了很多,但没有找到新方法。请帮助。

【问题讨论】:

  • 在网络上稍微搜索一下就很容易找到:en.wikipedia.org/wiki/Arithmetic_progression#Sum 了解详情。是的,甚至 SO 已经覆盖了this question。下次再做一点研究。
  • @Evert:我很确定你链接的问题不一样。
  • @Julo0sS:正则表达式效率不高。
  • @Dair,哈哈哈!我的错,我主要是按标题来的。
  • 你说得对,这个不用构造数字就可以计算出来。 提示:设 g(n) 是所有 456 个数字的总和,恰好是 n 位数字。写下 g 的递归关系。

标签: algorithm math


【解决方案1】:

对于 1 位数字,请注意

4 + 5 + 6 == 5 * 3

对于 2 位数字:

(44 + 45 + 46) + (54 + 55 + 56) + (64 + 65 + 66)
== 45 * 3 + 55 * 3 + 65 * 3
== 55 * 9

等等。

一般来说,n-digits 数字有 3n 个,只有 4,5,6,它们的平均值正好是 5...5 (n 位数)。使用代码,它们的总和是('5' * n).to_i * 3 ** n(Ruby)或int('5' * n) * 3 ** n(Python)。

您最多计算 6 位数字,然后将 666555 的总和减去 666666。


P.S:对于像666554 这样的小数字,使用模式匹配已经足够快了。 (example)

【讨论】:

  • 如果我没看错你的想法,结果应该是 409632209。
  • n位数字的公式是什么?从示例中我看不到。
  • @ColonelPanic 在代码中,('5' * n).to_i * 3 ** n (Ruby) 或 int('5' * n) * 3 ** n (Python)。
  • @YuHao;您应该将comment 添加到答案中以概括该概念。
  • 干杯。对于后代,您也可以在没有字符串操作的情况下编写 (10**n-1)*5//9 * 3**n
【解决方案2】:

实现一个以 3 为底的计数器(数字值的数量),例如0,1,2,10,11,12,20,21,22,100.... 然后将 base-3 数字转换为具有数字 4,5,6 (0->4, 1->5) 的十进制数, 2->6),并添加到运行总数中。重复直到极限。

def compute_sum(digits, max_val):

  def _next_val(cur_val):
    for pos in range(len(cur_val)):
      cur_val[pos]+=1
      if cur_val[pos]<len(digits):
        return
      cur_val[pos]=0
    cur_val.append(0)

  def _get_val(cur_val):
    digit_val=1
    num_val=0
    for x in cur_val:
      num_val+=digits[x]*digit_val
      digit_val*=10
    return num_val

  cur_val=[]
  sum=0
  while(True):
    _next_val(cur_val)
    num_val=_get_val(cur_val)
    if num_val>max_val:
      break
    sum+=num_val
  return sum

def main():
  digits=[4,5,6]
  max_val=666554
  print(digits, max_val)
  print(compute_sum(digits, max_val))

【讨论】:

  • 我在考虑这些行,但我认为转换为十进制并不是特别简单,尤其是对于较大的数字。我认为您需要提供翻译方法。
  • 上面代码中的 _get_val 方法根据给定的数字值从 base-3 转换为十进制
  • 也许人们不知道什么是嵌套方法,所以尽管代码清晰简洁(更不用说优雅),但他们不理解代码?
【解决方案3】:

数学很好,但并非所有问题都可以简单地“压缩”,因此知道如何在没有数学的情况下处理它们是值得的。


在这个问题中,求和是微不足道的,乍一看,困难在于有效地枚举需要相加的数字。

“过滤”路线是一种可能性:逐步生成所有可能的数字,并过滤掉那些不匹配的数字;但是它也非常低效(通常):

  • 条件匹配可能并不简单:在这种情况下,更简单的方法是转换为字符串(对除法和测试相当繁重),然后进行字符串匹配
  • 过滤的比率从每位数 30% 开始并不算太差,但它的扩展性非常差,正如gen-y-s 所说:对于 4 位数字,它是 1%,或者生成并检查 100 个数字从中抽出 1 个。

因此,我建议采用“世代相传”的方法:只生成符合条件的数字(以及所有这些数字)。

我会注意到生成所有由 4、5 和 6 组成的数字就像计数(以三进制):

  • 从4开始
  • 45 变成 46(小心结转)
  • 66 变成 444(极端结转)

让我们开始吧,在 Python 中,作为生成器:

def generator():
    def convert(array):
        i = 0
        for e in array:
            i *= 10
            i += e
        return i

    def increment(array):
        result = []
        carry = True

        for e in array[::-1]:
            if carry:
                e += 1
                carry = False
            if e > 6:
                e = 4
                carry = True
            result = [e,] + result

        if carry:
            result = [4,] + result

        return result

    array = [4]
    while True:
        num = convert(array)
        if num > 666554: break

        yield num
        array = increment(array)

它的结果可以用sum(generator())打印出来:

$ time python example.py
409632209
python example.py  0.03s user 0.00s system 82% cpu 0.043 total

还有here is the same in C++。

【讨论】:

  • 过滤率为每位数30%。因此,对于 4 位数字,该比率小于 0.1%
  • @gen-y-s:哦!好收获!
  • @MatthieuM。 - gen-y-s 实际上是错误的。它不到 1%,而不是 0.1%(显然仍然很低)。确实有点明显,例如,在 100 和 1000 之间,您将有 444、445、446、454... 远不止 1,实际上它将是 27。
【解决方案4】:

“从一个更简单的问题开始。” ——波利亚

将仅由数字 4,5,6 组成的 n 位数字相加

正如余浩在上面解释的那样,有3**n 数字,它们的对称平均值是例如。 555555,所以总和是3**n * (10**n-1)*5/9。但是,如果您没有发现这一点,那么您可以通过以下方式解决问题。

这个问题有一个递归结构,所以让我们尝试一个递归解决方案。令 g(n) 为正好 n 位的所有 456 个数字的总和。然后我们有递归关系:

g(n) = (4+5+6)*10**(n-1)*3**(n-1) + 3*g(n-1)

要查看这一点,请将总和中每个数字的第一个数字分开(例如,对于 n=3,百列)。这给出了第一个术语。第二项是剩余数字的总和,每个前缀 4,5,6 的 g(n-1) 计数。

如果仍不清楚,请写出 n=2 的总和并将十位与单位分开:

g(2) = 44+45+46 + 54+55+56 + 64+65+66
     = (40+50+60)*3 + 3*(4+5+6)
     = (4+5+6)*10*3 + 3*g(n-1)

酷。在这一点上,敏锐的读者可能想检查于浩的 g(n) 公式是否满足我们的递推关系。

为了解决 OP 的问题,从 4 到 666666 的所有 456 个数字的总和是g(1) + g(2) + g(3) + g(4) + g(5) + g(6)。在 Python 中,使用动态编程:

def sum456(n):
    """Find the sum of all numbers at most n digits which consist of 4,5,6 only"""
    g = [0] * (n+1)
    for i in range(1,n+1):
        g[i] = 15*10**(i-1)*3**(i-1) + 3*g[i-1]
    print(g) # show the array of partial solutions
    return sum(g)

对于 n=6

>>> sum456(6)
[0, 15, 495, 14985, 449955, 13499865, 404999595]
418964910

编辑:我注意到 OP 在 666554 处截断了他的总和,因此它不符合一般模式。最后几项会少一些

>>> sum456(6) - (666555 + 666556 + 666564 + 666565 + 666566 + 666644 + 666645 + 666646 + 666654 + 666655 + 666656 + + 666664 + 666665 + 666666)
409632209

【讨论】:

  • 在那里使用幻数9332701...为什么不直接使用def sum456(): return 409632209呢?
  • 如何通过归纳法或其他方法证明递归关系。
  • @DeepakYadav 上面是递归关系的证明(重新排列总和并对其进行不同的分组是一种常见的技巧)。你的意思是解决它(作为n的函数)?那会很酷,但我不知道如何。
  • @SparK OP 的总和与具有简洁数学属性的一般模式相比被截断。我怀疑有什么聪明的方法来计算差异。
【解决方案5】:

4 到 666666 的总和是:

total = sum([15*(3**i)*int('1'*(i+1)) for i in range(6)])
>>> 418964910

666554和666666之间的几个数之和是:

rest = 666555+666556+666564+666565+666566+
666644+666645+666646+
666654+666655+666656+
666664+666665+666666
>>> 9332701

total - rest
>>> 409632209

【讨论】:

    【解决方案6】:

    问题的Java实现:- 这使用模(10^9 +7)作为答案。

    public static long compute_sum(long[] digits, long max_val, long count[]) {
        List<Long> cur_val = new ArrayList<>();
        long sum = 0;
        long mod = ((long)Math.pow(10,9))+7;
        long num_val = 0;
        while (true) {
            _next_val(cur_val, digits);
            num_val = _get_val(cur_val, digits, count);
            sum =(sum%mod + (num_val)%mod)%mod;
            if (num_val == max_val) {
                break;
            }
    
        }
        return sum;
    }
    
    public static void _next_val(List<Long> cur_val, long[] digits) {
        for (int pos = 0; pos < cur_val.size(); pos++) {
            cur_val.set(pos, cur_val.get(pos) + 1);
            if (cur_val.get(pos) < digits.length)
                return;
            cur_val.set(pos, 0L);
        }
    
        cur_val.add(0L);
    }
    
    public static long _get_val(List<Long> cur_val, long[] digits, long count[]) {
        long digit_val = 1;
        long num_val = 0;
        long[] digitAppearanceCount = new long[]{0,0,0};
        for (Long x : cur_val) {
            digitAppearanceCount[x.intValue()] = digitAppearanceCount[x.intValue()]+1;
            if (digitAppearanceCount[x.intValue()]>count[x.intValue()]){
                num_val=0;
                break;
            }
            num_val = num_val+(digits[x.intValue()] * digit_val);
            digit_val *= 10;
        }
        return num_val;
    }
    
    
    public static void main(String[] args) {
    
        long [] digits=new long[]{4,5,6};
        long count[] = new long[]{1,1,1};
        long max_val= 654;
    
        System.out.println(compute_sum(digits, max_val, count));
    }
    

    @gen-y-s (https://stackoverflow.com/a/31286947/8398943) 的答案是错误的(它包括 x=y=z=1 的 55、66、44,超出了可用的 4s、5s、6s)。它给出的输出为 12189,但对于 x=y=z=1,它应该是 3675。

    @Yu Hao (https://stackoverflow.com/a/31285816/8398943) 的逻辑和上面说的有同样的错误。它给出的输出为 12189,但对于 x=y=z=1,它应该是 3675。

    【讨论】:

      猜你喜欢
      • 2016-10-15
      • 2015-07-28
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 1970-01-01
      • 2022-11-10
      • 1970-01-01
      相关资源
      最近更新 更多