Count the number of Ks between 0 and N

巧了我就是萌 提交于 2019-11-28 17:39:56

I believe this is what's your need, simple, general and fast.

Below is an example in Python:

Slow Checker

The checker is simple, use string to find all number in string from '0' - 'n', and count the match times of k, it's slow but we can use it to check other solutions.

import string       

def knChecker( k, n ):
    ct = 0
    k = str(k)
    for i in xrange(0,n+1):
        ct += string.count(str(i),k)
    return ct   

Fast and General Solution

k ≠ 0

for every k = [1,9],it's much clear that in [0,9] we can find 1 match in first bit;

in [0,99] we can find 1 matches in first bit and 10 matches in second bit, so all is 1*10^1 + 10*10^0 = 20 matches,

in [0,999] we can find 1 matches in first bit ,10 matches in second bit and 100 matches in third bit, so all is 1*10^2 + 10*10^1 + 100*10^0 = 300 matches...

So we can easily conclude that in [0,10^l - 1], there is l * 10^(l-1) matches.

More general, we can find in [0,f * 10^l - 1], there f*10^(l-1) * l matches.

So here is the solution:

for example, n = 'abcd', k = 'k'

  • step1: if n = 0 or n = '', return 0; count matches in 'a000', use the up formula, l = len(n)
  • step2A: if a == k, we know all 'bcd' is matched, so add bcd matches.
  • step2B: if a > k, we know all 'k***' is matched, so add 10^(l-1) matches.
  • step3: cut the first bit a, and set n = 'bcd', go to step1

Here is the code for k ≠ 0:

def knSolver( k, n ):
    if k == '0':
        return knSolver0( n, 0 )
    if not n:
        return 0
    ct = 0
    n = int(n)
    k = int(k)
    l = len(str(n))
    f = int(str(n)[:1])
    if l > 1:
        ct += f * 10 ** (l-2) * (l-1)
    if f > k:
        ct += 10 ** (l-1)
    elif f == k:
        ct += n - f * 10 ** (l-1) + 1
    return ct + knSolver( k, str(n)[1:])

k = 0

k = 0 is a bit of tricky, because 0*** is equal to *** and will not allowed to count it marches '0'.

So solution for k ≠ 0 can't fit k = 0. But the idea is similar.

We can find that if n < 100, there must be n/10 + 1 matches.

if n in [100,199], it's much similar that as k ≠ 0 in [0,99], has 20 matches;

if n in [100,999], it's much similar that as k ≠ 0 in [100,999], has 20 * 9 matches;

if n in [1000,9999], it's much similar that as k ≠ 0 in [1000,9999], has 300 * 9 matches...

More general, if n in [10^l,k*10^l-1], it will has l*10^(l-1)*k matches.

So here is the solution:

for example, n = 'abcd', k = '0', recurse step s = 0

  • step0: if n = '', return 0; if n < 100, return n/10+1;
  • step1A: n='f(...)', f is first bit of n. if s > 0, say we have handled the first bit before, so 0 can treat as k ≠ 0, so if f == 0, all rest (...) should match, just add (...)+1 matches.
  • step1B: if s > 0 and f > 0, l = len(n), we know there will be 10 ** (l-1) matched in the first bit of 0(...), and (l-1) * 10 ** (l-2) in (...)
  • step2: if s == 0, count matches in 'f(...)-1', use the up formula
  • step3: if s > 0, just check for (...) as s == 0 in step2, will get (f-1) * 10 ** (l-2) * (l-1), (f-1), because we can't start form 0***.
  • step4: cut the first bit f, and set n = '(...)', s += 1, go to step1

Here is the code of k = 0:

def knSolver0( n, s ):
    if n == '':
        return 0
    ct = 0
    sn = str(n)
    l = len(sn)
    f = int(sn[:1])
    n = int(n)
    if n < 100 and s == 0:
        return n / 10 + 1
    if s > 0 and f > 0:
        ct += 10 ** (l-1) + (l-1) * 10 ** (l-2)
    elif s > 0 and f == 0:
        ct += n + 1
    if n >= 100 and s == 0:
        ct += 10
        for i in xrange(2,l):
            if i == l-1:
                ct += i * 10 ** (i-1) * (f-1)
            else:
                ct += i * 10 ** (i-1) * 9
    elif s > 0 and f != 0:
        ct += (f-1) * 10 ** (l-2) * (l-1)
    return int(ct + knSolver0( sn[1:], s+1 ))

