寻找第k个十一非自由数。

12

让我们定义eleven-non-free数字:

如果我们将一个数字视为字符串,那么如果其中任何子字符串是11的(非零)幂,则此数字是一个eleven-non-free数字。

例如,1123是一个eleven-non-free数字,因为内部的1111^1。同样,12154也是一个eleven-non-free数字,因为12111^2。但是12345不是,因为我们找不到任何非零幂11

所以给定一个k,找到第k个eleven-non-free数字。例如,第13个这样的数字是211

我不知道如何高效地做到这一点。暴力的方法是从1开始递增i,并检查每个数字并计数,直到第k个。

我想我们应该考虑具有不同长度(1、2、3、4、...)的字符串。然后对于每个长度,我们尝试填充11、11^2、11^3等,并尝试获取所有组合。

但这看起来也很复杂。

有人能提供思路吗?


3
你尝试过编写代码来解决这个问题吗?虽然我不反对纯理论问题,但如果你能描述一下这个问题的实际用途,可能会得到更多的答案。 - Reinstate Monica -- notmaynard
1
@iamnotmaynard,实际上这是http://projecteuler.net/problem=442的变异。它不一定是一个学术问题,可以考虑面试问题,其中许多问题你在实践中永远不需要。 - Jackson Tale
2
@JacksonTale:“想一想那些你在实际工作中永远不需要的糟糕面试问题” - 我替你修正了一下。;-) - Chris
@Chris,这真的取决于情况。例如,“纽约有多少窗户”这样的糟糕面试问题虽然不实用,但确实有其目的。 - Jackson Tale
5
Project Euler 的问题应该是你自己的工作。公开发布答案是不鼓励的。 顺便说一句,感谢你的发布。我有段时间没做 Project Euler 了,200多个问题通常对我来说太难了,但是我认为我可以解决这一个。 - Justin Blank
显示剩余18条评论
8个回答

3

好的,这花了几天时间才解决。要理解的第一件重要的事情是,欧拉谜题442唯一的具体要求是解决E(10 ^ 18)。与11-free number意味着什么的无关示例只会分散注意力。

暴力选项是不可能的,原因有2个。

  1. 它需要进行五千万亿次字符串比较,并且在大多数家用电脑上需要一周才能解决。

  2. Leonhard Euler是一位数学家,所以问题的精神必须在这个背景下解释。

过了一会儿,我开始专注于10的幂的模式匹配,而不是在算法中不断包括11的幂。在计算出11的幂之后,您就可以完成与此相关的任何内容。它们只表示字符串。它们甚至没有包含在最终算法中,只是用于侦查工作。

我开始尝试详细了解如何在10的幂之间引入新的11包含数字,以及是否存在可预测的模式。如果有,那么只需使用该模式推断出在任何10 ^停止点之前引入的所有包含11的数字。

理解主要概念

对于每个新的10^到达,以前的11^字符串匹配数与以前的10^范围相同。使用图表更容易解释。

这是一个10 ^ 2-10 ^ 6列表,显示到目前为止引入的新11包含数字的计数,并按匹配的11 ^字符串分组。

图1.)按11 ^字符串匹配分组的10 ^范围中的11 ^匹配数

---------------------------
10^2 range (10 - 100)
---------------------------
    11            1
---------------------------
10^3 range (100 - 1000)
---------------------------
    11           18
    121           1
---------------------------
10^4 range (1000 - 10000)
---------------------------
    11          260
    121          18
    1331          1
---------------------------
10^5 range (10000 - 100000)
---------------------------
    11         3392
    121         260
    1331         18
    14641         1
---------------------------
10^6 range (100000 - 1000000)
---------------------------
    11        41760
    121        3392
    1331        260
    14641        18
    161051        1

etc...

制作此列表有两个要求。

  1. 当多个包含11的数字按顺序匹配(例如11000-11999),所有这些项都必须归属于第一个匹配的11^组。这意味着即使在该示例中121和1331也有匹配,但该范围内的所有匹配都归属于11,因为它是第一个。

  2. 匹配是基于最短可能性首先进行的。即:11、121、1331等等...这是因为有些数字匹配多个11^字符串,而其他数字出现在连续范围内。从高到低匹配并不产生可预测的模式。从功能上讲,它们都是相同的。这只是为了使图片更加清晰。

