您的位置:首页 > 其它

正整数中数字1的计数问题 - 采用分治法快速计算f(n)(下)

2011-05-22 16:43 429 查看
在我的另外一篇文章"正整数中数字1的计数问题(上)"中,实现了一个简单的算法来计算f(n)。该算法由于

只考虑相邻两个数的变化规律,因此在计算单个长整数时(比如n=911111111099999009L),可

能要很长时间才能算完,显然该算法适用范围比较狭小。本文继续围绕那道Google面试题,探讨一个

计算单个值f(n)的快速算法以及如何求出所有满足f(i)=i这样的整数(对很大的数计算时间能达到毫秒数量级)。

分析:

首先注意到,比如要计算i=3260这样的f(i),在算到i=3200的时候,其实又重复了以前的计算,即重新

计算1到60的值。本算法就是基于这个考虑,将一个数拆成左右两半,采用分治法(divide-and-conquer)

来计算每一半的值,然后将二个值合并到一起。这与归并排序算法(merge sort)的思想是一样的。

首先推导适合于分治算法的公式:

以i=3260为例,将该数拆成左右两半,左边等于32,右边等于60。可以找到这样的等式:

f(3260) = f(3200) + [ones(32)*60 + f(60)]。

其中ones(32)表示数32中含有数字1的个数(这里等于0),ones(32)*60表示32这个数含有数字1的个数需

要重复60次(即从3201~3260),上面方括号中的值就是从3201到3260这总共60个数中包含数字1的总数。

很明显,经过这一步,f(3260)转化为只需计算很小的数f(60)和另外一个还是很大的数f(3200)。接着对

f(3200)进行类似的处理:

f(3200) = ones(3200) + f(3199)

= ones(32) + f(3199)

= ones(32) + [f(3100) + ones(31)*99 + f(99)]; ....(等式1)

f(3100) = ones(31) + f(3099)

= ones(31) + [f(3000) + ones(30)*99 + f(99)]; ....(等式2)

......

f(200) = ones(2) + f(199)

= ones(2) + [f(100) + ones(1)*99 + f(99)]; ....(等式31)

f(100) = ones(1) + f(99)

= ones(1) + [f(99)]. ....(等式32)

上面这些等式相互替换(例如用第二式的f(3100)值替换第一式右边的f(3100),最终效果就是左右两边

累加,这是因为左边除了f(3200)未被消掉外,其他的项左右抵消)得到:

f(3200) = [ones(1)+ones(2)+...+ones(32)] + [ones(1)+ones(2)+...+ones(31)]*99 + f(99)*32

= f(31)*100 + ones(32) + f(99)*32;

很漂亮的结果!经过一系列降低复杂度处理,最终f(3200)变成了只需要计算两位数值,即f(31)和f(99)

!类似,原数值f(3260)的等式为:

f(3260) = f(31)*100 + ones(32)*61 + f(60) + f(99)*32;

= ones(32)*61 + f(60) + f(31)*100 + f(99)*32;

接着对f(31),f(60), f(90)三个数进行同样的拆分计算(复杂度降低一半!)。

推广到一般情况,对于一个比较大的数,比如n=1234567890, 将它拆成两半,设左边的值为leftNum,右边

的值为rightNum,对应右边数值的位数为rightCount,共有rightCount个9的数为allRightNines,比如

1234567890右边共有5位,那么allRightNines=99999。这样可以得到:

f(n) = ones(leftNum)*(rightNum+1) + f(rightNum)

+ f(leftNum-1)*(allRightNines+1) + f(allRightNines)*leftNum;

由于像f(9),f(99),f(999)这样的数的上面的等式中要

重复使用,因此在计算过程中保留这些结果,便于共用,达到以空间换时间的效果。

更新: 计算所有满足f(i)=i的算法也是基于分治法思想,类似于折半查找,参考以下代码中的solveBatch(...)。基本原理是基于这样的事实:f(i)是一个递增序列。如果f(i)=j,而且j>i,那么从i到j中间的任何数k(k!=j)不可能等于f(k),因为f(k)>=f(i),也就是f(k)>=j>k。所以只需要考虑下一个数f(j)是否等于j。如果f(i)=j,而且j<i,情况类似。

以下是实现代码:在Pentium M 1.4GHz, 512M内存PC上,计算f(911111111099999009) =