Test

print "begin check..."
for k in xrange(0,10):
    sk = str(k)
    for i in xrange(0,10000):
        #knSolver( sk, i )
        if knChecker( sk, i ) != knSolver( sk, i ):
            print i, knChecker( sk, i ) , knSolver( sk, i )
print "check end!"

Test all k[0,9] from n[0,10000], it passed all cases.

The test will take a bit long time, because of the checker is slow. If remove the checker, all cases in my laptop take about one second.

It can be done arithmetically.

EDIT

I didn't see your code example at first. My code is very similar, except inputs are parametrized. So the answer is Yes, it can be generalized, but you need to handle 0 as special case.

If the given number N is two digits number, let's say AB and we are counting digit K (1..9).

IF B is less than K THEN 0 ELSE 1
IF A is less than K THEN A ELSE A + 10

Your example Input: K=2, N=35

5 is greater than 2 -> count = 1 (this is digit 2 in number 32)
3 is greater than 2 -> count += 3 (this are twos in 2, 12, 22) + 10 (this are 20,21,22,23,24,25,26,27,28,29)
*22 is counted twice

so we count 1 + 3 + 10 = 14 twos

C# Code example (n = 1..99, k = 1..9):

int numberOfKsBetween0AndN (int n, int k)        
{
    int power = 1;
    int counter = 0;

    while (n > 0)
    {
        int d = n % 10;
        n /= 10;

        counter += (d < k ? 0 : power) + d * power / 10;
        power *= 10;
    }

   return counter;
}

Improved code for n > 100

UPDATE

There was an error in condition I didn't take in account digits when d is equal to k, for k=2, N=2145 my algorithm didn't take in account fist digit two in 2000..2145. Now it works as it should (pass all tests):

    int numberOfKsBetween0AndN (int n, int k)   
    { 
        int originalNumber = n;
        int power = 1;
        int i = 0;
        int counter = 0;            

        while (n > 0)
        {
            int d = n % 10;
            n /= 10;

            counter += d * (power * i) / 10;

            if (d > k)
                counter += power;
            else if (d == k)
                counter += originalNumber % power + 1;

            power *= 10;
            i++;
        }

        return counter;
    }

UPDATE 2

For k=0 (including 0 and n) is easier, you just need to count numbers divisible by 10, 100, 1000, etc.

int numberOf0sBetween0AndN(int n)
{
    int power = 1;            
    int counter = 1;

    while(power < n)
    {                
        power *= 10;
        counter += n / power;
    }

    return counter;
}

It's simple. Any number can be represented in a special record:

73826 = 9999 + 6*(9999 + 1) + (3826 + 1)

You will need to count the number of these figures for the numbers 9, 99, 999, 9999 ... Then you can put them in the array. Special case - by 0, it uses an array [1, 11, 111, 1111, ...]

Keep in mind that the first digit so also may contain the required value.

For case 0, we need to handle separately,

For general case from 1 to 9:

Assume that we know the number contains k that has x digits and called it m, to calculate all number contains k and has (x + 1) digits, the formula will be :

9*m + (all_number_has_x_digit - m)

The reason is simple, to all number that already contains k, we can insert 1 to 9 as first digit,so we have 9*m. For all number that doesn't contains k, we can add k in front of them, which created all_number_has_x_digit - m).

To calculate the number of k appears from all these numbers, the formula should be similar, (By maintaining both values : the amount of numbers that contain k and the number of appearances of k) this is just an idea for you to start :)

You could do it if you convert the integers to Strings and match the String 'k' in a loop through that array.

NISHIKANT GOUTAM

The simplest solution this problem is using regular expression.

//_k --> pattern
//_n range

Regex r = new Regex(_k.ToString(), RegexOptions.IgnoreCase);
        int count = 0;

        for (int i = 0; i <= _n; i++)
        {
            MatchCollection m = r.Matches(i.ToString());
            foreach(Match _m in m)
            { 
            if (_m.Success)
            {
                count += 1;
            }
            }

        }

        Console.WriteLine(count);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!