请注意,在每个新的10^范围内,仅引入1个新的11^匹配。而且,每个以前的11^数字的匹配数量都是相同的,无论以前的幂是多少。难点在于不能直接从以前的幂中推断出*11字符串匹配的数量。

图2.) 将其他11^模式与*11匹配区分开来

    ****11
    ***110 pattern
    **1100 pattern
    *11000 pattern
    110000 pattern
    etc...

所有的“模式”匹配都在10^次方内高度可预测。只有*11个数字在累积方式上不明显。实际上并不是没有模式,问题在于你必须假设从右到左扫描数字,结果其他模式就再也无法预测了。
事实证明,必须同时跟踪一个单独的累加器,以便我们可以使用先前的*11匹配来计算新的*11匹配数量。对于任何数学天才,可能会有更优雅的解决方案。但这是我的方案。
您必须始终单独跟踪前两个幂的*11匹配数。使用它们作为操作数,您可以计算当前幂的*11匹配数。
例如:
10^3范围内的*11匹配数为8。还有10个“110”的匹配,但这里并不重要,我们只跟踪以“11”结尾的数字。因此,我们从前两个10^范围的*11匹配计数开始,分别为8和0。
重要提示:对于小于10^2的任何数字,0都是该计算的前一个匹配计数。这就是为什么在处理10^3时,0是第二个回溯操作数的原因。
图3)如何使用回溯计算每个10^范围内的*11匹配计数。
    10^3 |   80 = 8 * 10 - 0;
    10^4 |  792 = 80 * 10 - 8;
    10^5 | 7840 = 792 * 10 - 80;

然而,仅仅从不同的角度观察这个数字是有用的。

图4.) 展示了匹配组如何按10的幂进行缩放的示例。

    ==================================
      #    Formulas for 10^4
    ==================================
     80    **11         = 8 * 10 - 0
     80    *110 pattern = previous power's *11 times 10
    100    1100 pattern = previous power's 110 times 10
    ==================================
    260   Total matches in range

    ==================================
       #  Formulas for 10^5
    ==================================
     792  ***11         = 80 * 10 - 8
     800  **110 pattern = previous power's **11 times 10
     800  *1100 pattern = previous power's *110 times 10
    1000  11000 pattern = previous power's 1100 times 10
    ==================================
    3392  Total matches in range

在这些示例中,只有*11数字需要单独计算。看到这些数字堆叠在一起会让它感觉比实际上更加复杂。重要的是理解这个概念。在实践中,我们通过累积乘法将除*11计算以外的所有东西抽象出来。
现在,在我们进入工作算法之前,最后一个概念需要理解的是如何使用这些模式来计算每个^10结尾的第N个11-free数字。
这是一个两步过程,因此我将使用一个示例进行演示。让我们看一下图1中1000-10000范围内的情况。10000是10^4,因此这是我们要解决的范围。
以下11^组都在该范围内。18和1可以从以前的幂(见图1)推导出来。
    match         #
    ---------------
    11          260
    121          18
    1331          1
  1. Calculate the total 11-containing matches in the current 10^4 range. To do this, we first have to calculate the value for the "11" match category. The 260 value above can be calculated using numbers from both the previous ranges and the separate *11 tracking operands like so:

    Calculating *11 count for 10^4

        260 = (18 * 10) + (8 * 10 - 0)        
    
    • Where 18 is the total of all "11" matches (not necessarily *11) from the 10^3 range (see Fig 1).
    • The 10's are constants.
    • And, 8 and 0 are our starting points, mentioned previously, for separately calculating *11 changes.
  2. Combine the result of 10^4 with all the other results back to 10^2. Note: The total matches for 10^2 is 1 because there is only one 11-containing number from 0 through 100.

    Total 11-containing numbers in 10^4 range:  279 = 260 + 18 + 1
    Total 10^4 range plus previous totals    :  299 = 279 + 19 + 1
                                               ---------------------
                                  10^4 solved: 9701 = 10^4 - 299
    