1648888888779991781以及计算满足f(i)=i(i<=911111111099999009)这样的数总共耗时0.221秒。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
* @author ljs
* 2011-05-23
*
*/
public class OnesCounter {
//memoization: save the all nines f(n), like f(9),f(99),f(999)....
private long[] allNinesFn;
private boolean memoization;

public OnesCounter(boolean memoization){
this.memoization = memoization;

if(memoization){
long max = Long.MAX_VALUE;

int maxDigitsCount=1;
while((max /= 10) >0)
maxDigitsCount++;
allNinesFn = new long[maxDigitsCount + 1];
}
}
/**
* Problem:
* Consider a function which, for a given whole number n,
* returns the number of ones required when writing out all
* numbers between 0 and n.
* For example, f(13)=6. Notice that f(1)=1.
* What is the next largest n such that f(n)=n?
*
* Quick Solution: using divide-and-conquer
* return f(i)
* n: long integer
*/
public long quickSolve(long n){
//the number of digits in n
int digitsCount=1;
long tmp = n;
while((tmp /= 10) >0)
digitsCount++;

//separate n into digits
byte[] digits = new byte[digitsCount];
tmp = n;
int p = digitsCount - 1;
while(tmp>0){
byte digit = (byte)(tmp%10);
digits[p--]=digit;
tmp/=10;
}

long result =  quickSolve(digits);

//memoization feasibility check: all nines
if(memoization && allNinesFn[digitsCount]==0){
boolean isAllNines = true;
for(int i=0;i<digitsCount;i++){
if(digits[i]!=9) {
isAllNines = false;
break;
}
}
if(isAllNines){
//memoization
allNinesFn[digitsCount] = result;
}
}
return result;
}

private long quickSolve(byte[] digits){
int digitsCount=digits.length;

if(digitsCount == 1) {
if(digits[0]>=1){
return 1;
}else{
return 0;
}
}

int leftCount = digitsCount / 2;
int rightCount = digitsCount - leftCount;

int leftOnes = 0;
//count 1's in the left digits
for(int i=0;i<leftCount;i++){
if(digits[i]==1)
leftOnes++;

}

long leftNum = digits[0];
//using Horner's rule
for(int i=1;i<leftCount;i++){
leftNum = leftNum*10 + digits[i];
}

int rightP = leftCount; //the start pos for the right half
long rightNum = digits[rightP];
long allRightNines = 9;
for(int i=rightP+1;i<digitsCount;i++){
rightNum = rightNum*10 + digits[i];
allRightNines = allRightNines*10 + 9;
}

long allRightNinesResult = 0;
//memoization usage
if(memoization && allNinesFn[rightCount] > 0){
allRightNinesResult = allNinesFn[rightCount];
}else{
allRightNinesResult = quickSolve(allRightNines);
}

long onesCount = leftOnes * (rightNum+1)
+ quickSolve(rightNum) + quickSolve(leftNum-1)* (allRightNines+1)
+ allRightNinesResult * leftNum;
return onesCount;
}

public static List<Long> foundList = new ArrayList<Long>();
public static void solveBatch(OnesCounter qc,long m, long n){
if(n<0 || n<m) return;

long right = qc.quickSolve(n);

if(n==m){
if(right==n){
foundList.add(n);
//System.out.format("f(%d) = %d%n",n,right);
}
return;
}

if(right<n){
solveBatch(qc,m,right);
}else if(right==n){
foundList.add(n);
//System.out.format("f(%d) = %d%n",n,right);
solveBatch(qc,m,right-1);
}else if(right>n){
long midNum = (n+m)/2;
long mid = qc.quickSolve(midNum);
if(mid<midNum){
solveBatch(qc,m,mid);
solveBatch(qc,midNum+1,n-1);
}else if(mid==midNum){
foundList.add(midNum);
//System.out.format("f(%d) = %d%n",midNum,mid);
solveBatch(qc,m,midNum-1);
solveBatch(qc,midNum+1,n-1);
}else{
solveBatch(qc,m,midNum-1);
solveBatch(qc,mid,n-1);
}
}

}

public static void main(String[] args) {
//long n=1111111110;
long n=911111111099999009L;

OnesCounter qc1 = new OnesCounter(true);

long start = System.currentTimeMillis();

System.out.format("Test 1: caculate F(%d)%n",n);

long ones = qc1.quickSolve(n);
System.out.format("f(%d) = %d%n",n,ones);

System.out.println();
System.out.format("**********************%n");
System.out.println();

System.out.format("Test 2: caculate F(n)=n%n");
OnesCounter qc2 = new OnesCounter(true);
OnesCounter.solveBatch(qc2, 0, n);
System.out.format("Total numbers satisfying f(n)=n (n<%d): %d%n",n,foundList.size());
Collections.sort(foundList);
for(long k:foundList){
System.out.format("%d%n",k);
}

long end = System.currentTimeMillis();

double timeElapsed = (end-start)/1000.0;
System.out.format("Time elapsed(sec): %.3f%n", timeElapsed);

}
}


测试结果:

Test 1: caculate F(911111111099999009)

f(911111111099999009) = 1648888888779991781

**********************

Test 2: caculate F(n)=n

Total numbers satisfying f(n)=n (n<911111111099999009): 84

0

1

199981

199982

199983

199984

199985

199986

199987

199988

199989

199990

200000

200001

1599981

1599982

1599983

1599984

1599985

1599986

1599987

1599988

1599989

1599990

2600000

2600001

13199998

35000000

35000001

35199981

35199982

35199983

35199984

35199985

35199986

35199987

35199988

35199989

35199990

35200000

35200001

117463825

500000000

500000001

500199981

500199982

500199983

500199984

500199985

500199986

500199987

500199988

500199989

500199990

500200000

500200001

501599981

501599982

501599983

501599984

501599985

501599986

501599987

501599988

501599989

501599990

502600000

502600001

513199998

535000000

535000001

535199981

535199982

535199983

535199984

535199985

535199986

535199987

535199988

535199989

535199990

535200000

535200001

1111111110

Time elapsed(sec): 0.220
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