有多少个长度为N且K个数字连续为D的数字

时间:2014-10-17 09:21:47

标签: algorithm math

给出正数N, K, D (1<= N <= 10^5, 1<=K<=N, 1<=D<=9)。有N个数字的数字有多少,K个连续数字D?写下答案mod (10^9 + 7)

例如:N = 4, K = 3, D = 6,有18个数字:

1666, 2666, 3666, 4666, 5666, 6660, 6661, 6662, 6663, 6664, 6665, 6666, 6667, 6668, 6669, 7666, 8666 and 9666

我们可以在O(N*K)(也许是动态编程)中计算答案吗? 我尝试过使用组合。 如果

N = 4, K = 3, D = 6。我必须找到的数字是abcd。

+) if (a = b = c = D), I choose digit for d. There are 10 ways (6660, 6661, 6662, 6663, 6664, 6665, 6666, 6667, 6668, 6669)

+) if (b = c = d = D), I choose digit for a (a > 0)。有9种方式(1666, 2666, 3666, 4666, 5666, 6666, 7666, 8666, 9666) 但在两种情况下,数字6666计算两次。 NK非常大,我怎么能统计所有这些?

2 个答案:

答案 0 :(得分:1)

Miquel几乎是正确的,但他错过了很多案例。因此,在N = 8,K = 5和D = 6的情况下,我们需要查找具有以下形式的数字:

66666xxx
y66666xx
xy66666x
xxy66666

附加条件y不能为D.

所以我们可以得到这个例子的公式:

66666xxx = 10^3
y66666xx = 8*10^2 // As 0 can also not be the first number
xy66666x = 9*9*10
xxy66666 = 9*10*9

所以,结果是3420。

对于N = 4,K = 3且D = 6的情况,我们有

666x = 10
y666 = 8//Again, 0 is not counted!

所以,我们有18个案例!

注意:我们需要注意第一个数字不能为0!当D为零时我们需要处理这个案例!

更新 Java工作代码,时间复杂度O(N-K)

 static long cal(int n, int k, int d) {
    long Mod = 1000000007;
    long result = 0;
    for (int i = 0; i <= n - k; i++) {//For all starting positions
        if (i != 0 || d != 0) {
            int left = n - k;
            int upper_half = i;//Amount of digit that preceding DDD
            int lower_half = n - k - i;//Amount of digit following DDD
            long tmp = 1;
            if (upper_half == 1) {
                if (d == 0) {
                    tmp = 9;
                } else {
                    tmp = 8;
                }
            }else if(upper_half >= 2){
                //The pattern is x..yDDD...
                tmp = (long) (9 * 9 * Math.pow(10, upper_half - 2));
            }

            tmp *= Math.pow(10, lower_half);
            //System.out.println(tmp + " " + upper_half + " " + lower_half);
            result += tmp;
            result %= Mod;

        }
    }
    return result;
}

样本测试:

  • N = 8,K = 5,D = 6

输出

3420
  • N = 4,K = 3,D = 6

输出

18
  • N = 4,K = 3,D = 0

输出

9

答案 1 :(得分:1)

如果一个人正在寻找一个数学解决方案(而不一定是一个算法解决方案),最好根据基本情况和一些公式来看待它。它们可能会变成你可以做某种重构并得到一个整洁的公式。所以只是为了它...它的一个看法是没有处理零的特殊处理。因为那会抛出一些扳手。

让我们看几个基本情况,然后调用我们的答案F(N,K)(不考虑D,因为它与帐户无关;但无论如何都将其作为参数。)< / EM>:

当N = 0 时

当没有数字时,你永远不会找到任何长度的数字序列。

  • F(0, K) = 0任何K。
当N = 1时,

相当明显。如果您要查找单个数字中的K个连续数字,则选项会受到限制。寻找多个?没有骰子。

  • F(1, K) = 0任何K&gt; 1

正在寻找一个?好的,有一个。

  • F(1, 1) = 1

允许零连续数字的序列?然后所有十位数字都没问题。

  • F(1, 0) = 10

表示N> 1

当K = 0 时

基本上,所有N位数字都符合条件。因此,符合条形码的可能性数量为10^N(例如,当N为3时,对于任何D,则为000,001,002,... 999)

  • F(N, 0) = 10^N任何N&gt; 1

当K = 1

符合条件的可能性是任何至少有一个D的数字。有多少个N位数字包含至少一个数字D?好吧,它将是10 ^ N减去所有具有 no 数字D实例的数字。10^N - 9^N

  • F(N, 1) = 10^N - 9^N任何N&gt; 1

当N < ķ

如果N小于K

,则无法获得K个连续数字
    时,
  • F(N, K) = 0 ķ
当N = K 时

只有一种可能的方法来获得N个数字的K个连续数字。

    当N = K 时,
  • F(N, K) = 1
当N> ķ

好的,我们已经知道N&gt; 1和K>所以这将成为我们希望将子表达式用于我们已经解决过的事情的主力。