一个可行的算法

这是一个用C#编写的算法,可以解决10^1到10^18的每个问题。它几乎瞬间返回结果。出于对欧拉计划的尊重,我没有直接发布答案。不过输出结果是准确的。目前第442个问题只有163人解决了,而第1个问题已经有336260人解决了。如果那个数字开始无缘无故地增长,我将删除此帖子。

    private ulong GetNthElevenFreeForPow10(uint pow){

        if(pow > 18)
            throw new ArgumentException("Argument must be a positive integer between 1 and 18", "pow");

        // All of the numbers up to 10^0 & 10^1 are 11-free
        // if 1 is considered 11-free as the euler question 
        // states.
        if (pow == 0 || pow == 1)
            return (ulong)Math.Pow(10, pow);

        // The sequence of 11s doesn't form a repeatable pattern
        // until 10^4 because it requires back tracking to 
        // calculated running totals.
        if (pow == 2)
            return (ulong)Math.Pow(10, pow) - 1;

        if (pow == 3)
            return (ulong)Math.Pow(10, pow) - (18 + 1 + 1);

        // At each new power the number of elevens created is 
        // easily predicted based on the previous power. But, 
        // the number of new entries ending with 11 i.e.) xxx11
        // is a little trickier. It requires a lookback at the 
        // two previous powers. This array stores the lookback 
        // operands used to make that calculation.
        ulong[] lookbackOperands = new ulong[] { 
            0, 8
        };

        // this number is used to calculate all of the new 11-containing 
        // numbers for each power. The next power is always a calculation 
        // on the previous.
        ulong elevensConsumedCurrent = 18;
        ulong elevensConsumedPrevious = 18 + 1;

        // this number holds a running total all 11-containing
        // numbers consumed so far.
        ulong elevensConsumedTotal = 20;

        for (int n = 4; n <= pow; n++) {
            // Calculate the number of new items added that end with "11".
            ulong endsWith11Current = lookbackOperands[1] * 10 - lookbackOperands[0];
            lookbackOperands[0] = lookbackOperands[1];
            lookbackOperands[1] = endsWith11Current;

            elevensConsumedCurrent = elevensConsumedCurrent * 10 + endsWith11Current;
            elevensConsumedPrevious += elevensConsumedCurrent;
            elevensConsumedTotal += elevensConsumedPrevious;
        }

        return (ulong)Math.Pow(10, pow) - elevensConsumedTotal;

    }

使用方法:

        StringBuilder sb = new StringBuilder();
        for (uint n = 1; n <= 18; n++)
            sb.AppendLine(String.Format(
                    "Nth 11-Free Number @ 10^{0}\t{1}", 
                    n, 
                    GetNthElevenFreeForPow10(n).ToString()
                )
            );

        Console.WriteLine(sb.ToString());

以下是前15个。
Nth 11-Free Number @ 10^1   10
Nth 11-Free Number @ 10^2   99
Nth 11-Free Number @ 10^3   980
Nth 11-Free Number @ 10^4   9701
Nth 11-Free Number @ 10^5   96030
Nth 11-Free Number @ 10^6   950599
Nth 11-Free Number @ 10^7   9409960
Nth 11-Free Number @ 10^8   93149001
Nth 11-Free Number @ 10^9   922080050
Nth 11-Free Number @ 10^10  9127651499
Nth 11-Free Number @ 10^11  90354434940
Nth 11-Free Number @ 10^12  894416697901
Nth 11-Free Number @ 10^13  8853812544070
Nth 11-Free Number @ 10^14  87643708742799
Nth 11-Free Number @ 10^15  867583274883920

