作为一个很好的起点,考虑与计算N位数字中有多少个不自由数(ENF)相关的问题。这并不完全等同于找到第n个ENF整数,但后者可以相对容易地转化为前者(请参见本文末尾提供的执行此操作的Java代码)。
其想法是在前缀上进行动态编程。对于任何数字字符串s和任何整数k,让N(k,s)表示以s开头的长度为k的ENF字符串的数量。对于任何字符串s,让s0是s的最长后缀,它也出现在长度小于或等于k的11的幂的任何前缀中。然后,假设s本身不包含任何POE子字符串,则我们有以下方程式:
N(k,s) = N(k-length(s)+length(s_0),s_0)
这个等式的推导如下。因为
s_0
是
s
的后缀,所以让我们将
s
写成其他字符串
q
和
s_0
的串联形式:
s=[q,s_0]
。现在,让
r
是以
s
开头的任何数字字符串,并再次将其写成串联形式:
r=[s,r_0]=[q,s_0,r_0]
。
如果
r
包含一个POE作为子串,那么这个POE要么包含在
r_0
中,要么跨越
s
和
r_0
之间的界限。在这种情况下,
s
必须以POE前缀结尾,由于我们知道出现在
s
后缀中的最长的POE前缀是
s_0
,所以如果
r
包含POE子串,则该子串必须包含在
[s_0,r_0]
中。这给了我们一一对应的关系,从以
s=[q,s_0]
开头的ENF到以
s_0
开头的ENF。
上面的等式引出了一种递归算法来计算具有给定前缀的k位ENF的数量。基本情况最终是
length(s_0)=length(s)
的实例,这意味着
s
本身是长度小于等于k的POE的前缀。由于这样的POE前缀不算多(最多k个选择2,即O(k^2)),这个公式导致了一种有效的算法。以下是伪代码:
Given a prefix s and an integer k, this function computes N(k,s)
if s contains a POE then return 10^(k-length(s))
if length(s) = k return 0
let s0 = longest POE prefix which is a suffix of s
if(length(s0)<length(s))
return N(k-length(s)+length(s0),s0)
total = 0
for d = "0" to "9"
total += N(k,concatenate(s,d))
return total
使用动态规划,该算法的运行时间将在k(位数)中是多项式级别的。该算法仅递归于POE前缀,由于这些前缀有O(k^2)个,因此运行时间将是每次迭代的运行时间的O(k^2)倍。使用朴素的O(k^2)算法来查找与POE前缀匹配的最长后缀将导致O(k^4)的算法,而使用基数树以线性时间查找匹配项将导致O(k^3)。下面给出的java代码使用了朴素的算法,实验表明,对于k值高达100左右的值,似乎是Θ(k^4.5),尽管这种差异完全可能是由于实现细节或常数因素影响小输入大小的运行时间。以下是代码:
public class Eleven {
public static final String[] digits =
{"0","1","2","3","4","5","6","7","8","9"};
public static BigInteger countENF(String prefix, int k){
return countENF(prefix,k,new HashMap(),getPowers(k));
}
public static BigInteger countENF(String prefix,
int k,
// This map contains stored results for dynamic programming
// Pair is an auxiliary class that does what you think it does
Map<Pair<String,Integer>,BigInteger> resultMap,
// precomputed list of powers of 11
List<String> powers
){
BigInteger res = resultMap.get(new Pair(prefix,k));
if(res != null)
return res;
if(!isEFree(prefix, powers)){
res = new BigInteger("10").pow(k-prefix.length());
resultMap.put(new Pair<>(prefix,k), res);
return res;
}
if(prefix.length() >= k){
res = new BigInteger("0");
resultMap.put(new Pair<>(prefix,k), res);
return res;
}
String s0 = getMaxPrefix(prefix, powers);
if(s0.length() < prefix.length()){
return countENF(s0,k+s0.length()-prefix.length(),resultMap,powers);
}
res = new BigInteger("0");
for(String d : digits){
String s1 = prefix + d;
res = res.add(countENF(s1,k,resultMap,powers));
}
resultMap.put(new Pair<>(prefix, k), res);
return res;
}
private static List<String> getPowers(int length) {
List<String> ret = new ArrayList<>();
BigInteger val = new BigInteger("11");
BigInteger eleven = new BigInteger("11");
while(val.toString().length() <= length){
ret.add(val.toString());
val = val.multiply(eleven);
}
return ret;
}
private static String getMaxSuffix(String s, List<String> powers){
for(int i=s.length(); i>0; i--){
String sub = s.substring(0,i);
for(String p : powers){
if(p.endsWith(sub))
return sub;
}
}
return "";
}
public static boolean isEFree(String s, List<String> powers){
for(String p : powers){
if(s.indexOf(p)>=0)
return false;
}
return true;
}
如上所述,这个算法并不能解决OP中的确切问题。但是,修改此代码以实际找到第n个ENF数字非常简单。通过反复调用countENF
,我们首先确定第n个ENF数字有多少位数,然后逐个数字从左到右确定第n个ENF的数字。
public static String getNthENF(BigInteger i){
int k = i.toString().length();
HashMap results = new HashMap();
List<String> powers = getPowers(k);
while(countENF("",k,results,powers).compareTo(i) < 0){
k++;
powers = getPowers(k);
}
String solution = "";
BigInteger total = new BigInteger("0");
while(solution.length() < k){
for(String d : digits){
BigInteger newTotal = total.add(countENF(solution + d,k,results,powers));
int comp = newTotal.compareTo(i);
if(comp >= 0){
solution = solution + d;
break;
}
total = newTotal;
}
}
return solution;
}
以下是一些示例输出,显示使用动态规划计算的第N个ENF,以及使用暴力方法计算10的幂次方。
Dynamic Programming:
10^1: 118
10^2: 1178
10^3: 11680
10^4: 115730
10^5: 1146628
10^6: 11360558
10^7: 112558960
10^8: 1115229050
10^9: 11049731548
10^10: 103258311161
10^11: 935443232311
10^12: 8576360477119
10^13: 79330786951511
10^14: 732117130575070
10^15: 6880811638385388
10^16: 64284911460844887
10^17: 610616803411054857
10^18: 5759459802926457113
10^19: 54555977711878792498
10^20: 518773721711219891634
Brute Force:
10^1: 118
10^2: 1178
10^3: 11680
10^4: 115730
10^5: 1146628
10^6: 11360558
10^7: 112558960