在 ruby 中从没有循环的范围编号中计算相同的包含编号

Counting same containt number from range number without loop in ruby

示例:如果我有 1..100 范围内的数字并且我想计算所有包含 14 的数字,我会使用:

total = 0

(1..100).each do |x|
 if x.to_s.include?("14")
   total += 1
 end
end

puts "total : #{total}"

此代码有效,但如果数字范围太长,如 (1..1000000000) 或更长,如何执行此操作。当然这需要很长时间来计算总数。我的问题是如何在没有循环的情况下计算相同包含的数字?因为在我的情况下,我需要更快的方式来获得总数。

考虑范围:

def str_range(n)
  "1"..(10**n).to_s
end

我将解决查找范围内包含字符串 "14"(一次或多次)的字符串数的问题。1 一种直接的方法是:

def nbr_14s_brute_force(n)
  str_range(n).count { |s| s[/14/] }
end

nbr_14s_brute_force(2) #=>     1
nbr_14s_brute_force(3) #=>    20
nbr_14s_brute_force(4) #=>   299
nbr_14s_brute_force(5) #=>  3970 
nbr_14s_brute_force(6) #=> 49401 

现在让我们寻找一种更有效的方法,借鉴离散概率论。

假设n = 2。那么:

str_range(2) #=> "1".."100"

由于 "14" 在范围 "1".."100" 中只出现一次,因此范围中只有一个(字符串)元素包含 "14".

现在让 cnt_first(n,i) 等于 str_range(2) 的元素数 包含 "14" 当十位等于 "i":

cnt_first(2,1) # => 9
cnt_first(2,i) # => 10

对于 0 <= i <= 9, i != 4

此外,让 cnt(2) 表示范围 str_range(2) 中不包含 "14" 的字符串数。我们有:

cnt(2) = (0..9).reduce { |i| cnt_first(2,i) }
  #=> 99

由此可见,包含"14"的字符串的个数等于100 - cnt(2) #=> 1

现在考虑 n #=> 3,其中:

str_range(3)
  #=> "1".."1000"

在此范围内的 1,000 个元素中,有多少元素包含字符串 "14"?

我们可以直接剔除"1000",因为"14"的单位位置没有零,所以我们只需要考虑范围"1".."999".

假设第一位(百位)的数字是1。那么不包含 "14" 的字符串必须在十列中包含 "4" 以外的字符。因此,以"1"开头且不包含"14"的两位数字符串的个数等于:

cnt_first(3,1) = cnt_first(2,0) + cnt_first(2,1) + cnt_first(2,2) +
                 cnt_first(2,3) + cnt_first(2,5) + cnt_first(2,6) +
                 cnt_first(2,7) + cnt_first(2,8) + cnt_first(2,9)  

               = (0..9).reduce { |i| cnt_first(2,i) } - cnt_first(2,4)
  #=> cnt(2) - cnt_first(2,4)
  # => 99 - 10 => 89

现在假设百位数字 (i) 不等于 1。那么:

cnt_first(3,i) = (0..9).reduce { |i| cnt_first(2,i) }
  # => cnt(2) => 99

所以

cnt(3) = 10 * cnt(2) - cnt_first(2,4)
  #=> 10 * 99 - 10 => 980

表示 "1"-"1000" 范围内有 1000 - 980 #=> 20 个数字不包含 "14"。早先的 "brute force" 计算证实了这一点。

可见计算cnt(4)的步骤与计算cnt(3)的步骤相同。

对于i = 1

cnt_first(4,1) = cnt(3) - cnt_first(3,4)

对于i != 1

cnt_first(4,i) = cnt(3) => 980

所以

cnt(4) = 10 * cnt(3) - cnt_first(3,4)
  #=> 10 * 980 - 99 => 9800 - 9701

所以对于 n => 410000 - 9701 #=> 299 个不包含 "14" 的字符串,这再次被前面的计算所证实。

如果reader不服气,这个可以很容易的用归纳法证明。

可以使用简单的递归方法来计算所需的结果:

def nbr_14s_smart(n)
  10**n - recurse(n).first
end

def recurse(n)
  if n == 2
    puts "2: 1"
    [99, 10]
  else 
    cnt, cnt4 = recurse(n-1)
    new_cnt, new_cnt4 = 10 * cnt - cnt4, cnt
    puts "#{ n }: #{ 10**n - new_cnt }"
    [new_cnt, new_cnt4]
  end
end

nbr_14s_smart(10)
  #  2: 1
  #  3: 20
  #  4: 299
  #  5: 3970
  #  6: 49401
  #  7: 590040
  #  8: 6850999
  #  9: 77919950
  # 10: 872348501
  #=> 872348501 

1Only minor modifications are required to replace "14" with the string representation of an
arbitrary two-digit number, but the exposition is easier to follow by hardwiring the string.