2
作为一个很好的起点,考虑与计算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_0s的后缀,所以让我们将s写成其他字符串qs_0的串联形式:s=[q,s_0]。现在,让r是以s开头的任何数字字符串,并再次将其写成串联形式:r=[s,r_0]=[q,s_0,r_0]
如果r包含一个POE作为子串,那么这个POE要么包含在r_0中,要么跨越sr_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
      ){
    // Dynamic programming case
    BigInteger res = resultMap.get(new Pair(prefix,k));
    if(res != null)
      return res;

    // base cases
    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);
    }

    // recursive summation
    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;
  }  


  //
  // helper functions
  //

  // returns all POEs of at most length digits
  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;
  }

  // finds the longest string that is both a prefix of s and a suffix of a POE
  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

抱歉,但这并不是很清楚。请描述一个算法来解决OP的问题(而不是其他可以修改的问题),并证明其时间复杂度。 - Tomas
1
有什么不清楚的吗?我已经用伪代码解释了基本算法,然后给出了明确的Java代码来解决OP的确切问题。我没有证明时间复杂度,虽然很明显它的运行时间是多项式到数字的数量。从实验上看,运行时间似乎为“O(n ^ 4.5)”,尽管这可能可以改进。我并不声称这是最快的算法,但肯定比暴力算法要好得多。 - mrip
很遗憾这个答案只得到了一半的赏金,而完整的赏金没有被授予! - Tomas

2
有趣的是,人们很容易被“暴力破解”这个术语所吓倒,甚至没有尝试量化它 :)
暴力解决方案实际上是O(R * log R)。
其中R是结果(第k个非免费数字)。您只需测试所有1..R的数字,并为每个数字执行字符串搜索11、121、1331、14641等,使用准备好的自动机(对于给定的数字位数)。如果您从这个角度看待它, O(R * log R)不那么糟糕,不是吗? :-)
生成解决方案?
巧妙的想法是尝试更直接地生成数字:
1.通过直接生成第k个数字; 2.通过按顺序生成所有1-k个非免费数字; 3.通过一次生成< 10^n的所有非免费数字并对其进行排序。
如果不是接近不可能,方案1将必须非常聪明。方案2比暴力解决方案更好,因为它跳过了那些不是十一非自由的数字。方案3将是一个易于实现的替代方案,这使我们面临一个重要问题:
< 10^n存在多少个非免费数字?
简单的组合数学(仅适用于n <= 10;对于更高的n,这是一个接近估计,忽略11^2是11^13的子字符串,11是11^11的子字符串,11^19):
所以基本上对于限制为10^n,您将获得超过10^(n-2)个解决方案!这意味着解决方案的数量是极限的常数比例,这意味着
O(R)= O(k)
这意味着
暴力破解解决方案实际上是O(k * log k),以及“生成所有”解决方案!
可怕的暴力破解解决方案看起来好多了,不是吗?然而,它仍然是一样的 :)
我们能做得比这更好吗?
  • 解决方案1 - 可能是一次机会,但接近于神奇。
  • 解决方案2 - 最好的情况是O(k),但您必须非常小心才能实现这一点。有许多复杂因素会使选择下一个最小的十一个非自由数字变得困难。例如,在生成所有11xxx、121xx、1331x时,例如13121位于中间,使得任何有序数字的自动生成都很困难,更不用说在xxx数字中出现模式引起的重复性等问题。您可能最终会得到一些复杂的数据结构,每一步都需要O(log k),因此总共需要O(k log k)
  • 解决方案3 - 我们已经知道这是O(k log k),与查找k个数字相同。

好!我从来没有去修复我的答案,现在我也不需要了。 ;) - Timothy Shields
不要使用蛮力法——有一个O(polylog(k))时间复杂度的算法。也许其他人会有兴趣透露一些关于它的细节。 - David Eisenstat
动态规划在这里起作用。找到第k个非11自由数与计算给定长度的非11自由数的数量密切相关,这是DP擅长的事情。例如,请参见下面的答案。 - mrip
我看到你真的对十一非自由数问题的更好解决方案感兴趣。我解决了欧拉版本并发布了它,但我很可能很快就会删除它。我为发布它感到内疚。我不想冒犯任何希望答案保密的人。所以如果你有兴趣,现在可以看到它。顺便说一句,我很羡慕你能从源代码中回溯代数方程。无论如何,保重。 - drankin2112
@drankin2112 我支持你的解决方案。为什么要删除它呢?那是你的工作,你完全拥有它的作者权利! - Tomas
@Tomas - 是的,我知道。但对我来说,这更多是关于挑战而不是其他任何事情。然而,我越想越觉得,像projecteuler.net这样的网站唯一的奖励就是知道你解决了问题的满足感。为什么会有人试图作弊呢?我们拭目以待。谢谢你的+1。回赠给你。 - drankin2112

