一道谷歌面试题:求一定范围内满足f(n)=n的最大解

English: Consider a function which, for a given whole numbern, returns the number of ones required when writing out all numbers between 0 andn. For example, f(1) = 1,f(13) = 6. Notice that f(1) = 1. What is the next largestn such that f(n) = nn <= 4 000 000 000.

e.g. f(n) = 6, because the number of "1" in 1,2,3,4,5,6,7,8,9,10,11,12,13 is 6 (1,10,11,12,13).

中文:考虑这样一个函数,对于一个给定的整数n,它能够返回罗列出0到n的所有数字时字符"1"出现的次数。比如f(1) = 1,f(13) = 6,并注意 f(1) = 1。对于n <= 4 000 000 000,满足 f(n) = 的 n 的最大值是多少?

例:f(n) = 6,因为 0,1,2,3,4,5,6,7,8,9,10,11,12,13 中 "1" 的个数为 6 (1,10,11,12,13)。


解题思路:

       为了计算 f(n) 的值,我们需要写一个函数求出从1到n之间"1"的个数。这与 POJ3286 -- How many 0's? 那道计算"0"的个数的题目类似,基本思路是分别计算1到n之间所有的数中"1"出现在个位、十位、百位……上的次数,然后将各结果相加,即得到了f(n)的值。

       然而,知道怎么计算f(n)的值后,如果直接暴力搜索,将 n 从 4 000 000 000 起始,逐次减1,每次求出其f(n)的值,看其是否满足 f(n) = n 来求解最大的 n 值,这对于本题中设定的数据范围是不实际的,速度将会很慢。

       因此,为了能够快速求出 f(n) = n 在给定范围内的最大解,我们需要设计方法替代每次减1求 f(n) 的简单搜索,以便加快 n 从 4 000 000 000 处开始,向下逼近最接近的解的速度。

       易知 f(n) 是一个非减函数,因为若 m < n ,则1到 n 之间的数包含了1到 m 之间的数,所以1到 n 之间的数中出现"1"的次数一定不会小于1到 m 之间的数中"1"出现的次数,即 f(n) 必然不小于 f(m)。

       一个事实是,对于给定一个数n0,求出其 f(n0),然后比较 n0 与 f(n0) 的大小。若f(n0) = n0,则直接求得答案;若 f(n0) > n0 ,则对于所有满足 n0 < n < f(n0) 的n,f(n) >= f(n0) > n,均不符合条件,于是可以直接测试n = f(n0) 时的f(n),而不必逐次加1递增到f(n0);若 f(n0) < n0,则对于所有满足 f(n0) < n < n0 的n,f(n) <= f(n0) < n,均不符合条件,于是可以直接测试n = f(n0)时的f(n),而不必逐次减1递减到f(n0)。 

       根据上述事实,得到求[a, b](其中a >= 0)之间满足f(n)=n的最大解的算法G(a, b):

1. 若a > b,无解,返回-1,算法结束;否则求f(b),比较f(b)与b的大小,转2。

2. 若 f(b) = b,则b即为所求的解,返回b,算法结束;否则转3。

3. 若 f(b) < b:若 f(b) >= a,则令b = f(b),将求解区间缩小为[a, b] = [a, f(b)],转3;若f(b) < a,则在[a, b]区间内无解,返回-1,算法结束。若f(b) > b 则转4。

4. 若 f(a) = a,则返回a与G(a+1, b)中的较大者,算法结束;否则转5。

5. 若 f(a) > a:若f(a) <= b,则令a= f(a),将求解区间缩小为[f(a), b],转5;若f(a) > b,则在[a, b]区间内无解,返回-1,算法结束否则f(a) < a,转6。

6. 令 mid = (a+b)/2,将原区间分为两半。若G(mid, b)存在,则返回G(mid, b),算法结束;否则返回G(a, mid-1),算法结束。

       面试题中a = 0, b = 4 000 000 000,求得的G(a, b)即为答案。

       下面是实现该算法的C代码:

#include <stdio.h>
#include <stdlib.h>

//poj3286

__int64 arr10[] = {1, 10, 100, 1000, 10000, 100000,
1000000, 10000000, 100000000, 1000000000, 10000000000};


//计算0到n之间"1"的个数
__int64 getonesnum(__int64 n)
{
    __int64 result = 0;
    int bitnum;

    if (n <= 0)
        return 0;
   // if (n < 10)
     //   return result;

    bitnum = 0;
    while (bitnum < 10 && n / arr10[bitnum])
    {
        if (n / arr10[bitnum] % 10 == 0)
        {
            result += (n / arr10[bitnum + 1]) * arr10[bitnum];
        }
        else if (n / arr10[bitnum] % 10 == 1)
        {
            result += (n / arr10[bitnum + 1]) * arr10[bitnum];
            result += n % arr10[bitnum] + 1;
        }
        else if (n / arr10[bitnum] % 10 > 1)
        {
            result += (n / arr10[bitnum + 1] + 1) * arr10[bitnum];
        }
        ++bitnum;
    }

    return result;
}


//用递归的方法求出low到high满足f(n)=n的最大解
__int64 getboundedans(__int64 low, __int64 high)
{
    __int64 mid, ans = 0, f;

    if (high < low)
        return 0;

    f = getonesnum(high);
    while (high >= low && f < high)
    {
        high = f;
        f = getonesnum(high);
    }
    if (high < low)
        return 0;
    else if (f == high) //若进入while循环,则此式理应成立
        return high;

    // f(high) > high 的情况
    f = getonesnum(low);
    while (low <= high && f > low)
    {
        low = f;
        f = getonesnum(low);
    }
    if (low > high)
        return 0;
    else if (f == low)
    {
        ans = getboundedans(low + 1, high);
        if (ans > low) // 此时(low + 1, high)区间有解
            return ans;
        else
            return low;
    }

    // f(high) > high 且 f(low) < low 的情况
    --high;
    ++low;
    mid = (low + high) / 2;
    ans = getboundedans(mid, high);
    if (ans != 0)
        return ans;
    else
        return getboundedans(low, mid - 1);

}


//找出不大于n的 f(n)=n 的最大解
__int64 findmaxsolution(__int64 n)
{
    if (n < 1) // f(0) == 0 ?
        return 0;
    else
        return getboundedans(1, n);

}


int main()
{
    __int64 n;

    //测试
    n = findmaxsolution(400000);
    printf("n = %I64d, f = %I64d\n", n, getonesnum(n));

    n = findmaxsolution(4000000000);
    printf("n = %I64d, f = %I64d\n", n, getonesnum(n));

    n = findmaxsolution(400000000000);
    printf("n = %I64d, f = %I64d\n", n, getonesnum(n));

    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值