让我们首先考虑弹出头部的数字,在尾部留下N-1个数字。如果那个N-1系列本身可以实现一系列K个数字,那么添加另一个数字将不会改变任何相关的数字。这给了我们一个术语10 * F(N-1, K)

但如果我们的头部数字是D,那就很特别。我们的案例将是:

  1. 它可能是以K-1 D实例开头的系列的缺失键,创建了全范围的K.

  2. 它可能会完成一系列D-K的K-1实例,但是对于已经有K系列相邻D的情况(我们因此在上述术语中考虑)

  3. 根本没有帮助。

  4. 因此,让我们考虑两个不同类别的尾部序列:以D的K-1实例开始的那些和不的那些。假设我们将N = 7显示为D:DDDXYZ并且K = 4。我们从N和K中减去一个得到6和3,如果我们减去它们,我们得到允许变化的任意位数(XYZ)。我们加入的(1)和(2)联合的术语是10^((N-1)-(K-1))

    现在是时候对我们的双重计数进行一些减法了。我们没有重复计算任何没有以K-1 D实例开始的情况,所以我们继续关注它(DDDXYZ)。如果X槽中的值是D,则绝对重复计算。我们可以将该术语减去10^(((N - 1) - 1) - (K - 1));在这种情况下,给我们所有的YZ数字配对,你可以用X得到D.(100)。

    要摆脱的最后一件事是X是而不是 D的情况,但是在X位置之后剩下的任何东西仍然是K长度系列D.再次我们重用我们的功能,并减去术语9 * F(N - K, K, D)

    将它们粘贴在一起并简化您获得的几个术语:

    • F(N, K) = 10 * F(N-1,K,D) + 10^(N-K) - 10^(10,N-K-1) - 9 * F(N-K-1,K,D)

    现在我们有一个很适合Haskelling或其他的功能定义。但是我仍然很尴尬,而且用C ++进行测试很容易。所以这里是(假设有一个长整数power函数的可用性):

    long F(int N, int K, int D) {
        if (N == 0) return 0;
        if (K > N) return 0;
        if (K == N) return 1;
    
        if (N == 1) {
            if (K == 0) return 10;
            if (K == 1) return 1;
            return 0;
        }
        if (K == 0)
            return power(10, N);
        if (K == 1)
            return power(10, N) - power(9, N);
    
        return (
            10 * F(N - 1, K, D)
            + power(10, N - K)
            - power(10, N - K - 1)
            - 9 * F(N - K - 1, K, D)
        );
    }
    

    要对详尽的生成器进行仔细检查,这里有一个小的C ++测试程序,它使用std::search_n构建它扫描的向量列表。它检查N和K的快速方式的慢速方式。我将它从0运行到9:

    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    
    // http://stackoverflow.com/a/1505791/211160    
    long power(int x, int p) {
        if (p == 0) return 1;
        if (p == 1) return x;
    
        long tmp = power(x, p/2);
        if (p%2 == 0) return tmp * tmp;
        else return x * tmp * tmp;
    }
    
    long F(int N, int K, int D) {
        if (N == 0) return 0;
        if (K > N) return 0;
        if (K == N) return 1;
    
        if (N == 1) {
            if (K == 0) return 10;
            if (K == 1) return 1;
            return 0;
        }
        if (K == 0) 
            return power(10, N);
        if (K == 1)
            return power(10, N) - power(9, N);
    
        return (
            10 * F(N - 1, K, D)
            + power(10, N - K)
            - power(10, N - K - 1) 
            - 9 * F(N - K - 1, K, D)
        );
    }
    
    long FSlowCore(int N, int K, int D, vector<int> & digits) {
        if (N == 0) {
            if (search_n(digits.begin(), digits.end(), K, D) != end(digits)) {
                return 1;
            } else
                return 0;
        }
    
        long total = 0;
        digits.push_back(0);
        for (int curDigit = 0; curDigit <= 9; curDigit++) {
                total += FSlowCore(N - 1, K, D, digits);
                digits.back()++;
        }
        digits.pop_back();
        return total;
    }
    
    long FSlow(int N, int K, int D) {
        vector<int> digits;
        return FSlowCore(N, K, D, digits);
    }
    
    bool TestF(int N, int K, int D) {
        long slow = FSlow(N, K, D);
        long fast = F(N, K, D); 
        cout << "when N = " << N
             << " and K = " << K 
             << " and D = " << D << ":\n";
        cout << "Fast way gives " << fast << "\n";
        cout << "Slow way gives " << slow << "\n";
        cout << "\n";
        return slow == fast;
    }
    
    int main() {
       for (int N = 0; N < 10; N++) {
            for (int K = 0; K < 10; K++) {
                if (!TestF(N, K, 6)) {
                    exit(1);
                }
            }
        } 
    }
    

    当然,因为它计算前导零,所以它与你得到的答案不同。请参阅the test output here in this gist

    修改以考虑特殊情况零处理是留给读者的练习(与模运算一样)。消除零会使它变得更加混乱。无论哪种方式,这可能是一种攻击的途径,甚至可以通过一些转换进一步减少数学运算的数量......也许。