1

编辑:这种方法会错过一些非自由数,例如1011。我知道问题所在并将修复它,但现在无法做到。

让我们采取一个原则性的方法,逐步构建解决方案,而不是试图一次处理所有问题。

设S为11的幂序列:

11、121、1331、14641、161051、……

设E(n)为包含n的字符串表示形式作为子字符串的数字集合。您要查找的集合是S中n的E(n)的并集。

如何为某些n生成E(n)的元素?(让我们以n = 121为例。)

创建一个有向无环图,其中n是根节点。使n指向以下19个数字中的所有数字:

附加0-9:n0 n1 n2 n3 n4 n5 n6 n7 n8 n9
在1-9之前添加:1n 2n 3n 4n 5n 6n 7n 8n 9n

对于每个新节点,重复这个过程。请注意,某些节点可能会有多个父节点,因此您需要维护从数字到节点指针的哈希映射。(您可以通过121->1217->71217或121->7121->71217访问71217。)
此图具有以下特性:对于u到v的任何边缘,我们都有u
现在将这些图合并到S中的所有n中。
您将拥有一个大的有向无环图(无限),可以以广度优先的方式生成它,始终扩展具有最小编号的未扩展节点,发出下一个(第k个)十一非自由数。
伪代码
它有效,我已经测试过了。这是C#要点:https://gist.github.com/timothy-shields/8026924
procedure generate_eleven_non_free
i = 1
n = 11^i
//frontier nodes; F.insert(x) does nothing if x already in F
F = empty-min-heap-set
while (true)
{
    if (F.empty() or F.min() > n)
    {
        F.insert(n)
        i = i + 1
        n = 11^i
    }
    else
    {
        m = F.extractmin()
        yield m
        for j = 0:9        
            F.insert(append(m,j))
        for j = 1:9
            F.insert(append(j,m))
    }
}

要获取第k个十一非自由数,只需执行类似 generate_eleven_non_free().element_at(k) 的操作,其中 element_at(k) 应该选择第k个产生的数。

1
其实我认为如果我们可以建立一个关系,使得树中同一层级的元素也保证按照递增/递减顺序排列,那么这个方法是可行的。否则,每一层级的元素数量将呈指数级增长,以19的幂次方增长。 - Abhishek Bansal
@AbhishekBansal 元素数量呈指数增长是非常正常的。如果你正在寻找第k个十一非自由数,你仍然只需要在图中生成O(k)个节点。 - Timothy Shields
看看 k 是否等于 21。如果不区分同一层级的元素,那么在最坏情况下,你需要生成 19*19 个节点吗? - Abhishek Bansal
@AbhishekBansal 当我说你只需要生成O(k)个节点时,这意味着它受到常数c的限制,大约为19。仔细考虑一下 - 这绝对是一个正确的O(k)空间/时间解决方案。 - Timothy Shields
@AbhishekBansal 我认为你没有正确理解这个工作方式。常数19不是问题。如果有什么问题,那就是渐近复杂度O(k)的问题。你可以希望得到一些次线性的东西,但我立刻看不出如何超越它。 - Timothy Shields
显示剩余2条评论

0

给定一个k,找到第k个十一非自由数。...是一个很大的问题。

由于这是一个耗时的问题,我写了伪代码。

  1. 第一步 保留十一的幂方便使用

var eleven_powers = []; for (var i=0; i<1000; i++) eleven_powers.push(Math.pow(11,i));

  1. 第二步 尽可能地保留所有可能的非十一自由数。这将涉及到大整数的处理。

for (var i=11; i<Integer.MAX_VALUE; i++){ if(string(i).substring(for each of eleven_powers) == true) store it sequentially results_array }

  1. 第三步 取k,返回results_array[k]

0

10^18非常大。暴力破解不是你的朋友。

然而,让我们注意一些便利:

  • 如果字符串s表示一个十一位非自由数,则d s(其中d是数字字符串)也是如此。
  • 如果您想知道有多少个k位数字是十一位非自由数,则可以基于有多少个k-1位数字是十一位非自由数。
  • (例如,有多少个1xx数字是十一位非自由数?好吧,有多少个xx数字是十一位非自由数?显然至少有那么多个1xx数字是十一位非自由数。唯一的额外情况是当十一的幂以刚刚附加到前面的数字开头时--即以1开头的数字。)
  • 这表明了一种相对简单的动态规划方法,用于确定具有固定前缀的已知长度字符串中有多少个十一位非自由数。(例如,在934xxxxx中有多少个十一位非自由数)

最后,添加一些二分搜索样式的逻辑,您应该能够找到第N个十一位非自由数是哪个数字。


我省略了很多解释,希望这能给你解决问题的“推动”。如果您想要更明确的答案,请说明。 - Kaganar
我认为你还需要考虑后缀 - 即 x s y 而不是 x s,其中 xy 可以为空(但不能同时为空,因为你已经知道 s 的状态)。否则你可能会错过像 112 这样的东西... - twalberg
这里的概念是我们在已知字符串类别的基础上添加前缀。也就是说,我们知道两位数(即形如xx的数字)中恰好有一个非自由十一位数。如果我们想要检查1xx形式的数字,我们只需要添加以新添加的“1”开头的十一次幂的情况,因为所有后缀情况都已经处理过了。因此,没有理由单独考虑后缀。(因此,我们将寻找11和121是否出现在1xx中,如果是,那么这个数字已经被计算过了吗?) - Kaganar
是的,但你是通过考虑已经在类中的字符串来构建该类。如果你从 {11, 121, ...} 开始,如果不考虑后缀,你怎么能确定 112 属于该类呢?我同意,如果你知道 112 已经在类中,那么你添加的任何前缀也应该产生一个类的成员,但如果你只考虑前缀,你就没有生成完整的集合。你需要考虑有前缀和后缀的情况,以及有前缀和后缀的情况。 - twalberg

0
这看起来像是一个 O(klog^2(k)) 的算法:
def add(vals, new_vals, m, val):
    if val < m and val not in vals and val not in new_vals:
        new_vals.append(val)

def kth11(k):
    vals = [ 11**i for i in range(1, k+1) ]
    new_vals = [ ]
    while True:
        vals = sorted(vals + new_vals)
        vals = vals[:k]
        m = vals[-1]
        new_vals = [ ] 
        for v in vals:
            for s in range(1, 10):
                val = int(str(s) + str(v))
                add(vals, new_vals, m, val)
            for s in range(0, 10):
                val = int(str(v) + str(s))
                add(vals, new_vals, m, val)
        if len(new_vals) == 0: break
    return vals[-1]

print kth11(130)

0

这听起来像是一个分治算法。

这个问题感觉非常类似于:http://www.geeksforgeeks.org/divide-and-conquer-maximum-sum-subarray/

以及这个:http://en.wikipedia.org/wiki/Maximum_subarray_problem

当你进行分治时,将原始字符串s分成大约一半大小的两部分。在递归的底部,您需要处理小型的易于处理的问题,这些问题包含所有不包含11的字符(因为它们不包含足够的字符以内部具有任何11的幂)。

“诀窍”在于当您“向上”递归时。在此步骤中,您需要利用要查找的任何“幂”(即121)必须跨越输入字符串的“中间”的事实。

例如,当您从长度为1的字符串“向上”迈进到长度为2的字符串时,您将知道您正在寻找的某些幂位于输入字符串的第一半部分,而另一些幂位于第二半部分。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接